Deep links and URL schemes
Register myapp:// URL handlers across macOS, Windows, and Linux from one Gradle block.
A URL is the universal way for the OS — and other apps — to talk to yours. Email a link, click a Slack message, scan a QR code: it all ends up as myapp://something?id=42 on some user's machine. Nucleus registers the protocol at packaging time and hands you the URI in your main() at runtime.
TL;DR
- Declare protocols once in
nucleus { application { nativeDistributions { protocol(...) } } }. - The plugin writes the platform plumbing: LSBundleURLTypes (macOS), HKCU registry entries (Windows),
.desktopMimeType (Linux). DeepLinkHandler.setHandler(args, onDeepLink)parses the URI whether it arrives via Apple Events or CLI args.- Combine with
SingleInstanceManagerto forward URIs from a relaunched secondary to the running primary.
Install
Bundled with core-runtime (transitively pulled in by nucleus-application).
Quickstart
1. Declare the URL scheme
nucleus.application {
nativeDistributions {
protocol("MyApp", "myapp")
// protocol(name, vararg schemes) — name shows up in the OS UI,
// schemes are the URL prefixes (without ://) you want to capture.
}
}2. Handle inbound URIs
import dev.nucleusframework.core.runtime.DeepLinkHandler
fun main(args: Array<String>) = nucleusApplication(args) {
onDeepLink { uri ->
// uri is a java.net.URI — route on uri.host / uri.path / query params
navigateTo(uri)
}
DecoratedWindow(onCloseRequest = ::exitApplication) { /* ... */ }
}Without nucleusApplication { }:
fun main(args: Array<String>) {
DeepLinkHandler.setHandler(args) { uri -> navigateTo(uri) }
// On macOS, also call:
DeepLinkHandler.installAwtAppleEventHandler()
application { /* ... */ }
}How it works
The three OSes deliver URLs in three different shapes:
| OS | Mechanism |
|---|---|
| macOS | Apple Events. Your already-running app receives GetURL events via NSAppleEventManager (or AWT's Desktop.setOpenURIHandler on JBR/OpenJDK). The plugin registers your scheme in Info.plist under CFBundleURLTypes. |
| Windows | Registry entry under HKCU\Software\Classes\<scheme>. Clicking a myapp://... URL launches your .exe with the URL as the first CLI argument — a new process. |
| Linux | A .desktop file with MimeType=x-scheme-handler/myapp; plus xdg-mime association. Activation also passes the URL as a CLI argument. |
DeepLinkHandler.setHandler(args, onDeepLink) papers over the difference: on macOS it installs the AWT Apple-event handler so the callback fires while the app is already running, and on Windows / Linux it scans args for URI-shaped entries. Combine with SingleInstanceManager to handle the very common Windows / Linux case where a second .exe is launched just to deliver the URL: the secondary writes the URI to the restore-request file, the primary reads it back.
The plugin handles every static side of this — Info.plist, registry entries, .desktop files. You only describe the protocol once.
Reference
Gradle DSL
nativeDistributions {
protocol(name: String, vararg schemes: String)
}DeepLinkHandler
| Member | Signature | Notes |
|---|---|---|
setHandler(args, onDeepLink) | (Array<String>, (URI) -> Unit) -> Unit | Wires the handler. Call before application { }. |
installAwtAppleEventHandler() | () -> Unit | macOS-only; idempotent. nucleusApplication { } calls it for you. |
uri | var URI? | The most recent URI, e.g. the cold-start one. |
writeUriTo(path) | (Path) -> Unit | Persist the current URI to a file — pair with SingleInstanceManager. |
deliver(uri) | (URI) -> Unit | Manually inject a URI into the handler chain (useful in tests). |
NucleusApplicationScope.onDeepLink
nucleusApplication { onDeepLink { uri -> /* ... */ } }Shorthand for DeepLinkHandler.setHandler that defers registration until the application is running.
Notes
- Test in dev with
xdg-open myapp://test(Linux),open myapp://test(macOS),start myapp://test(Windows). Jump-list-style entries only work in packaged builds — see Launcher: Windows. - The cold-start URI (the one that launched the app) is also stashed in
DeepLinkHandler.uri— read it after your handler is registered. - For file associations (open
.csvwith your app), use the plugin'sfileAssociation(...)DSL instead.