Universe’s extension API is designed to be self-contained: your extension depends only onDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/universeclouddev/Universe/llms.txt
Use this file to discover all available pages before exploring further.
:api and :extensions:extension-api, receives Guice-managed dependencies via field injection, and registers providers in onLoad(). This page walks through every step from project setup to deployment.
Extension Anatomy
An extension is a compiled JAR placed in./extensions/. At startup, ExtensionService adds all JARs in that directory to the runtime classloader and then scans for classes that implement Extension. Any implementing class with a no-argument constructor is instantiated, injected, and stored in the extension registry.
The minimal requirements for a loadable extension are:
- A class that implements
Extensionfrom:extensions:extension-api - Implementations of
id(),version(),onLoad(),onUnload(), andonReload() - A no-argument constructor (Kotlin data classes and plain classes satisfy this automatically)
The Example Extension
Theextension-example module in the Universe repository is the canonical minimal implementation:
Receiving Guice Dependencies
ExtensionService calls app.injector.injectMembers(extension) before onLoad(). Any @Inject-annotated field in your extension class is populated with the corresponding Guice binding from the application’s injector, giving you access to all registries and services:
Extension Point Interfaces
The:extensions:extension-api module exposes five interfaces you can implement to extend Universe’s core behaviour. Register each via its corresponding injected registry in onLoad().
TemplateStorageProvider — custom template storage backends
TemplateStorageProvider — custom template storage backends
Implement Registration:Templates in
TemplateStorageProvider to add a new storage backend (FTP, GCS, Azure Blob, etc.). Register via TemplateStorageRegistry.templateInstallationConfig reference this backend with "storage": "<storageKey>".TemplateVariableProvider — inject custom template variables
TemplateVariableProvider — inject custom template variables
Implement Registration:All variables returned are applied to every file listed in
TemplateVariableProvider to inject additional %PLACEHOLDER% variables during instance deployment. Register via TemplateVariableRegistry.fileModifications and to every value in environmentVariables.DatabaseProvider — custom persistence backends
DatabaseProvider — custom persistence backends
Implement Registration:Activate the provider by setting
DatabaseProvider to add a new database backend. Register via DatabaseRegistry."provider": "my-db" in ./database.json.MetricsProvider — custom metrics exporters
MetricsProvider — custom metrics exporters
Implement Registration:
MetricsProvider to push or expose metrics in a format not covered by the built-in Prometheus and InfluxDB extensions. Register via MetricsRegistry.RuntimeProvider — custom execution environments
RuntimeProvider — custom execution environments
RuntimeProvider lives in the main :api module (not :extensions:extension-api) and is implemented by the Docker and Kubernetes extensions. It defines how instances are started, stopped, and monitored. Register via RuntimeRegistry."runtime": "my-runtime" is set in their configuration.Gradle Project Setup
Create a new submodule underextensions/ and add it to settings.gradle.kts. Your build.gradle.kts should only compile against :api and :extensions:extension-api, never against :app. Use runtimeDownload for any external libraries you need at runtime so they are not bundled into the core JAR.
settings.gradle.kts by adding it to the subProjects array of the registerSubProjects("extensions", "extension", ...) call.
masterOnly() and reloadable() Usage
Override these two flags in your Extension implementation to control when and how your extension is active:
Build Rules Summary
Depend only on :api and :extension-api
Never add a
compileOnly or implementation dependency on :app. Extensions must not access internal application classes.Use runtimeDownload for external deps
Universe’s loader downloads
runtimeDownload dependencies at startup. Do not shadow external libraries into your extension JAR — this causes classpath conflicts.Register via injected registries
Call
registry.register(...) inside onLoad(), never in a static initialiser or companion object. The injector is not available before onLoad() runs.Unregister in onUnload()
Always unregister your providers and commands in
onUnload() so that resources are cleaned up when the node shuts down or the extension is removed.