native_messaging
A batteries-included Rust crate for browser Native Messaging:
- Build a Native Messaging host that talks to your extension over stdin/stdout
- Install, verify, and remove the native host manifest for multiple browsers
- Safe framing + size caps + structured errors (
NmError) - Cross-platform: Windows / macOS / Linux
This crate aims to be the “it just works” choice for native messaging.
What is Native Messaging?
Native Messaging is how a browser extension talks to a local native process (your “host”). The protocol is:
- A 4-byte length prefix (
u32, native endianness) - Followed by that many bytes of UTF-8 JSON
Your host reads from stdin and writes to stdout.
Big gotchas (read this first)
- Disconnect is normal: when the extension disconnects or browser exits, stdin usually closes.
Treat
NmError::Disconnectedas a normal shutdown. - Never log to stdout: stdout is reserved for framed messages. Logging to stdout corrupts the stream. Log to stderr or a file.
- Size limits are real:
- host → browser: 1 MiB (enforced)
- browser → host: 64 MiB (enforced)
Install
Tokio is required for the async host helpers (recommended). Use these features:
= { = "1", = ["macros", "rt", "rt-multi-thread", "sync"] }
Quickstart: robust async host loop (recommended)
This is the easiest correct way to run a host continuously and reply to messages.
use ;
use ;
async
Logging (important)
Do not use println!() in a host. It writes to stdout and breaks the protocol.
Use stderr:
eprintln!; // ✅ safe
// println!("host starting"); // ❌ unsafe
JS extension example (Chrome/Chromium)
This is what the extension side typically looks like:
const port = chrome..;
port..;
port..;
port.;
Pure framing (unit-test friendly)
You can test framing without stdin/stdout using an in-memory buffer:
use ;
use json;
use Cursor;
let msg = json!;
let frame = encode_message.unwrap;
let mut cur = new;
let raw = decode_message.unwrap;
let back: Value = from_str.unwrap;
assert_eq!;
One-shot read/write helpers (convenience)
These helpers read one message from stdin and write one reply to stdout.
For production hosts, prefer event_loop.
use ;
use NmError;
use ;
async
Installing a manifest (config-driven browsers)
This crate includes an installer for writing/verifying/removing manifests for supported browsers.
Browser allowlists differ by family:
- Chromium-family uses
allowed_originslike:chrome-extension://<EXT_ID>/ - Firefox-family uses
allowed_extensionslike:your-addon@example.org
use Path;
use ;
let host_name = "com.example.host";
let description = "Example native messaging host";
// On macOS/Linux, this must be an absolute path.
let exe_path = new;
// Chromium-family allow-list:
let allowed_origins = vec!;
// Firefox-family allow-list:
let allowed_extensions = vec!;
// Install for selected browsers by key:
let browsers = &;
install.unwrap;
Verify installation
use ;
let ok = verify_installed.unwrap;
assert!;
Remove a manifest
use ;
remove.unwrap;
Troubleshooting
Native messaging failures are usually manifest issues, not code.
“Specified native messaging host not found”
Check:
- The extension calls the exact same
host_nameyou installed (case-sensitive). - The manifest exists at the expected location (User vs System scope).
- The manifest JSON is valid.
“Access to the specified native messaging host is forbidden”
Check:
- Chromium-family:
allowed_originscontains exactchrome-extension://<id>/ - Firefox-family:
allowed_extensionscontains your addon ID
“Native host has exited” / “Failed to start native messaging host”
Check:
- The manifest
pathpoints to a real executable. - On macOS/Linux, manifest
pathis absolute. - Your host does not log to stdout.
- Your host handles disconnect cleanly (EOF →
NmError::Disconnected).
Host prints weird JSON / extension can’t parse
This almost always means stdout was corrupted by logs. Switch logging to stderr/file.
API overview
Most users only need:
-
Host:
native_messaging::host::event_loopnative_messaging::host::Sendernative_messaging::host::NmError
-
Installer:
native_messaging::installnative_messaging::verify_installednative_messaging::removenative_messaging::Scope
Notes for crate maintainers / contributors
Run tests (including docs)
Clippy (strict)
License
MIT