- The extension declares the
nativeMessagingpermission. - A host manifest, a small JSON file registered with the OS, names the application and the extensions that may call it.
- The application speaks the length-prefixed stdio protocol.
Declare the permission
AddnativeMessaging to the permissions in your manifest.json:
Write the host manifest
The host manifest tells the browser where the application lives and who may start it:nameis the identifier that your extension passes toconnectNative(). Use lowercase letters, digits, underscores, and dots.pathmust be absolute on macOS and Linux. On Windows it may be relative to the manifest, and it must point to an executable (use a.batwrapper for a Node.js script).typeis alwaysstdio.allowed_originslists the extension IDs that may call this host. Read your ID fromchrome://extensionswith Developer mode on.
Where to install it
Chromium browsers find the manifest in fixed per-OS locations, named after the host (com.example.ping.json):
Chromium (the open-source build) uses
Chromium instead of Google/Chrome on macOS, and ~/.config/chromium/NativeMessagingHosts/ plus /etc/chromium/native-messaging-hosts/ on Linux. The per-user directory on macOS and Linux lives inside the browser’s user data directory, which matters during extension dev (see below).
A minimal Node.js host
Each message is a 32-bit unsigned integer length, in native byte order (little-endian on every supported platform), followed by that many bytes of UTF-8 JSON. The same framing applies in both directions. This host echoes every message back:ping-host.js
path at an executable wrapper script and make it executable:
ping-host
Connect from the background script
Use a port for an ongoing conversation. The browser starts the host process onconnectNative() and stops it when the port disconnects:
background.js
sendNativeMessage() starts a fresh host process per call:
Firefox differences
Firefox uses the same permission, the same APIs (browser.runtime.connectNative), and the same stdio protocol. The registration side differs:
- Your extension needs an explicit ID through
browser_specific_settings.gecko.id. Use thefirefox:manifest prefix so the key only reaches Firefox builds. - The host manifest replaces
allowed_originswithallowed_extensions, listing that ID.
Native messaging during extension dev
The host is registered with the OS, not bundled with your code.extension dev neither copies nor registers anything, so a host that works in development behaves the same after a store install.
One profile detail matters on Chromium. By default, extension dev launches the browser with a fresh managed profile through --user-data-dir. Chromium resolves per-user host manifests inside the active user data directory, so a manifest that you installed for your everyday Chrome is invisible to that managed profile. Choose one of these setups:
- Install the host manifest system wide (or, on Windows, in the registry). Those locations do not depend on the profile.
- Run against your real browser profile instead:
Best practices
- Validate every message: The host runs with the user’s full OS privileges, so treat extension input as untrusted and vice versa.
- Keep stdout clean: One stray
console.login the host corrupts the frame stream. - Handle disconnects: Inspect
chrome.runtime.lastErrorinonDisconnectto distinguish a missing host from a crashed one. - Prefer ports for repeated calls:
sendNativeMessage()pays a process start per message.
Next steps
- Review extension-internal channels in Messaging.
- Understand permission prompts in Permissions and host permissions.
- Learn how dev profiles work in Browser profile.

