Skip to content

pkajaba/junction

Junction

A native, open-source browser router for macOS. Click a link anywhere — Junction decides which browser (and which profile) opens it, from rules you define. No rule? A fast keyboard-driven picker lets you choose.

Status: feature-complete, in dogfooding. Not yet signed/notarized, so there's no one-click installer — build from source for now (it's quick). A signed Homebrew cask is the last milestone before v0.1.

The Junction picker


What it does

You probably use more than one browser — or more than one Chrome profile (work vs. personal). macOS only lets you pick one default browser, so every link is a small papercut: wrong browser, wrong profile, drag the URL across, sign in again.

Junction sits in that gap. It registers as your default browser, then for every link:

  1. Rules match first. *.zoom.us → Zoom app. github.com → work Chrome. news.ycombinator.com → Safari. Silent, instant.
  2. No match? The picker appears — a keyboard-driven panel. Pick a browser, or pin it to create a rule on the spot.
  3. Native apps win where it matters. A Zoom link can open the Zoom app directly, skipping the browser entirely.

Why another one of these?

The browser-router niche exists, but every option has a catch in 2026:

App The catch
Browserosaurus Discontinued. Picker only, no rules.
Velja Closed-source, paid.
Finicky Open-source, but JS-config only — no GUI, no picker.
Choosy Closed-source, paid.
Browserino Open-source and native — closest sibling. No URL rewriting, no app-origin rules, no native-app handoff yet (see issues #13, #42, #49).

Junction is the combination that didn't exist: open source (MIT), native (Swift/SwiftUI — no Electron, ~10 MB), with both a rules engine and a picker, plus a real settings UI.

A handful of recurring Browserino feature requests are first-class in Junction by design — app-origin rules (#13), native-app handoff for Zoom/Slack/etc. (#42), URL rewriting (#49), hiding non-browser apps that register as http handlers (#36), and promoting unrecognized browsers the allowlist missed (#24). If you found your way here from one of those threads, that's why.

Features

  • Rule engine — match on host (+ subdomains), host regex, URL substring, or URL prefix (path-aware) — and optionally narrow by the app the link came from (e.g. links from Slack → work Chrome). Route to any browser + profile + launch args.
  • Keyboard-driven picker — for unmatched URLs. Number keys, arrows, Return. Pin a row to turn the choice into a saved rule.
  • Per-browser profiles — Chrome/Brave/Edge/Vivaldi/Opera (--profile-directory) and Firefox (-P). Real profile names are detected and shown in the rule editor — no guessing Default vs Profile 1.
  • Native-app handoff — Zoom, Teams, Slack, Notion, Linear, Spotify, Discord. A matching URL opens the native app instead of a browser.
  • URL rewriting — strips utm_*, fbclid, gclid, and other tracking params before routing. Pure, local, no network.
  • Visual rule editor — a two-pane Settings tab: grouped rule list + inline editor with a host-chip matcher and a live "test this URL" field.
  • Live config reloadrules.json is plain text; edit it in any editor and Junction picks it up instantly (it watches the file).
  • Native picker chrome — a system blur material (NSVisualEffectView) that the OS renders with Liquid Glass on macOS 26 and a standard vibrant material below.

Screenshots

The picker

No rule matched, so Junction asks — keyboard-first (number keys, arrows, ), or pin a row (⌥↩) to turn the choice into a saved rule.

The picker

Rules

A two-pane editor: grouped rules on the left, an inline host-chip matcher + live URL tester on the right.

Settings — Rules

Native-app handoff

Send matching links straight to a native app instead of a browser — each toggle enabled only when the app is installed.

Settings — Handoff

Advanced

Launch at login, theme, Activity-log retention (secrets are always redacted before anything is logged), and the tracking-parameter strip list.

Settings — Advanced

More — Browsers & Activity

Browsers

Toggle which browsers show in the picker; add ones Junction didn't auto-detect.

Settings — Browsers

Activity

Every link Junction has handled, with the outcome (rule match / picker / handoff) and one-click "make it a rule."

Settings — Activity

Install

No signed build yet — build from source. It takes about a minute.

Requirements

  • macOS 14 Sonoma or newer
  • Xcode 16 or newer
  • XcodeGenbrew install xcodegen

Build & run

git clone https://github.com/pkajaba/junction.git
cd junction
xcodegen generate          # writes Junction.xcodeproj from project.yml
open Junction.xcodeproj
# ⌘R in Xcode

Junction.xcodeproj is generated from project.yml and is not committed — that sidesteps the merge-conflict pain of binary Xcode projects. Re-run xcodegen generate after pulling changes to project.yml.

Set it as your default browser

System Settings → Desktop & Dock → Default web browser → Junction. (An unsigned build may not appear in that dropdown; design/set_default_browser.swift is a small helper that sets it via the same API the dropdown uses.)

How it works

Rules

Rules live in ~/Library/Application Support/Junction/rules.json — plain JSON, hand-editable, git-friendly. Junction watches the file and reloads on save. Or use Settings → Rules (⌘,): a grouped list on the left, an inline editor on the right with a visual host-chip matcher and a live URL tester.

{
  "schemaVersion": 1,
  "rules": [
    {
      "name": "GitHub → work Chrome",
      "enabled": true,
      "match": { "type": "host", "value": "github.com" },
      "target": {
        "browserBundleID": "com.google.Chrome",
        "profile": "Default"
      }
    }
  ]
}

Matchers: host (exact or subdomain — github.com matches api.github.com but not notgithub.com), hostRegex (case-insensitive regex on the host), urlContains (case-insensitive substring on the whole URL), urlPrefix (anchored, case-insensitive prefix on the whole URL — e.g. https://github.com/acme/ to route just one org's pages), and any (matches every URL, for rules that key off the source app alone). A rule can additionally require the link to come from specific apps (sourceApps, by bundle ID). Rules evaluate top to bottom; first enabled match wins.

The picker

When no rule matches, the picker appears — centered, keyboard-first:

  • 19 — open in that browser
  • — move selection · — open · esc — cancel
  • ⌥↩, or click a row's pin — open and save a rule for that host

URL rewriting

Tracking parameters (utm_*, fbclid, gclid, msclkid, …) are stripped before matching and opening, so the browser gets a clean URL. Configurable in Settings → Advanced. Pure value transform — no network, no logging.

Native-app handoff

If you have the native app, a matching link can open it directly:

Link Opens in
*.zoom.us/j/… Zoom
teams.microsoft.com/l/… Microsoft Teams
app.slack.com/client/… Slack
notion.so/… Notion
linear.app/… Linear
open.spotify.com/… Spotify
discord.com/channels/… Discord

Each is opt-in per app in Settings → Advanced (only enabled when the app is installed). Handoff takes priority over rules.

Profiles

Browser family How
Chromium (Chrome, Brave, Edge, Vivaldi, Opera, all channels) --profile-directory=<dir> — profile names read from the browser's Local State
Firefox (incl. Developer Edition, Nightly) -P <name> — read from profiles.ini
Safari / Arc Not supported — Apple/Arc expose no external profile API (#13)

Status & roadmap

Core (URL routing, rules, picker, profiles, rewriter) ✅ Done
Settings UI, native-app handoff, redesigned picker ✅ Done
Unit tests, SwiftLint, CodeQL, CI ✅ Done
Sign + notarize + Homebrew cask → v0.1 ⏳ Last step (needs an Apple Developer ID)

See SPEC.md for the architecture and CLAUDE.md for a contributor's orientation to the codebase.

Contributing

Issues and PRs welcome. The codebase is small, dependency-free, and documented in CLAUDE.md. See CONTRIBUTING.md and the issue templates. Tests run on every PR via GitHub Actions; swiftlint runs on every build.

License

MIT — same as Browserosaurus and Finicky. Fork freely.

Acknowledgements

Junction stands on the shoulders of Browserosaurus (Will Stone) and Finicky (Johannes Stenmark) — both proved this kind of tool can be loved. Browserino (Alex Strnik) is the nearest contemporary MIT-licensed native option; reading through its issue tracker shaped several of Junction's defaults. Junction's goal is to keep that experience alive, natively and openly.

About

A native, open-source browser router for macOS. Pick the right browser for every link.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages