Skip to content

Introduction to extension points

In this section you can learn how to create new extension points, how to use and configure existing ones and how to query for extensions when generating documentation.

Declaring extension points

If you are writing a plugin, you can create your own extension point that other developers (or you) can use later on in some other part of code.

class MyPlugin : DokkaPlugin() {
    val sampleExtensionPoint by extensionPoint<SampleExtensionPointInterface>()
}

interface SampleExtensionPointInterface {
    fun doSomething(input: Input): List<Output>
}

class Input
class Output

Usually you would want to provide some default implementation(s) for your extension point, you can do that within the same plugin class by extending an extension point you've just created. See Extending from extension points for examples.

Extending from extension points

You can use extension points to provide your own implementation(s) in order to customize plugin's behaviour.

You can do that within the same class as the extension point itself:

open class MyPlugin : DokkaPlugin() {
    val sampleExtensionPoint by extensionPoint<SampleExtensionPointInterface>()

    val defaultSampleExtension by extending {
        sampleExtensionPoint with DefaultSampleExtension()
    }
}

...

class DefaultSampleExtension : SampleExtensionPointInterface {
    override fun doSomething(input: Input): List<Output> = listOf()
}

If you want to extend someone else's plugin (including DokkaBase), you can use plugin querying API to do that. In the example below we will extend MyPlugin that was created above with our own implementation of SampleExtensionPointInterface.

class MyExtendedPlugin : DokkaPlugin() {
    val mySampleExtensionImplementation by extending {
        plugin<MyPlugin>().sampleExtensionPoint with SampleExtensionImpl()
    }
}

class SampleExtensionImpl : SampleExtensionPointInterface {
    override fun doSomething(input: Input): List<Output> = listOf()
}

Providing

If you need to have access to DokkaContext in order to create an extension, you can use providing instead.

val defaultSampleExtension by extending {
    sampleExtensionPoint providing { context ->
        // can use context to query other extensions or get configuration 
        DefaultSampleExtension() 
    }
}

You can read more on what you can do with context in Obtaining extension instance.

Override

By extending an extension point, you are registering an additional extension. This behaviour is expected for some extension points, for instance Documentable transformers, since all transformers do their own transformations and all of them will be invoked before proceeding.

However, a plugin can expect only a single registered extension for an extension point. In this case, you can override existing registered extensions:

class MyExtendedPlugin : DokkaPlugin() {
    private val myPlugin by lazy { plugin<MyPlugin>() }

    val mySampleExtensionImplementation by extending {
        (myPlugin.sampleExtensionPoint
                with SampleExtensionImpl()
                override myPlugin.defaultSampleExtension)
    }
}

This is also useful if you wish to override some extension from DokkaBase to disable or alter it.

Order

Sometimes the order in which extensions are invoked matters. This is something you can control as well using order:

class MyExtendedPlugin : DokkaPlugin() {
    private val myPlugin by lazy { plugin<MyPlugin>() }

    val mySampleExtensionImplementation by extending {
        myPlugin.sampleExtensionPoint with SampleExtensionImpl() order {
            before(myPlugin.firstExtension)
            after(myPlugin.thirdExtension)
        }
    }
}

Conditional apply

If you want your extension to be registered only if some condition is true, you can use applyIf:

class MyExtendedPlugin : DokkaPlugin() {
    private val myPlugin by lazy { plugin<MyPlugin>() }

    val mySampleExtensionImplementation by extending {
        myPlugin.sampleExtensionPoint with SampleExtensionImpl() applyIf {
            Random.Default.nextBoolean()
        }
    }
}

Obtaining extension instance

After an extension point has been created and some extension has been registered, you can use query and querySingle to find all or just a single implementation for it.

class MyExtension(context: DokkaContext) {
    // returns all registered extensions for this extension point
    val allSampleExtensions = context.plugin<MyPlugin>().query { sampleExtensionPoint }

    // will throw an exception if more than one extension is found
    // use if you expect only a single extension to be registered for this extension point
    val singleSampleExtensions = context.plugin<MyPlugin>().querySingle { sampleExtensionPoint }

    fun invoke() {
        allSampleExtensions.forEach { it.doSomething(Input()) }

        singleSampleExtensions.doSomething(Input())
    }
}

In order to have access to context you can use providing when registering this as an extension.

Back to top