Native Messaging Helper for WebExtensions
Rust helpers for building native messaging hosts for Chrome/Chromium and Firefox. This crate handles:
- Writing browser-correct host manifests (Chrome
allowed_origins, Firefoxallowed_extensions) - Installing at user or system scope (with Windows registry handling for Chrome)
- Verifying/removing manifests
- Correct message framing (4-byte length prefix + UTF-8 JSON) with async helpers to read, write, and loop
Build the parts that matter—leave the manifest details and wire protocol to us.
Goal of this crate
Make the native host side of WebExtensions native messaging boringly correct and portable:
- Zero boilerplate for manifests: Generate the right shape for each browser, write to the correct OS paths, and (on Windows) handle the Chrome registry.
- Correct-by-default protocol: Enforce the 4-byte length prefix + UTF-8 JSON framing and sensible size limits.
- Ergonomic async I/O: Small, focused helpers (
get_message,send_message,event_loop) so you can ship a host quickly. - Testability: Functions are easy to unit/integration test (e.g., in-memory framing, temp HOME paths).
What this library is and isn’t
Purpose: This crate is for the native host (app) side of WebExtensions native messaging. It gives you:
- Host manifest creation/installation/removal for Chrome & Firefox
- Host-side message framing (length-prefixed JSON) and async I/O helpers
Not included: It does not implement the extension (browser) side. You’ll still write normal Chrome/Firefox extension code (connectNative / sendNativeMessage). The README includes minimal extension snippets purely to help you test your host.
Table of Contents
Features
- ✅ Cross-browser: Generates separate host manifests for Chrome and Firefox with the right keys.
- ✅ Cross-platform: Linux, macOS, and Windows (Chrome registry supported).
- ✅ Scopes: Write to user or system locations (system may require elevated privileges).
- ✅ Protocol helpers: Encode/decode frames; async
get_message,send_message, andevent_loop.
Supported platforms
| OS | Chrome/Chromium | Firefox | Notes |
|---|---|---|---|
| Linux | ✅ | ✅ | Absolute path required in manifests |
| macOS | ✅ | ✅ | Absolute path required in manifests |
| Windows | ✅ | ✅ | Chrome requires a registry value pointing to the manifest file |
Install
Add to your Cargo.toml:
[]
= "0.1.2"
= { = "1", = ["derive"] }
= { = "1", = ["full"] }
Quick Start
Install manifests (Chrome + Firefox)
use Path;
use ;
Verify and remove manifests
use ;
Send/receive messages
Hosts talk to the browser via length-prefixed JSON. Use the helpers below.
Read one message from stdin:
use get_message;
async
Send one JSON message to stdout:
use send_message;
use Serialize;
async
Run an async event loop:
use ;
use json;
use io;
async
async
API Overview
install::manifest
-
install(name, description, exe_path, chrome_allowed_origins, firefox_allowed_extensions, browsers, scope)Writes the correct manifests for requested browsers. On Windows+Chrome, also writes the registry value pointing at the Chrome manifest file. -
verify(name) -> io::Result<bool>Returnstrueif user-scope Chrome or Firefox manifest exists. -
remove(name, browsers, scope)Deletes the manifest files for the given browsers and scope. Also removes Chrome’s registry value on Windows.
Types
enum Browser { Chrome, Firefox }enum Scope { User, System }
⚠️ On macOS/Linux,
exe_pathmust be absolute. On Windows, absolute paths are strongly recommended (and used by default in examples).
host
-
encode_message<T: Serialize>(&T) -> io::Result<Vec<u8>>Build a framed message (len:u32+ JSON bytes). Enforces a 1 MB limit host→browser. -
decode_message<R: Read>(&mut R, max_size: usize) -> io::Result<String>Read and decode one frame from a reader (defaults elsewhere to ≤64 MB browser→host). -
get_message() -> io::Result<String>(async) Read a single framed message from stdin. -
send_message<T: Serialize>(&T) -> io::Result<()>(async) Frame and write a JSON message to stdout. -
event_loop(handler) -> io::Result<()>(async) Callhandler(String)for each incoming message forever.
Extension-side notes
-
Chrome:
- Manifest key is
allowed_originswith entries likechrome-extension://<ID>/. - Your extension must declare
"permissions": ["nativeMessaging"]and callchrome.runtime.connectNative("com.example.native_host"). - MV3 service workers: a live native messaging port keeps the worker alive, but only while connected—handle
onDisconnectand reconnect as needed.
- Manifest key is
-
Firefox:
-
Manifest key is
allowed_extensionswith your add-on IDs. -
Set a stable ID in your extension’s
manifest.json: -
Use
browser.runtime.connectNative("com.example.native_host").
-
Testing locally
Protocol sanity (no browser):
use ;
use json;
End-to-end with a tiny host:
- Build a small echo host that reads with
get_message()and replies withsend_message(). - Install manifests (user scope).
- Load a minimal test extension in Chrome/Firefox and connect using
connectNative().
Troubleshooting
-
“Specified native messaging host not found”
- Host
namemismatch between extension & manifest - Manifest written to the wrong location (check OS paths)
- Windows + Chrome: missing registry value (re-run install on Windows)
exe_pathnot absolute (macOS/Linux)
- Host
-
Disconnects / No messages
- Host crashed: run the host binary manually to see stderr logs
- Message too large: host enforces 1 MB host→browser
- Chrome MV3: service worker stopped—reconnect on
onDisconnect
-
Multiple Chrome channels/profiles
- Use stable Chrome and the default profile for first validation; paths vary per channel/profile.
Contributing
Contributions welcome!
- Fork
- Branch (
feature/my-feature) - Commit (
feat: add X) - PR
Please follow the Contributor Covenant.
License
MIT — see LICENSE.
Build great WebExtensions. We’ll handle the plumbing.