Don’t use default package

Users who have their build files in some package will not be able to use
your plugin if it’s defined in default (no-name) package.

Follow the naming conventions

Use the sbt-$projectname scheme to name your library and artifact.
A plugin ecosystem with a consistent naming convention makes it easier for users to tell whether a
project or dependency is an SBT plugin.

If the project’s name is foobar the following holds:

BAD: foobar

BAD: foobar-sbt

BAD: sbt-foobar-plugin

GOOD: sbt-foobar

If your plugin provides an obvious “main” task, consider naming it foobar or foobar... to make
it more intuitive to explore the capabilities of your plugin within the sbt shell and tab-completion.

Use settings and tasks. Avoid commands.

Your plugin should fit in naturally with the rest of the sbt ecosystem.
The first thing you can do is to avoid defining commands,
and use settings and tasks and task-scoping instead (see below for more on task-scoping).
Most of the interesting things in sbt like
compile, test and publish are provided using tasks.
Tasks can take advantage of duplication reduction and parallel execution by the task engine.
With features like ScopeFilter, many of the features that previously required
commands are now possible using tasks.

Settings can be composed from other settings and tasks.
Tasks can be composed from other tasks and input tasks.
Commands, on the other hand, cannot be composed from any of the above.
In general, use the minimal thing that you need.
One legitimate use of commands may be using plugin to access the build definition itself not the code.
sbt-inspectr was implemented using a command before it became inspect tree.

Use sbt.AutoPlugin

sbt is in the process of migrating to sbt.AutoPlugin from sbt.Plugin.
The new mechanism features a set of user-level
controls and dependency declarations that cleans up a lot of
long-standing issues with plugins.

Reuse existing keys

sbt has a number of predefined keys.
Where possible, reuse them in your plugin. For instance, don’t define:

val sourceFiles = settingKey[Seq[File]]("Some source files")

Instead, simply reuse sbt’s existing sources key.

Avoid namespace clashes

Sometimes, you need a new key, because there is no existing sbt key. In
this case, use a plugin-specific prefix.

In this approach, every lazy val starts with obfuscate. A user of the
plugin would refer to the settings like this:

obfuscateStylesheet := file("something.txt")

Provide core feature in a plain old Scala object

The core feature of sbt’s package task, for example, is implemented in sbt.Package,
which can be called via its apply method.
This allows greater reuse of the feature from other plugins such as sbt-assembly,
which in return implements sbtassembly.Assembly object to implement its core feature.

Follow their lead, and provide core feature in a plain old Scala object.

Configuration advices

If your plugin introduces either a new set of source code or
its own library dependencies, only then you want your own configuration.

You probably won’t need your own configuration

Configurations should not be used to namespace keys for a plugin.
If you’re merely adding tasks and settings, don’t define your own
configuration. Instead, reuse an existing one or scope by the main
task (see below).

When to define your own configuration

If your plugin introduces either a new set of source code or
its own library dependencies, only then you want your own configuration.
For instance, suppose you’ve built a plugin that performs fuzz testing
that requires its own fuzzing library and fuzzing source code.
scalaSource key can be reused similar to Compile and Test configuration,
but scalaSource scoped to Fuzz configuration (denoted as scalaSource in Fuzz)
can point to src/fuzz/scala so it is distinct from other Scala source directories.
Thus, these three definitions use
the same key, but they represent distinct values. So, in a user’s
build.sbt, we might see:

should be used to create a configuration.
Configurations actually tie into dependency resolution (with Ivy) and
can alter generated pom files.

Playing nice with configurations

Whether you ship with a configuration or not, a plugin should strive to
support multiple configurations, including those created by the build
user. Some tasks that are tied to a particular configuration can be
re-used in other configurations. While you may not see the need
immediately in your plugin, some project may and will ask you for the
flexibility.

The baseObfuscateSettings value provides base configuration for the
plugin’s tasks. This can be re-used in other configurations if projects
require it. The obfuscateSettings value provides the default Compile
scoped settings for projects to use directly. This gives the greatest
flexibility in using features provided by a plugin. Here’s how the raw
settings may be reused:

In the above example, sources in obfuscate is scoped under the main
task, obfuscate.

Mucking with globalSettings

There may be times when you need to muck with globalSettings. The
general rule is be careful what you touch.

When overriding global settings, care should be taken to ensure previous
settings from other plugins are not ignored. e.g. when creating a new
onLoad handler, ensure that the previous onLoad handler is not
removed.