Nucleus
Lifecycle

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:

build.gradle.kts
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:

build.gradle.kts
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:

OSMechanism
macOSApple 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.
WindowsA 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.
LinuxA .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.

MemberSignatureNotes
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.
uriURI? (read-only)The most recently received URI, including the cold-start one.
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. Use onDeepLink { … } or setHandler.
  • Test in development with xdg-open myapp://test (Linux), open myapp://test (macOS), or start 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 .csv with your app), use the plugin's fileAssociation(...) DSL instead.

What's next