Skip to content
← All articles
Manatan SRSExtensionsDevelopment

Build your first Manatan SRS extension

The Manatan SRS SDK is now on crates.io. Build your first extension from a fresh Rust project, then package and test a dashboard panel with read-only permissions.

Manatan SRS extensions let you shape your study workflow. Add a dashboard panel, respond to review events, or connect a study tool through a local API. You write the feature; Manatan provides the collection, review, and interface capabilities your extension is allowed to use.

The Manatan SRS SDK 0.1.0 is now available on crates.io. In this tutorial, you will build a dashboard panel that counts your decks, starting from a fresh Rust project. You do not need to clone Manatan or access its private source code.

Version 0.1.0 is a developer preview. Test your extension against the Manatan SRS host version and platforms you intend to support.

What you are building

An extension package contains a manifest.json and a WebAssembly component. The manifest names the extension, identifies the component, and declares its permissions. The component implements the extension API; the host supplies the capabilities the user grants.

For a deck counter, read-only collection access is enough. It does not need to change cards, control the reviewer, or connect to the network.

Set up the toolchain

You will need a current Rust toolchain, a ZIP utility, and a compatible Manatan SRS app for testing. The shell commands below work on macOS, Linux, or Windows through WSL.

Install the WebAssembly compilation target and component tools:

rustup target add wasm32-unknown-unknown
cargo install wasm-tools --locked

The SDK uses host-provided capabilities instead of ambient filesystem or socket access. Build for wasm32-unknown-unknown, not a WASI target. If your extension later needs time, logging, or network access, use the SDK interfaces rather than operating-system APIs.

Create the project

cargo new --lib deck-counter
cd deck-counter

Replace the generated Cargo.toml with:

[package]
name = "deck-counter"
version = "0.1.0"
edition = "2021"
publish = false

[lib]
crate-type = ["cdylib"]

[dependencies]
manatan-srs-sdk = "=0.1.0"

[profile.release]
opt-level = "s"
lto = true
strip = true

cdylib produces the core WebAssembly module that the component tools will wrap. The dependency is pinned to the SDK version used here. publish = false keeps this example from accidentally being uploaded as a Rust crate; sharing the finished extension is a separate step.

Write the panel

Replace src/lib.rs with:

use manatan_srs_sdk::{collection, Extension};

#[derive(Default)]
struct DeckCounter;

impl Extension for DeckCounter {
    fn render(&mut self, surface: String, _context: String) -> String {
        if surface != "dashboard" {
            return String::new();
        }

        let count = collection::list_decks().len();
        format!("<section><h3>Your decks</h3><p>Decks in this collection: {count}</p></section>")
    }
}

manatan_srs_sdk::export_extension!(DeckCounter);

render returns an HTML fragment for the requested surface. Returning an empty string leaves other surfaces alone. Every Extension hook has a default implementation, so a panel does not need to implement review or editor hooks. The export macro connects your type to the component interface.

This example inserts only a number into HTML. If you later include note fields, deck names, or other external text, escape it before inserting it into markup. Keep expensive work out of rendering.

Declare the permission

Create manifest.json in the project root:

{
  "schemaVersion": 1,
  "id": "deck-counter",
  "name": "Deck Counter",
  "version": "0.1.0",
  "description": "A small dashboard panel showing your deck count.",
  "author": "Your name",
  "license": "MIT",
  "apiVersion": 1,
  "wasm": "deck-counter.wasm",
  "surfaces": ["dashboard"],
  "permissions": {
    "collection": "read"
  }
}

Choose your own unique extension ID before sharing a real project. Keep the wasm filename consistent with the file you place in the package. The user should be able to understand why each requested permission is necessary.

The extension's version identifies your package release. apiVersion identifies the host API it targets; it is not the SDK crate's version number.

Build and package it

From the deck-counter project directory:

cargo build --target wasm32-unknown-unknown --release
wasm-tools component new target/wasm32-unknown-unknown/release/deck_counter.wasm -o target/deck-counter.wasm
wasm-tools validate --features component-model target/deck-counter.wasm
wasm-tools component wit target/deck-counter.wasm

The first command produces a core WebAssembly module. component new wraps it as a component; uploading the core module alone is not sufficient. Validation checks the component's structure, not its behavior in the app.

Cargo replaces the hyphen in deck-counter with an underscore in the core module filename, deck_counter.wasm. The component command deliberately writes deck-counter.wasm to match the manifest. Keep the generated Cargo.lock so later builds can use the same dependency versions.

Create a package with the manifest and component at its root:

mkdir -p target/deck-counter-package
cp manifest.json target/deck-counter-package/manifest.json
cp target/deck-counter.wasm target/deck-counter-package/deck-counter.wasm
cd target/deck-counter-package
zip ../deck-counter.mext manifest.json deck-counter.wasm
cd ../..

The .mext file is a ZIP archive with the manifest and component directly at its root, not inside a surrounding folder. The file to install or share is target/deck-counter.mext.

Test before sharing

In a compatible Manatan SRS app, open Extensions, choose Install from File, and select target/deck-counter.mext. Check that the permission prompt asks only for collection reading. Use a disposable collection and verify that the dashboard panel shows your deck count. Then restart the app, disable the extension and confirm the panel disappears, re-enable it, and uninstall it to check its lifecycle. This example does not need to modify your collection to do its job.

Test against the host version and platforms you intend to support. Native file dialogs, local HTTP listeners, and system media features have platform-specific requirements; a structurally valid component does not guarantee that every capability is available everywhere. Prefer a desktop host for your first test; do not assume browser and mobile support from the build result alone.

If installation or rendering fails, check these first:

  • A missing-component error usually means the manifest filename does not match the archive, or the files are inside an extra folder.
  • A core-module error means you packaged the output from cargo build instead of the component produced by wasm-tools component new.
  • If the panel does not appear, confirm the extension is enabled, the dashboard surface is declared, and collection reading was granted.

When you are ready to share, open Manatan SRS Extensions, choose Publish an extension, and upload the reviewed package. Explain what it does, include useful screenshots, choose a license you can grant, and describe any required host version or platform restrictions. Do not include account secrets or private collection data in a package.

Explore more extension features

After the panel works, try on_review_answered for review feedback, store for persistent configuration, or ui for a command. Add permissions only when the feature needs them.

Explore the Rust API reference, the authoring guide, and the capability guide for the wider API. The SDK repository also includes examples of review hooks, commands, timers, and integrations. Start with one useful improvement and make it reliable before expanding the extension.