Registering Native Hosts on Windows, macOS and Linux

Where Chrome, Edge, Brave and Firefox look for native messaging host manifests on Windows, macOS and Linux — registry keys, per-user and system directories, and a script that registers all of them.

Published October 2, 2026 Updated October 2, 2026 8 min read
Table of Contents

chrome.runtime.connectNative("com.acme.vault") fails with “Specified native messaging host not found”, even though the host manifest is sitting right there on disk. It is in the wrong place — or the right place for a different browser. Every Chromium-based browser reads its own directory, Firefox reads another, Windows uses the registry instead of directories, and per-user and system-wide locations differ again. This guide lists them and builds a registration script that covers the browsers your users actually run. It belongs to native messaging and host integration.

How the browser finds a host

When an extension calls connectNative(name), the browser looks up a file named <name>.json — or, on Windows, a registry key named <name> — in a fixed list of locations, checking per-user before system-wide. The first match wins. The file must be valid JSON, its name field must equal the requested name, its path must point at an existing executable, and its allowed_origins (or Firefox’s allowed_extensions) must include the caller. Each of those checks has its own failure, but “not found” specifically means the lookup step: no file or key with that name existed in any of the places this particular browser searches.

How the lookup proceedsThe browser checks per-user then system locations for the host manifest, on Windows via registry keys pointing at a file, elsewhere via fixed directories, then validates name, path and allowed callers.Which OS?WindowsRegistry keyHKCU then HKLMDefault value→ path of the JSONmacOSDirectory~/Library then /Library<name>.jsonabsolute path insideLinuxDirectory~/.config then /etc<name>.jsonabsolute path inside
"Not found" means the first stage failed; the other errors come later.

Step-by-step: register for every browser you support

1. Write the manifest once per browser family

Chromium browsers and Firefox disagree on one key, so generate two variants from a template.

1{
2  "name": "com.acme.vault",
3  "description": "Acme Vault native bridge",
4  "path": "/opt/acme-vault/vault-host",
5  "type": "stdio",
6  "allowed_origins": [
7    "chrome-extension://abcdefghijklmnopabcdefghijklmnop/"
8  ]
9}

Execution context: a file on disk read by Chromium-based browsers. name may contain only lower-case letters, digits, dots and underscores, and may not start or end with a dot. Each origin in allowed_origins needs the trailing slash and no wildcards. For Firefox, replace allowed_origins with "allowed_extensions": ["vault@acme.example"], using the id from browser_specific_settings.gecko.id.

2. Know the per-user locations

 1macOS
 2  Chrome    ~/Library/Application Support/Google/Chrome/NativeMessagingHosts/
 3  Chromium  ~/Library/Application Support/Chromium/NativeMessagingHosts/
 4  Edge      ~/Library/Application Support/Microsoft Edge/NativeMessagingHosts/
 5  Brave     ~/Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts/
 6  Firefox   ~/Library/Application Support/Mozilla/NativeMessagingHosts/
 7
 8Linux
 9  Chrome    ~/.config/google-chrome/NativeMessagingHosts/
10  Chromium  ~/.config/chromium/NativeMessagingHosts/
11  Edge      ~/.config/microsoft-edge/NativeMessagingHosts/
12  Brave     ~/.config/BraveSoftware/Brave-Browser/NativeMessagingHosts/
13  Firefox   ~/.mozilla/native-messaging-hosts/

Execution context: the user’s home directory, writable without elevation. Beta, Dev and Canary channels of Chrome use their own directories (Google/Chrome Beta, google-chrome-beta), which surprises developers who test on Canary. Flatpak and Snap browsers on Linux run sandboxed and cannot see these directories at all without extra configuration.

3. Know the system-wide locations

 1macOS
 2  Chrome    /Library/Google/Chrome/NativeMessagingHosts/
 3  Edge      /Library/Microsoft/Edge/NativeMessagingHosts/
 4  Firefox   /Library/Application Support/Mozilla/NativeMessagingHosts/
 5
 6Linux
 7  Chrome    /etc/opt/chrome/native-messaging-hosts/
 8  Chromium  /etc/chromium/native-messaging-hosts/
 9  Edge      /etc/opt/edge/native-messaging-hosts/
10  Firefox   /usr/lib/mozilla/native-messaging-hosts/   (or /usr/lib64/…)

Execution context: system directories requiring root or admin, used by package installers and enterprise deployment tools. A per-user manifest with the same name takes precedence, which is useful for developers overriding an installed host and confusing for support when an old per-user file shadows a newly installed system one.

Registration mechanism by OS and browserHow Chrome, Edge and Firefox discover native host manifests on Windows, macOS and Linux, and whether per-user registration needs elevation.BrowserWindowsmacOSLinuxChromeHKCU\…\Google\ChromeApplication Support dir~/.config/google-chromeEdgeHKCU\…\Microsoft\EdgeApplication Support dir~/.config/microsoft-edgeFirefoxHKCU\…\MozillaApplication Support/Mozil…~/.mozillaSandboxed packagesMSIX: limited—Flatpak/Snap: blocked
Windows is the odd one out: a registry value points at a JSON file anywhere on disk.

4. Register on Windows through the registry

 1# Per-user, Chrome and Edge, PowerShell
 2$manifest = "$env:LOCALAPPDATA\AcmeVault\com.acme.vault.json"
 3foreach ($key in @(
 4  "HKCU:\Software\Google\Chrome\NativeMessagingHosts\com.acme.vault",
 5  "HKCU:\Software\Microsoft\Edge\NativeMessagingHosts\com.acme.vault",
 6  "HKCU:\Software\Mozilla\NativeMessagingHosts\com.acme.vault"
 7)) {
 8  New-Item -Path $key -Force | Out-Null
 9  Set-ItemProperty -Path $key -Name "(default)" -Value $manifest
10}

Execution context: PowerShell as the user. The key’s default value is the full path to the JSON file — not the JSON itself, and not a named value. Firefox’s key should point at the Firefox variant of the manifest. In the manifest, path may be relative to the JSON file’s directory on Windows only. 32-bit browsers on 64-bit Windows read the WOW6432Node view of HKLM; per-user HKCU keys avoid that complication entirely.

5. Script the whole thing on macOS and Linux

 1#!/usr/bin/env bash
 2set -euo pipefail
 3NAME=com.acme.vault
 4HOST_PATH="$1"                                  # absolute path to the executable
 5CHROME_ID="abcdefghijklmnopabcdefghijklmnop"
 6GECKO_ID="vault@acme.example"
 7
 8chromium_json=$(printf '{"name":"%s","description":"Acme Vault","path":"%s","type":"stdio","allowed_origins":["chrome-extension://%s/"]}' "$NAME" "$HOST_PATH" "$CHROME_ID")
 9firefox_json=$(printf '{"name":"%s","description":"Acme Vault","path":"%s","type":"stdio","allowed_extensions":["%s"]}' "$NAME" "$HOST_PATH" "$GECKO_ID")
10
11if [[ "$(uname)" == "Darwin" ]]; then
12  base="$HOME/Library/Application Support"
13  chromium_dirs=("$base/Google/Chrome" "$base/Chromium" "$base/Microsoft Edge" "$base/BraveSoftware/Brave-Browser")
14  firefox_dir="$base/Mozilla/NativeMessagingHosts"
15else
16  chromium_dirs=("$HOME/.config/google-chrome" "$HOME/.config/chromium" "$HOME/.config/microsoft-edge" "$HOME/.config/BraveSoftware/Brave-Browser")
17  firefox_dir="$HOME/.mozilla/native-messaging-hosts"
18fi
19
20for d in "${chromium_dirs[@]}"; do
21  [[ -d "$d" ]] || continue                     # only browsers that are installed
22  mkdir -p "$d/NativeMessagingHosts"
23  echo "$chromium_json" > "$d/NativeMessagingHosts/$NAME.json"
24done
25mkdir -p "$firefox_dir" && echo "$firefox_json" > "$firefox_dir/$NAME.json"
26chmod +x "$HOST_PATH"

Execution context: a shell as the user, typically run by an installer’s post-install step. Registering only for browsers whose profile directory exists avoids littering the home directory; re-run the script after the user installs a new browser, or register for every known browser unconditionally if your support burden favours that. The chmod matters: a manifest pointing at a non-executable file produces “Native host has exited”, not “not found”.

What the registration script doesThe script generates Chromium and Firefox manifest variants, finds installed browsers by their profile directories, writes the manifests into each NativeMessagingHosts directory, and marks the host executable.Templateschromium + firefoxDetect browsersprofile dirs existWrite manifests<name>.json eachthen make the host launchablechmod +xexecutable bitSign / notarisemacOS GatekeeperSmoke testsendNativeMessage hello
Two templates, many destinations — the browser list is the part that grows.

6. Keep a separate registration for development

An unpacked extension has a different id from the store build unless you pin it with the key field, and a host built from source lives in your checkout rather than in /opt. Rather than editing the production manifest, register a second host name for development and choose between them at build time.

1// build-time constant injected by the bundler
2const HOST_NAME = import.meta.env.DEV ? "com.acme.vault.dev" : "com.acme.vault";
3export const connectHost = () => chrome.runtime.connectNative(HOST_NAME);

Execution context: the service worker, compiled per environment. The development manifest (com.acme.vault.dev.json) points at a wrapper script in your checkout and lists the unpacked extension’s origin. Because the names differ, a developer can have the production host installed from the real installer and the development host registered from source on the same machine without either shadowing the other — which also means you can test the production installer without uninstalling your working setup.

Linux sandboxed browsers deserve a special note here. A Chrome or Firefox installed as a Flatpak or Snap cannot see host manifests in the usual home-directory locations and cannot launch executables outside its sandbox without additional portal configuration. If your users report “not found” on Linux and everything looks correct, ask how the browser was installed; the honest answer for a sandboxed package is usually to recommend the distribution’s native package or the vendor’s own .deb/.rpm.

Cross-browser variation

  • Chrome / Edge / Brave / Chromium: identical manifest format with allowed_origins; each reads only its own directory or registry key. Opera and Vivaldi read Chrome’s locations on some platforms and their own on others — test them explicitly if you support them.
  • Firefox: allowed_extensions with add-on ids; separate registry key and directories. Firefox ESR uses the same locations as release.
  • Safari: no registration. The host is the macOS app that contains the Safari extension; installing the app is registering the host.

Verification

  1. Confirm the file exists where the browser looks: ls "$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts/".
  2. Validate the JSON: python3 -m json.tool < com.acme.vault.json.
  3. Confirm the executable runs on its own: "$HOST_PATH" < /dev/null; echo $? should print an exit code of 0, not “permission denied”.
  4. From the extension’s service worker console:
1await chrome.runtime.sendNativeMessage("com.acme.vault", { type: "hello" });
2// → { protocol: 3, version: "2.0.0", features: [...] }

Execution context: the service worker console. A resolved reply proves lookup, authorisation, launch and framing all work. On Windows, reg query "HKCU\Software\Google\Chrome\NativeMessagingHosts\com.acme.vault" confirms the key and its default value.

FAQ

Do I need a separate manifest for each Chrome channel?

The file can be identical, but it must exist in each channel’s directory — Chrome Beta, Dev and Canary each read their own. Developers testing on Canary often register only for stable and wonder why lookup fails.

Can the manifest point at a script instead of a binary?

On macOS and Linux, yes, if it has a shebang and the executable bit; the browser launches it with a minimal environment, so absolute interpreter paths are safest. On Windows, point at a .bat or .exe; a .bat wrapper is common in development and slow in production.

How do enterprises deploy hosts?

With system-wide locations written by their software distribution tool, often alongside the NativeMessagingAllowlist policy, which restricts which host names extensions may connect to at all.

Other Core APIs & Cross-Browser Data Management Resources