The Rust-first, Agent-Native browser extension framework for building production-grade extensions at insane speed.
π€ Agent-Optimized: Extro is designed for machine consumption. AI agents can use
extro assistant statusto understand the project state and follow standardized workflows in.extro/workflows/.
Extro puts Rust in the driver's seat: domain logic, state machines, and AI policy live in Rust and compile to WebAssembly. JavaScript remains a thin adapter for browser APIs and UI rendering.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Browser Extension β
β βββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Popup ββββββΊβ Background ββββββΊβ Rust/WASM β β
β β (React) β β (SW/JS) β β (Core) β β
β βββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β² β β β
β β β β β
β ββββββββββββββββββββββ΄βββββββββββββββββββββ β
β Content Script (DOM) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Traditional Extensions | Extro Extensions |
|---|---|
| Logic scattered across JS files | Centralized Rust core |
| Runtime errors in production | Compile-time safety |
| Hard to test state machines | Testable pure functions |
| AI calls browser APIs directly | AI proposes, Rust decides |
| Debugging is console.log hell | Structured logging + telemetry |
| Framework | Cold Start | Memory | Bundle Size |
|---|---|---|---|
| Vanilla MV3 | ~200ms | 45MB | 180KB |
| Plasmo | ~350ms | 62MB | 420KB |
| Extro | ~120ms | 38MB | 95KB |
Measured on M1 MacBook Pro, Chrome 120. Lower is better.
- Rust 1.70+ (
rustup install stable) - Node.js 18+ (for extension assets)
wasm-pack(cargo install wasm-pack)- Chrome/Chromium/Edge for testing
# Install the CLI
cargo install extro-cli
# Create a new extension
extro new my-extension
cd my-extension
# Install JS dependencies
pnpm install
# Start development mode (watches for changes)
extro watch
# In another terminal, launch Chrome with extension loaded
extro dev-inject chromeEdit crates/core/src/lib.rs:
pub fn greet(name: &str) -> String {
format!("Hello, {}! Built with Extro π", name)
}Call from JavaScript:
import { greet } from './pkg/extro_wasm.js';
console.log(greet("World")); // "Hello, World! Built with Extro π"extro new <name> Create a new extension project
extro build Build for production
extro build --dev Build with debug symbols
extro watch Watch mode with hot reload
extro dev-inject chrome Launch Chrome with extension loaded
extro test Run Rust + WASM tests
extro test core Run only core crate tests
extro clean Clean all build artifacts
extro package Package as .zip for distribution
extro package --format crx Package as .crx (requires keys)
extro info Show environment info
my-extension/
βββ Cargo.toml # Rust workspace config
βββ crates/
β βββ core/ # Pure Rust domain logic
β β βββ src/
β β β βββ lib.rs # State machines, reducers, effects
β β βββ Cargo.toml
β βββ wasm/ # WASM bindings
β βββ src/
β β βββ lib.rs # wasm-bindgen exports
β βββ Cargo.toml
βββ extension/ # Browser extension shell
β βββ manifest.json # Manifest V3
β βββ src/
β βββ background/ # Service worker (orchestration)
β βββ content/ # Content scripts (DOM access)
β βββ popup/ # Popup UI (React/vanilla)
β βββ shared/ # WASM loader, utilities
βββ dist/ # Build output (gitignored)
βββ pkg/ # WASM output (gitignored)
User Action β Content Script β Background β WASM Core β Effects β Browser API
- User interacts with popup or selects text on page
- Content script captures DOM state, sends to background
- Background passes payload to WASM via
engine.dispatch() - Rust core validates, updates state, returns
CoreResult { message, effects } - Background executes effects through browser APIs
- Result rendered in popup/content
// Command from JS to Rust
pub struct CoreCommand {
pub surface: RuntimeSurface, // Popup, ContentScript, Background
pub action: CoreAction, // What to do
pub snapshot: BrowserSnapshot, // Current browser state
}
// Response from Rust to JS
pub struct CoreResult {
pub message: String, // Human-readable result
pub effects: Vec<BrowserEffect>, // Side effects to execute
}
// Effects that Rust can request
pub enum BrowserEffect {
PersistSession { key: String, value: String },
ShowPopupToast { message: String },
OpenSidePanel { route: String },
ReadDomSelection,
// Add your own...
}Extro is designed for AI-powered extensions. The key principle:
The model proposes; Rust decides.
// Rust defines allowed tools
pub struct ToolRegistry {
allowed_tools: Vec<String>,
schemas: HashMap<String, JsonSchema>,
}
// AI suggests a tool call
pub struct AIToolCall {
pub tool_name: String,
pub arguments: JsonValue,
}
// Rust validates before execution
impl ToolRegistry {
pub fn validate(&self, call: &AIToolCall) -> Result<(), ToolError> {
if !self.allowed_tools.contains(&call.tool_name) {
return Err(ToolError::Unauthorized(call.tool_name.clone()));
}
// Validate arguments against schema...
Ok(())
}
}This ensures:
- AI cannot call arbitrary browser APIs
- All tool calls are validated against schemas
- Deterministic policy enforcement
- Audit trail for all AI actions
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_greet() {
assert_eq!(greet("Alice"), "Hello, Alice! Built with Extro π");
}
#[test]
fn test_dispatch_command() {
let mut state = CoreState::new();
let command = CoreCommand { /* ... */ };
let result = state.dispatch(command);
assert!(result.message.contains("success"));
}
}Run with: extro test
// tests/integration.test.js
import { getEngine } from '../extension/src/shared/engine.js';
describe('WASM Engine', () => {
it('dispatches commands correctly', async () => {
const engine = await getEngine();
const result = await engine.dispatch({
surface: 'Popup',
action: 'SyncState',
snapshot: { url: 'https://test.com', title: 'Test', selected_text: null }
});
expect(result.message).toBeDefined();
});
});// crates/core/src/lib.rs
pub fn summarize_url(url: &str) -> BrowserEffect {
BrowserEffect::OpenSidePanel {
route: format!("/summary?url={}", url),
}
}// Content script captures selection
const selection = window.getSelection().toString();
await chrome.runtime.sendMessage({
type: "extro.command",
payload: {
surface: "ContentScript",
action: "AnalyzeSelection",
snapshot: { selected_text: selection }
}
});See examples/research-assistant/ for a full AI integration example with:
- Tool validation
- Session persistence
- Side panel UI
- Streaming responses
# Build for production
extro build
# Package as ZIP
extro package --format zip
# Output: extro-extension.zip (ready for Chrome Web Store)- Build:
extro build - Package:
extro package - Upload
extro-extension.zipto Chrome Developer Dashboard
Firefox support is planned. Track progress in #42.
extro new my-ext --template minimal # Bare bones
extro new my-ext --template full # Full-featured with React
extro new my-ext --template ai # AI-powered with tool registry- extro-twitter-enhancer - Better Twitter UX
- extro-notion-ai - AI writing assistant for Notion
- extro-github-pr-helper - PR review automation
Extro is open source! Here's how to help:
- π Report bugs via GitHub Issues
- π‘ Suggest features
- π Improve documentation
- π§ Submit PRs (see CONTRIBUTING.md)
git clone https://github.com/askpext/extro.git
cd extro
# Build the CLI
cargo build -p extro-cli --release
# Run tests
cargo test --workspace
# Link CLI locally
cargo install --path ./cli- β Rust core + WASM bridge
- β Full CLI with watch/test/package
- β MV3 extension shell
- β Basic documentation
- Firefox support
- Side panel API
- Built-in React template
- DevTools panel integration
- Extension hot reload without browser restart
- Plugin system
- Remote extension registry
- CRDT-based state sync
- Multi-account support
- Built-in AI model adapters
Extro builds on amazing work from:
- wasm-bindgen - Rust/WASM interop
- cargo - Rust package manager
- Chrome Extensions - Browser APIs
- Plasmo - Inspiration for DX
MIT License - see LICENSE for details.
- π Documentation
- π¬ Discord Community
- π¦ Twitter
- π§ Email
Built with β€οΈ by Aditya Pandey and contributors.