A Windows desktop app that lets you route protocol links (like tel:) to the app or URL you actually want, using simple rules and ordered fallbacks.
Typical use case: clicking a tel: link in a browser should open WhatsApp (Desktop first, Web fallback), not the default dialer.

Main window – Settings tab (click to expand)
I built this because I lived the pain: I had TEL links mapped to WhatsApp, and after a recent Windows update they all stopped working.
win-link-router exists to make routing explicit and resilient: it lets you map ANY link type (scheme/protocol) to ANY application or web service, using templates—whether that target is a native app protocol like whatsapp://... or a web URL like https://... (with ordered fallbacks).
- Routes protocol URIs (for example
tel:+15551234567) to target apps/URLs based on your configuration. - Supports ordered fallbacks: try Template A, then Template B if opening fails.
- Windows Default Apps friendly:
- The app can register itself as a candidate handler (so Windows can show it as an option).
- You still choose the default handler in Windows Settings (the app does not and cannot set defaults for you reliably).
- Built-in presets to get started fast (currently includes
TEL/SIP/CALLTO/SMS/SMSTO → WhatsApp). - Debug tools built in: a Test tab to see extractor matches and rendered targets; a Log tab for routing history.
- Go to GitHub Releases: https://github.com/karmaniverous/win-link-router/releases
- Download and install the latest Windows build.
- Launch win-link-router from the Start Menu.
- Open win-link-router.
- On first run, choose the TEL WhatsApp preset (or add
TELlater).

First-run preset selection (click to expand)
- In Settings → Schemes, ensure
TELis:- Enabled (power icon)
- Registered (registration icon)
- Click Set default… for
TEL(or use Default Apps… in the header) and set win-link-router as the default handler fortel:links in Windows.

Windows Default Apps – choosing defaults by link type (click to expand)
- Paste a test link like
tel:+1 (555) 123-4567into the Test tab to confirm the rendered targets look correct. - Click a
tel:link anywhere (browser, email, chat) and the router will forward it.
win-link-router is configured per scheme (also called a protocol).
For each scheme you configure:
- Incoming URI normalization:
- The app preserves the original raw OS argument as
uri. - It also computes a decoded form,
decodedUri, by percent-decoding the URI payload (everything after the first:).- Example:
tel:+62%20816...becomestel:+62 816...
- Example:
- The app preserves the original raw OS argument as
- Extractor (regex) is applied to
decodedUri(noturi). - If it matches, named capture groups become variables for templates.
- Templates are evaluated in order:
- Render a target string (Handlebars)
- Try to open it via Windows
- If opening fails, try the next enabled template (fallback)
- If routing fails, the app opens the UI and shows diagnostics (and pre-fills the Test tab with the failing URI).
Why this matters: many apps generate protocol links by URL-encoding user-visible field content (spaces, +, etc.). Decoding before extraction avoids corruption (for example, %20 turning into digits via helpers like digits()).
- Scheme: the part before the first
:in a URI. Examples:TEL,MAILTO,CALLTO. - Enabled vs Registered
- Enabled: router will process incoming links for this scheme.
- Registered: the app should appear as a candidate handler in Windows for this scheme.
- Rule: Registered implies Enabled.
- Extractor: a single regex per scheme (pattern + optional flags). Must use named capture groups.
- Template: a Handlebars string that produces a target URL/protocol (for example
whatsapp://...orhttps://...).
This is where you configure schemes and templates, and manage lifecycle settings.

Schemes list – enabled/registered/default indicators (click to expand)
- enabled toggle (power)
- registration toggle
- default status indicator (default / not default / unknown) Also show the info tooltip content once (expected/actual ProgId), but redact any sensitive paths if present.
- Click Add scheme
- Enter a scheme name (for example
TELormailto) - If presets exist for that scheme, you can initialize from a preset
- Use the Extractor pattern field (regex)
- Use Flags for regex flags (global
gis not allowed)
Example extractor for TEL:
^tel:(?<number>.*)$Flags:
i
Templates are tried top-to-bottom (enabled templates only). You can:
- Add/remove templates
- Enable/disable templates
- Reorder templates (fallback order)
- Reset the scheme to a preset (if it was created from one)

Scheme editor – extractor and template list (click to expand)
- Extractor pattern + Flags
- At least two templates (WhatsApp Desktop + WhatsApp Web)
- Reorder controls and enable/disable (power) icons Use a non-personal sample URI in any visible fields.
Templates are Handlebars with strict variable resolution (missing values cause a render error). The app provides a few generic helpers:
{{trim value}}- Removes leading and trailing whitespace.{{lower value}}- Converts the value to lowercase.{{upper value}}- Converts the value to uppercase.{{urlEncode value}}- URL-encodes the value (percent-encoding) for safe use in query strings.
Example template:
Advanced note: the template context includes:
- top-level variables for each named capture group
uri(the full original incoming URI, as received)decodedUri(the decoded URI used for extractor matching)match(regex match info from running the extractor againstdecodedUri, includingmatch.groups.*)
The Log tab is for debugging. You can:
- Refresh entries
- Clear the log
- Toggle Redacted vs Full logging for new entries
Redacted mode is recommended: it avoids storing raw URIs and full targets (which may contain sensitive payloads).
Full mode may include both uri (raw) and decodedUri (decoded) in each entry.

Log tab – redacted mode and entries (click to expand)
Paste a URI and the app will:
- Infer the scheme from the URI
- Compute
decodedUri(decoded payload after the first:) - Show the decoded URI under the input (this is what the extractor matches)
- Run the extractor against
decodedUri - Show match groups
- Show each template’s rendered output (or render error)
This is the fastest way to debug regex and template issues before relying on Windows routing.

Test tab – match groups and rendered targets (click to expand)
- Candidate registration makes win-link-router appear as an option in Windows Default Apps for a scheme.
- Default handler is what Windows actually uses when a link is clicked.
- Windows protects default handlers; this app will not attempt to force defaults.
If some enabled + registered schemes are not set as default in Windows, the app will show a non-blocking warning.
- Run in Background: keeps the app in the tray so routing can happen without opening the UI.
- Start on Windows Login: start in the tray at login (implies Run in Background).

System tray menu (click to expand)
- Export saves your schemes and templates to a JSON file.
- Import replaces your schemes/templates from a JSON file.
- Per-user settings (like run-at-login and shared config path) are preserved locally.
If you enable Use shared config, schemes/templates are read from a shared JSON file path. This is useful for sharing rules across accounts or machines.
If the shared file is missing, unreadable, or not writable:
- scheme/template editing becomes read-only
- settings remain editable so you can fix or disable shared mode
win-link-router supports automatic updates on Windows (packaged builds) using update.electronjs.org.
Open Help → About to:
- see the current version
- check for updates
- update immediately
- toggle automatic updates

About – update status and controls (click to expand)
- Make sure win-link-router is set as the default handler for that scheme in Windows Default Apps.
- Confirm the scheme is Enabled in the app.
- Confirm at least one template is Enabled.
- Go to the Test tab.
- Paste the exact URI you clicked.
- Remember: the extractor runs against the decoded URI payload (percent-decoded after the first
:). - Fix the extractor pattern/flags until match groups appear.
- Your template referenced a missing value (for example
{{digits number}}when the extractor does not define(?<number>...)). - Use the Test tab to see which variables exist in match groups.
- The rendered target protocol may not be installed/registered on your machine (for example
whatsapp://). - Add an
https://...fallback template as the next option.
- Configuration and logs are stored per-user in the app’s data folder.
- Routing logs default to redacted mode (recommended).
- If you enable full logging, logs may include sensitive payloads and targets.
BSD-3-Clause. See LICENSE in this repository.