A macOS menu bar app for triggering deep links on iOS and Android devices.
Linktor sits in your menu bar and lets you open deep links on a running simulator, emulator, or connected physical device with one click. You define your links in a .deeplinks.json file in your project root, point the app at the folder, and it builds a browsable, searchable list with editable parameters.
Key features:
- One-click deep link triggering on iOS Simulator/physical device or Android Emulator/physical device
- Device picker with auto-selection (physical devices preferred)
- Auto-detects platform based on project files (
.xcodeproj= iOS,build.gradle= Android) - Parameterized URLs with editable path and query parameters
- Save presets for frequently-used parameter combinations
- Quick-add manual links without editing the config file
- Search/filter across all links
- Refresh button to reload config without restarting
- Automatic updates — checks every 24 hours, or manually via toolbar button
Download the latest Linktor.zip from GitHub Releases, unzip, and move Linktor.app to your Applications folder. Launch it — the link icon appears in your menu bar.
Note: Linktor checks for updates automatically every 24 hours while running. You can also check manually using the update button in the toolbar.
# Generate the Xcode project
xcodegen generate
# Build a release binary
xcodebuild -project DeepLinkTrigger.xcodeproj -scheme DeepLinkTrigger -configuration Release- Create a
.deeplinks.jsonfile in your project root (see format below) - Click the menu bar icon and select your project folder
- Your links appear — click any link to open it on the booted simulator/emulator
Place this file in the root of your project directory. The app reads it to build your link library.
{
"scheme": "myapp",
"links": [
{
"name": "Home",
"path": "/home"
}
]
}This produces the URL myapp://home and triggers it on click.
{
"scheme": "myapp",
"bundleId": "com.example.myapp",
"links": [
{
"name": "Home",
"path": "/home"
}
]
}The bundleId is required for triggering deep links on physical iOS devices (via xcrun devicectl). It is optional for simulators and all Android devices.
By default the app figures out the platform by scanning the selected folder for project markers (.xcodeproj = iOS, build.gradle = Android). If you hand someone just the .deeplinks.json file — with no project around it — that auto-detection has nothing to match, so no devices would show up.
To make the file work on its own, add a "platform" field:
{
"scheme": "myapp",
"bundleId": "com.example.myapp",
"platform": "ios",
"links": [
{ "name": "Home", "path": "/home" }
]
}"platform" accepts "ios" or "android". When present it takes priority over folder scanning, so device discovery works even on a machine that only has the JSON file (and the relevant SDK/Xcode installed). If it's omitted and the platform still can't be detected, the app shows a Select Platform menu so the user can pick manually.
{
"scheme": "myapp",
"links": [
{ "name": "Home", "path": "/home" },
{ "name": "Profile", "path": "/profile" },
{
"name": "Product Detail",
"path": "/product/{productId}",
"params": { "productId": { "type": "string", "default": "12345" } }
},
{
"name": "Category",
"path": "/category/{categoryId}/items",
"params": { "categoryId": { "type": "string", "default": "electronics" } }
},
{
"name": "Search",
"path": "/search",
"queryParams": {
"q": { "type": "string", "default": "shoes" },
"ref": { "type": "string", "default": "home" }
}
},
{
"name": "User Profile",
"path": "/user/{userId}/profile",
"params": { "userId": { "type": "string", "default": "42" } },
"queryParams": { "tab": { "type": "string", "default": "overview" } }
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
scheme |
string | Yes | Your app's URL scheme (e.g. "myapp" produces myapp://) |
bundleId |
string | No | App bundle identifier (e.g. "com.example.myapp"). Required for physical iOS devices. |
platform |
string | No | "ios" or "android". Declares the platform so devices list without a project alongside the file. Falls back to folder auto-detection when omitted. |
links |
array | Yes | Array of link definitions |
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Display name shown in the menu |
path |
string | Yes | URL path. Use {paramName} for path parameters |
params |
object | No | Path parameter definitions, keyed by parameter name |
queryParams |
object | No | Query parameter definitions, keyed by parameter name |
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | Parameter type (e.g. "string") |
default |
string | Yes | Default value shown in the form |
The app builds URLs following this pattern:
{scheme}://{path with params substituted}?{queryParams}
Examples based on the config above:
| Link | Resulting URL |
|---|---|
| Home | myapp://home |
| Product Detail | myapp://product/12345 |
| Search | myapp://search?q=shoes&ref=home |
| User Profile | myapp://user/42/profile?tab=overview |
Path parameters (params) replace {placeholders} in the path. Query parameters (queryParams) are appended as a query string. All values are editable in the UI before triggering.
The app auto-detects the platform when you open a project folder:
| Platform | Detected when project contains |
|---|---|
| iOS | .xcodeproj, .xcworkspace, or Package.swift |
| Android | build.gradle, build.gradle.kts, settings.gradle, settings.gradle.kts, or app/build.gradle |
The same .deeplinks.json file works for both platforms — only the trigger mechanism changes.
- macOS (menu bar app)
- For iOS Simulator: Xcode with a booted Simulator
- For iOS Physical Device: Xcode 15+ with a connected device (requires
bundleIdin config) - For Android: ADB installed. The app checks these locations in order:
~/Library/Android/sdk/platform-tools/adb/usr/local/bin/adb/opt/homebrew/bin/adb- System PATH (
which adb)
- Presets: For parameterized links, fill in the values you use often, then click "Save as Preset" to create a one-click shortcut
- Quick Add: Use the
+button to add a one-off link without editing the JSON file (e.g.myapp://debug/reset) - Refresh: After editing
.deeplinks.json, click the refresh button (⟳) to reload without restarting the app - Search: Use the search bar to filter links by name or path
- Device Picker: Use the dropdown in the header to switch between connected devices. Physical devices are auto-selected when available. Use "Refresh Devices" to rescan.
Releases are automated. Run one command and GitHub Actions handles the rest:
fastlane release bump:patch # 0.1.0 → 0.1.1
fastlane release bump:minor # 0.1.0 → 0.2.0
fastlane release bump:major # 0.1.0 → 1.0.0This bumps the version in project.yml, commits, tags, and pushes. GitHub Actions then builds, archives, signs the appcast, and creates the GitHub Release automatically.
Contributions welcome via pull requests. Fork the repo, create a feature branch, and open a PR. Only the maintainer pushes directly to main.
MIT