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.

Script plugin

A plugin is any class that implements the Plugin interface.

To create a "hello world" plugin:

build.gradle.kts
abstract class SamplePlugin1 : Plugin<Project> { (1)
    override fun apply(project: Project) {  (2)
        project.tasks.register("helloTaskInRootBuildFileSamplePlugin1") {
            println("Hello world from the root build file!")
        }
    }
}
build.gradle
class SamplePlugin1 implements Plugin<Project> { (1)
    void apply(Project project) {   (2)
        project.tasks.register("helloTaskInRootBuildFileSamplePlugin1") {
            doLast {
                println("Hello world from the root build file!")
            }
        }
    }
}

apply plugin: SamplePlugin1
1 Extend the org.gradle.api.Plugin interface.
2 Override the apply method.

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

Create a class that extends the Plugin interface.

build.gradle.kts
abstract class MyCreateFilePlugin : Plugin<Project> {
    override fun apply() {}
}
build.gradle
class SamplePlugin implements Plugin<Project> {
    void apply(Project project) {}
}

2. Override the apply method

Add tasks and other logic in the apply() method.

When SamplePlugin is applied in your project, Gradle calls the fun apply() {} method defined. This adds the SampleTask to your project.

You can then apply the plugin in your build script:

build.gradle.kts
abstract class SamplePlugin1 : Plugin<Project> { (1)
    override fun apply(project: Project) {  (2)
        project.tasks.register("helloTaskInRootBuildFileSamplePlugin1") {
            println("Hello world from the root build file!")
        }
    }
}
build.gradle
class SamplePlugin1 implements Plugin<Project> { (1)
    void apply(Project project) {   (2)
        project.tasks.register("helloTaskInRootBuildFileSamplePlugin1") {
            doLast {
                println("Hello world from the root build file!")
            }
        }
    }
}

apply plugin: SamplePlugin1
1 Apply the SamplePlugin.

Note that this is a simple hello-world example and does not reflect best practices.

Script plugins are not recommended. Plugin code should not be in your build.gradle(.kts) file.

Plugins should always be written as pre-compiled script plugins, convention plugins or binary plugins.

Pre-compiled script plugin

Pre-compiled script plugins offer an easy way to rapidly prototype and experiment. They let you package build logic as *.gradle(.kts) script files using the Groovy or Kotlin DSL. These scripts reside in specific directories, such as src/main/groovy or src/main/kotlin.

To apply one, simply use its ID derived from the script filename (without .gradle). You can think of the file itself as the plugin, so you do not need to subclass the Plugin interface in a precompiled script.

Let’s take a look at an example with the following structure:

└── buildSrc
    ├── build.gradle.kts
    └── src
       └── main
          └── kotlin
             └── my-create-file-plugin.gradle.kts

Our my-create-file-plugin.gradle.kts file contains the following code:

buildSrc/src/main/kotlin/my-create-file-plugin.gradle.kts
abstract class CreateFileTask : DefaultTask() {
    @get:Input
    abstract val fileText: Property<String>

    @Input
    val fileName = "myfile.txt"

    @OutputFile
    val myFile: File = File(fileName)

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

tasks.register<CreateFileTask>("createMyFileTaskInConventionPlugin") {
    group = "from my convention plugin"
    description = "Create myfile.txt in the current directory"
    fileText.set("HELLO FROM MY CONVENTION PLUGIN")
}
buildSrc/src/main/groovy/my-create-file-plugin.gradle
abstract class CreateFileTask extends DefaultTask {
    @Input
    abstract Property<String> getFileText()

    @Input
    String fileName = "myfile.txt"

    @OutputFile
    File getMyFile() {
        return new File(fileName)
    }

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

tasks.register("createMyFileTaskInConventionPlugin", CreateFileTask) {
    group = "from my convention plugin"
    description = "Create myfile.txt in the current directory"
    fileText.set("HELLO FROM MY CONVENTION PLUGIN")
}

And the buildSrc build file contains the following:

buildSrc/build.gradle.kts
plugins {
    `kotlin-dsl`
    `maven-publish`
}

repositories {
    gradlePluginPortal()
}

group = "com.example"
version = "1.0.0"

gradlePlugin {
    plugins {
        create("my-binary-plugin") {
            id = "com.example.my-binary-plugin"
            implementationClass = "MyCreateFileBinaryPlugin"
        }
    }
}

publishing {
    repositories {
        mavenLocal()
    }
}
buildSrc/build.gradle
plugins {
    id 'groovy-gradle-plugin'
    id 'maven-publish'
}

repositories {
    gradlePluginPortal()
}

group = 'com.example'
version = '1.0.0'

gradlePlugin {
    plugins {
        create("my-binary-plugin") {
            id = "com.example.my-binary-plugin"
            implementationClass = "MyCreateFileBinaryPlugin"
        }
    }
}

publishing {
    repositories {
        mavenLocal()
    }
}

The pre-compiled script can now be applied in the build.gradle(.kts) file of any subproject:

build.gradle.kts
plugins {
    id("my-create-file-plugin")  // Apply the convention plugin
    id("com.example.my-binary-plugin") // Apply the binary plugin
    `kotlin-dsl`
}
build.gradle
plugins {
    id 'my-create-file-plugin' // Apply the convention plugin
    id 'com.example.my-binary-plugin' // Apply the binary plugin
    id 'groovy' // Apply the Groovy DSL plugin
}

The createFileTask task from the plugin is now available in your subproject.

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 as a Kotlin script called MyConventionPlugin.kt and stored in buildSrc:

buildSrc/src/main/kotlin/MyCreateFileBinaryPlugin.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 MyCreateFileBinaryPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.tasks.register("createFileTaskFromBinaryPlugin", CreateFileTask::class.java) {
            group = "from my binary plugin"
            description = "Create myfile.txt in the current directory"
            fileText.set("HELLO FROM MY BINARY 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
group = "com.example"
version = "1.0.0"

gradlePlugin {
    plugins {
        create("my-binary-plugin") {
            id = "com.example.my-binary-plugin"
            implementationClass = "MyCreateFileBinaryPlugin"
        }
    }
}

publishing {
    repositories {
        mavenLocal()
    }
}
buildSrc/build.gradle
group = 'com.example'
version = '1.0.0'

gradlePlugin {
    plugins {
        create("my-binary-plugin") {
            id = "com.example.my-binary-plugin"
            implementationClass = "MyCreateFileBinaryPlugin"
        }
    }
}

publishing {
    repositories {
        mavenLocal()
    }
}

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("my-convention-plugin") // Apply the 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.