Skip to main content

Extension Registration

Register extensions at compile time with the register_extension! macro, which submits an ExtensionRegistration factory to the inventory crate for discovery.

Extensions register at compile time using the register_extension! macro, which leverages the inventory crate for zero-cost plugin discovery.

The register_extension! Macro

After implementing the Extension trait, register your extension:

use systemprompt::extension::prelude::*;

#[derive(Debug, Default)]
pub struct MyExtension;

impl Extension for MyExtension {
    fn metadata(&self) -> ExtensionMetadata {
        ExtensionMetadata {
            id: "my-extension",
            name: "My Extension",
            version: env!("CARGO_PKG_VERSION"),
        }
    }

    // ... other methods
}

register_extension!(MyExtension);

Macro Variants

Type Registration

For types that implement Default:

register_extension!(MyExtension);

Expands to:

inventory::submit! {
    ExtensionRegistration {
        factory: || Arc::new(MyExtension::default()) as Arc<dyn Extension>,
    }
}

Expression Registration

For types requiring configuration:

register_extension!(MyExtension::new(config));

Expands to:

inventory::submit! {
    ExtensionRegistration {
        factory: || Arc::new(MyExtension::new(config)) as Arc<dyn Extension>,
    }
}

ExtensionRegistration

The registration struct:

#[derive(Debug, Clone, Copy)]
pub struct ExtensionRegistration {
    pub factory: fn() -> Arc<dyn Extension>,
}

inventory::collect!(ExtensionRegistration);

Preventing Linker Stripping

Rust's linker removes unused code. Extensions in separate crates may be stripped if not directly referenced. The template's src/lib.rs prevents this by re-exporting each extension crate:

pub use systemprompt::{cli, *};
pub use systemprompt_web_extension as web;

The re-export keeps the crate reachable from the binary, so its inventory registrations survive linking.

Adding a New Extension

  1. Create extension crate:
# extensions/my-extension/Cargo.toml
[package]
name = "my-extension"
version = "0.1.0"
edition.workspace = true

[dependencies]
systemprompt = { workspace = true, features = ["full"] }
  1. Implement and register:
// extensions/my-extension/src/lib.rs
use systemprompt::extension::prelude::*;

#[derive(Debug, Default)]
pub struct MyExtension;

impl Extension for MyExtension {
    fn metadata(&self) -> ExtensionMetadata {
        ExtensionMetadata {
            id: "my-extension",
            name: "My Extension",
            version: env!("CARGO_PKG_VERSION"),
        }
    }
}

register_extension!(MyExtension);

pub const PREFIX: &str = "my-extension";
  1. Add to workspace:
# Cargo.toml (root)
[workspace]
members = [
    "extensions/my-extension",
    # ...
]
  1. Add the dependency and re-export it so the linker keeps it:
# Cargo.toml (root)
[dependencies]
my-extension = { workspace = true }
// src/lib.rs
pub use my_extension;

The Inventory Crate

The inventory crate provides compile-time plugin registration:

  • Zero runtime cost - Registration happens at compile time
  • No global mutable state - Type-safe collection
  • Automatic discovery - No manual registration list
  • Linker-based - Uses linker sections for collection

Verification

Check registered extensions:

systemprompt plugins list

Use --type compiled to show only compile-time extensions, or systemprompt plugins show <id> for details on one.