Chrome extensions,
entirely in Rust.

Write type-safe Chrome extensions with proc macros and Leptos. Compiled to WebAssembly. Zero JavaScript by hand.

lib.rs
#[oxichrome::extension( name = "My Extension", permissions = ["storage", "activeTab"] )] struct App; #[oxichrome::popup] fn Popup() -> impl IntoView { let count = RwSignal::new(0); view! { <button on:click=move |_| count.update(|c| *c += 1)> {move || count.get()} </button> } }
Features

Write less. Ship more.

Everything you need to build Chrome extensions without leaving Rust.

Proc Macro DX
Four attributes (#[extension], #[background], #[popup], #[on]) replace hundreds of lines of boilerplate.
Type-Safe APIs
Every Chrome API is wrapped in a typed, async Rust interface. Errors at compile time, not in production.
WebAssembly Output
Your Rust compiles to Wasm. Small binaries, near-native speed, sandboxed execution inside the browser.
Leptos UI
Build popup and options pages with Leptos fine-grained reactivity. No virtual DOM, no JS framework.
One Command Build
cargo oxichrome build compiles your crate, generates manifest.json, background.js, popup.html, and everything else.
Zero JavaScript
You never write JS. The build tool generates the glue code that wires Wasm exports to Chrome listeners.
API

Four macros. Full control.

Each attribute maps to a Chrome extension concept. If you know what a popup is, you know how to build one.

Defines your extension’s identity — name, version, permissions. Generates manifest.json at build time.

What you write
lib.rs
#[oxichrome::extension( name = "Color Picker", version = "0.1.0", permissions = ["storage", "activeTab"] )] struct ColorPickerExt;
What gets generated
expanded
// manifest.json { "manifest_version": 3, "name": "Color Picker", "version": "0.1.0", "permissions": ["storage", "activeTab"] }
Type Safety

Callback hell → async Rust.

Every Chrome API is wrapped in a type-safe, async Rust interface.

JavaScript / Storage
// callback-based, untyped chrome.storage.local.get("count", (result) => { const count = result.count; console.log(count + 1); });
Rust / Storage
// async, generic, type-safe let count: Option<i32> = oxichrome::storage::get("count").await?; oxichrome::log!("{}", count.unwrap_or(0) + 1);
JavaScript / Events
// manual listener registration chrome.runtime.onInstalled.addListener((details) => { if (details.reason === "install") { chrome.storage.local.set({ count: 0 }); } });
Rust / Events
// declarative attribute, async #[oxichrome::on(runtime::on_installed)] async fn on_install(details: JsValue) { oxichrome::storage::set("count", &0i32) .await.ok(); }
JavaScript / UI
// manual DOM, no reactivity const btn = document.getElementById("btn"); const display = document.getElementById("count"); btn.addEventListener("click", () => { let n = parseInt(display.textContent) + 1; display.textContent = n; });
Rust / UI
// fine-grained reactivity with Leptos #[oxichrome::popup] fn Popup() -> impl IntoView { let count = RwSignal::new(0); view! { <span>{move || count.get()}</span> <button on:click=move |_| count.update(|c| *c += 1) >"+"</button> } }
Examples

See it in action.

Full working extensions in a single Rust file. Try the interactive preview.

A minimal popup extension with persistent state. Click a button, the count increments and survives browser restarts via chrome.storage.

Counter Extension

Counter

0
Architecture

Five crates. One command.

A clean separation of concerns: macros, runtime, facade, analysis, and CLI.

Your code
oxichrome-macros
compile to Wasm
oxichrome-build
dist/
Macros
oxichrome-macros
Proc macros that expand your #[extension], #[popup], #[on] attributes into wasm_bindgen exports.
Runtime
oxichrome-core
Async Rust wrappers around chrome.* JS APIs — storage, tabs, runtime, messaging.
Facade
oxichrome
The crate you depend on. Re-exports macros and core. This is your one dependency.
Analysis
oxichrome-build
Parses your source to discover annotated functions, popup components, and event handlers.
CLI
oxichrome-cli
cargo oxichrome build — orchestrates wasm-pack, source analysis, and artifact generation.
Get Started

Five steps. Five minutes.

terminal
$ cargo install cargo-oxichrome
Installing cargo-oxichrome v0.1.0 Installed ~/.cargo/bin/cargo-oxichrome

$ cargo oxichrome new my-extension
Created my-extension/Cargo.toml Created my-extension/src/lib.rs

$ cd my-extension && cargo oxichrome build
[oxichrome] Building: my-extension Compiling my-extension v0.1.0 Finished release [optimized] [oxichrome] Build complete → dist/
01
Install the CLI
One cargo install gets you the oxichrome build tool.
02
Scaffold a project
Creates Cargo.toml with the oxichrome dependency, and a starter lib.rs.
03
Write your extension
Use #[extension], #[popup], #[background] in Rust.
04
Build
cargo oxichrome build compiles to WASM and generates manifest, JS shims, and HTML.
05
Load in Chrome
Open chrome://extensions, load the dist/ folder. Done.
Ready?

Start building Chrome extensions in Rust today.

One command to install. Five minutes to your first extension.

$ cargo install cargo-oxichrome