denoland/deno · error
Invalid deep-link scheme
Error message
Invalid deep-link scheme {scheme:?}: {reason}. What it means
Thrown by validate_url_scheme in cli/tools/desktop.rs when a deep-link scheme passed via `deno desktop --deep-link <scheme>` (or the config file `deep_links` array) violates the RFC 3986 scheme grammar enforced by the tool: it must start with an ASCII letter, may contain only letters/digits/'+'/'-'/'.', and must not be one of the reserved schemes http/https/file/ftp/ws/wss. The {reason} clause names the exact rule that failed. Reserved schemes are rejected because registering them as app handlers would hijack normal browsing.
Solutions
- Pass only the bare scheme without `://` or colon, e.g. `--deep-link acme` instead of `--deep-link "acme://app"`.
- Make the scheme start with an ASCII letter and contain only letters, digits, '+', '-', '.' — replace `my_app` with `my-app`.
- Pick a custom scheme (e.g. `acme`, `com.acme.app`) instead of a reserved one (http, https, file, ftp, ws, wss).
- Trim whitespace and remove empty strings from the `deep_links` entries in your deno config file.
Example fix
# before deno desktop --deep-link "acme://login" --deep-link my_app main.ts # after deno desktop --deep-link acme --deep-link my-app main.ts
Defensive patterns
Strategy: validation
Validate before calling
# bash: validate deep-link schemes before building
is_valid_scheme() {
local s="$1"
[[ "$s" =~ ^[A-Za-z][A-Za-z0-9+.-]*$ ]] || return 1
case "$s" in http|https|file|ftp|ws|wss) return 1;; esac
return 0
}
for scheme in "${DEEP_LINKS[@]}"; do
is_valid_scheme "$scheme" || { echo "bad scheme: $s" >&2; exit 1; }
done Type guard
// TypeScript (validating a deno.json config before invoking the CLI)
const RESERVED = new Set(["http", "https", "file", "ftp", "ws", "wss"]);
function isValidDeepLinkScheme(s: string): boolean {
return /^[A-Za-z][A-Za-z0-9+.-]*$/.test(s) && !RESERVED.has(s);
} Prevention
- Pass the bare scheme (`acme`), never a URL (`acme://x`) or a trailing colon.
- Use hyphens, not underscores, in scheme names.
- Never register http/https/file/ftp/ws/wss as deep links.
- Lint the `deep_links` array in CI with the same regex before running `deno desktop`.
When it happens
Trigger: Running `deno desktop --deep-link "acme://app" main.ts` (full URL passed instead of the bare scheme), `--deep-link 1app` (starts with a digit), `--deep-link my_app` (underscore is not in the allowed set), `--deep-link "my app"` (whitespace), `--deep-link https` (reserved), or an empty/blank entry coming from a config file `deep_links` array.
Common situations: Copying a full URL like `myapp://callback` from OAuth docs and pasting it as the scheme; including the trailing colon or `://`; using `https` or `file` because the app also handles web links; underscored names common in package names (my_app) which are legal elsewhere but not in URI schemes; whitespace sneaking in from a JSON config or shell quoting.
Related errors
- bundle identifier must be in reverse-DNS form (e.g…
- bundle identifier must match [A-Za-z0-9.-]+, but contains
- bundle identifier is empty
- unknown --compress format
- app name resolves its to , which is already part of the…
AI-assisted analysis of denoland/deno@f7822238ca (2026-08-20).
Data as JSON: /api/errors/ac86b2322a366739.
Report an issue: GitHub.
Appendix: source
Thrown at cli/tools/desktop.rs:690
}
}
};
match target_os {
"macos" => make_self_extracting_macos(bundle_path, format, desktop_flags),
"windows" => make_self_extracting_dir(bundle_path, format, true),
_ => make_self_extracting_dir(bundle_path, format, false),
}
}
/// Validate a deep-link URL scheme. Follows the RFC 3986 `scheme` grammar:
/// `ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )`. We additionally reject the
/// common reserved schemes (`http`, `https`, `file`, `ftp`, `ws`, `wss`) since
/// registering those as app handlers is almost never intended and would hijack
/// normal browsing.
fn validate_url_scheme(scheme: &str) -> Result<(), AnyError> {
let reserved = ["http", "https", "file", "ftp", "ws", "wss"];
let bail = |reason: &str| {
Err(deno_core::anyhow::anyhow!(
"Invalid deep-link scheme {scheme:?}: {reason}."
))
};
match scheme.chars().next() {
None => return bail("scheme is empty"),
Some(c) if !c.is_ascii_alphabetic() => {
return bail("scheme must start with an ASCII letter");
}
_ => {}
}
if !scheme
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '+' | '-' | '.'))
{
return bail("scheme may only contain letters, digits, '+', '-', and '.'");
}
if reserved.contains(&scheme) {
return bail("scheme is reserved and cannot be used as a deep link");View on GitHub (pinned to f7822238ca)