Deep links and URL schemes
Register a custom URL scheme at packaging time and receive the incoming deep-link URI in your Nucleus app at runtime.
Deep links let you register a custom myapp:// URL scheme and receive the incoming URI in Kotlin. The Nucleus Gradle plugin registers the scheme with the OS at packaging time, and DeepLinkHandler in core-runtime delivers the URI at runtime, whether it arrives through macOS Apple Events or command-line arguments.
Add the dependency
DeepLinkHandler ships in core-runtime, which nucleus-application pulls in transitively. If you already depend on nucleus-application, you have it. Otherwise add the module directly:
dependencies {
implementation("dev.nucleusframework:nucleus.core-runtime:2.0.7")
}Declare the URL scheme
Register the scheme in the nativeDistributions block of the Nucleus plugin. The plugin writes the platform registration into each installer it builds:
nucleus.application {
nativeDistributions {
// protocol(name, vararg schemes)
// name is shown in the OS UI; schemes are the URL prefixes (without "://").
protocol("MyApp", "myapp")
}
}Handle inbound URIs
Inside nucleusApplication { }, register a callback with onDeepLink. It selects the delivery path for the active backend (AWT or Tao) and fires for both the cold-start URI and later activations:
import java.net.URI
fun main(args: Array<String>) = nucleusApplication(args) {
onDeepLink { uri: URI ->
// Route on uri.host / uri.path / query parameters.
navigateTo(uri)
}
DecoratedWindow(onCloseRequest = ::exitApplication) {
// ...
}
}Without the nucleusApplication entry point, wire DeepLinkHandler yourself. setHandler is safe on every backend; on macOS you also install the AWT Apple Events handler:
import dev.nucleusframework.core.runtime.DeepLinkHandler
fun main(args: Array<String>) {
DeepLinkHandler.setHandler(args) { uri -> navigateTo(uri) }
// macOS only, and only for AWT-driven launches.
DeepLinkHandler.installAwtAppleEventHandler()
application {
// ...
}
}installAwtAppleEventHandler initializes AWT. On the Tao backend (decorated-window-tao), AWT and Tao each create their own NSApp and deadlock the event loop on macOS. Use nucleusApplication { onDeepLink { … } }, which picks the right path per backend.
How it works
Each OS delivers URLs differently:
| OS | Mechanism |
|---|---|
| macOS | Apple Events. The running app receives GetURL events, exposed through Desktop.setOpenURIHandler on the AWT backend. The plugin registers the scheme in Info.plist under CFBundleURLTypes. |
| Windows | A registry entry under HKCU\Software\Classes\<scheme>. Opening a myapp://... URL launches your .exe with the URL as the first command-line argument — a new process. |
| Linux | A .desktop file with MimeType=x-scheme-handler/myapp; plus an xdg-mime association. Activation passes the URL as a command-line argument. |
setHandler(args, onDeepLink) reconciles the difference. On the AWT path, installAwtAppleEventHandler registers an Apple Events handler so the callback fires while the app is already running; on Windows and Linux, setHandler scans args for the first entry containing ://. The cold-start URI is delivered to the handler once per process, so registering from a Compose scope that re-runs on recomposition does not re-fire it.
Windows and Linux start a new process for each activation. Combine with SingleInstanceManager to forward the URI to the already-running instance: the secondary process records its URI with captureFromArgs and writes it with writeUriTo(path) before exiting, and the primary reads it back with readUriFrom(path).
The plugin generates every static registration — Info.plist entries, Windows registry keys, and .desktop files. You declare the scheme once.
API reference
Gradle DSL
nativeDistributions {
protocol(name: String, vararg schemes: String)
}DeepLinkHandler
object DeepLinkHandler in dev.nucleusframework.core.runtime.
| Member | Signature | Notes |
|---|---|---|
setHandler | (args: Array<String>, onDeepLink: (URI) -> Unit) | Registers the callback and delivers the cold-start URI once. Safe on every backend. |
captureFromArgs | (args: Array<String>) | Records a URI from args into uri without installing a callback or touching AWT. Used by the single-instance secondary bootstrap. |
installAwtAppleEventHandler | () | macOS only. Installs the AWT Apple Events handler and initializes AWT. |
writeUriTo | (path: Path) | Writes the current uri to a file. Pair with SingleInstanceManager. |
readUriFrom | (path: Path) | Reads a URI from a file and fires the callback. |
deliver | (newUri: URI) | Injects a URI into the handler chain. Used by alternative delivery paths and tests. |
uri | URI? (read-only) | The most recently received URI, including the cold-start one. |
NucleusApplicationScope.onDeepLink
nucleusApplication { onDeepLink { uri -> /* ... */ } }Registers a DeepLinkHandler callback that defers until the application is running and selects the backend-appropriate delivery path.
Notes
DeepLinkHandler.register(args, onDeepLink)is deprecated: it always installs the AWT handler and is incompatible with the Tao backend on macOS. UseonDeepLink { … }orsetHandler.- Test in development with
xdg-open myapp://test(Linux),open myapp://test(macOS), orstart myapp://test(Windows). Registration through the installer only applies to packaged builds — see Launcher: Windows. - The cold-start URI is also stored in
DeepLinkHandler.uri; read it after your handler is registered. - For file associations (opening a
.csvwith your app), use the plugin'sfileAssociation(...)DSL instead.
What's next
- Single instance — forward a deep link from a relaunched process to the running one.
- Launcher: macOS — the
Info.plistand Apple Events integration. - Launcher: Windows — registry registration and jump-list entries.