If Gradle or the Gradle community does not offer the specific capabilities your project needs, creating your own plugin could be a solution.

Additionally, if you find yourself duplicating build logic across subprojects and need a better way to organize it, custom plugins can help.

Custom plugin

A plugin is any class that implements the Plugin interface. The example below is the most straightforward plugin, a "hello world" plugin:

build.gradle.kts
import org.gradle.api.Plugin
import org.gradle.api.Project

abstract class SamplePlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.tasks.create("SampleTask") {
            println("Hello world!")
        }
    }
}

Script plugin

Many plugins start as a script plugin coded in a build script. This offers an easy way to rapidly prototype and experiment when building a plugin. Let’s take a look at an example:

build.gradle.kts
// Define a task
abstract class CreateFileTask : DefaultTask() {                                     (1)
    @get:Input
    abstract val fileText: Property<String>                                         (2)

    @Input
    val fileName = "myfile.txt"

    @OutputFile
    val myFile: File = File(fileName)

    @TaskAction
    fun action() {
        myFile.createNewFile()
        myFile.writeText(fileText.get())
    }
}

// Define a plugin
abstract class MyPlugin : Plugin<Project> {                                         (3)
    override fun apply(project: Project) {
        tasks {
            register("createFileTask", CreateFileTask::class) {
                group = "from my plugin"
                description = "Create myfile.txt in the current directory"
                fileText.set("HELLO FROM MY PLUGIN")
            }
        }
    }
}

// Apply the local plugin
apply<MyPlugin>()                                                                   (4)
1 Subclass DefaultTask().
2 Use lazy configuration in the task.
3 Extend the org.gradle.api.Plugin interface.
4 Apply the script plugin.

1. Subclass DefaultTask()

First, build a task by subclassing DefaultTask().

abstract class CreateFileTask : DefaultTask() { }

This simple task adds a file to our application’s root directory.

2. Use Lazy Configuration

Gradle has a concept called lazy configuration, which allows task inputs and outputs to be referenced before they are actually set. This is done via the Property class type.

abstract val fileText: Property<String>

One advantage of this mechanism is that you can link the output file of one task to the input file of another, all before the filename has even been decided. The Property class also knows which task it’s linked to, enabling Gradle to add the required task dependency automatically.

3. Extend the org.gradle.api.Plugin interface

Next, create a new class that extends the org.gradle.api.Plugin interface.

abstract class MyPlugin : Plugin<Project> {
    override fun apply() {}
}

You can add tasks and other logic in the apply() method.

4. Apply the script plugin

Finally, apply the local plugin in the build script.

apply<MyPlugin>()

When MyPlugin is applied in the build script, Gradle calls the fun apply() {} method defined in the custom MyPlugin class.

This makes the plugin available to the application.

Script plugins are NOT recommended. Script plugins offer an easy way to rapidly prototype build logic, before migrating it to a more permanent solution such as convention plugins or binary plugins.

Convention Plugins

Convention plugins are a way to encapsulate and reuse common build logic in Gradle. They allow you to define a set of conventions for a project, and then apply those conventions to other projects or modules.

The example above has been re-written as a convention plugin stored in buildSrc:

buildSrc/src/main/kotlin/MyConventionPlugin.kt
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.File

abstract class CreateFileTask : DefaultTask() {
    @get:Input
    abstract val fileText: Property<String>

    @Input
    val fileName = project.rootDir.toString() + "/myfile.txt"

    @OutputFile
    val myFile: File = File(fileName)

    @TaskAction
    fun action() {
        myFile.createNewFile()
        myFile.writeText(fileText.get())
    }
}

class MyConventionPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.tasks.register("createFileTask", CreateFileTask::class.java) {
            group = "from my plugin"
            description = "Create myfile.txt in the current directory"
            fileText.set("HELLO FROM MY PLUGIN")
        }
    }
}

The plugin can be given an id using a gradlePlugin{} block so that it can be referenced in the root:

buildSrc/build.gradle.kts
gradlePlugin {
    plugins {
        create("my-convention-plugin") {
            id = "com.gradle.plugin.my-convention-plugin"
            implementationClass = "com.gradle.plugin.MyConventionPlugin"
        }
    }
}

The gradlePlugin{} block defines the plugins being built by the project. With the newly created id, the plugin can be applied in other build scripts accordingly:

build.gradle.kts
plugins {
    application
    id("com.gradle.plugin.my-convention-plugin") // Apply the new plugin
}

Binary Plugins

A binary plugin is a plugin that is implemented in a compiled language and is packaged as a JAR file. It is resolved as a dependency rather than compiled from source.

For most use cases, convention plugins must be updated infrequently. Having each developer execute the plugin build as part of their development process is wasteful, and we can instead distribute them as binary dependencies.

There are two ways to update the convention plugin in the example above into a binary plugin.

  1. Use composite builds:

    settings.gradle.kts
    includeBuild("my-plugin")
  2. Publish the plugin to a repository:

    build.gradle.kts
    plugins {
        id("com.gradle.plugin.myconventionplugin") version "1.0.0"
    }

Consult the Developing Plugins chapter to learn more.