Plugin author guide
This is a step-by-step guide to writing, building, and shipping a Confium
plugin. A Confium plugin is a dynamic library (.so / .dylib / .dll)
that implements the plugin contract: a small set of bootstrap symbols plus
per-interface implementation functions.
For the host-side API that loads and drives plugins, see the generated
header target/confium.h. For the engine architecture, see
the architecture page.
How a plugin is structured
Every plugin is a Rust cdylib that depends on confium-api and uses the
procedural macros from confium-macros to generate the contract symbols.
At minimum, a plugin exports:
cfmp_interface_version— returns the plugin-contract ABI version.cfmp_initialize(cfm, opts)— called once after load; receive a host handle and options.cfmp_finalize(cfm)— called before unload; release resources.cfmp_query_interfaces— returns a packed stream naming the interface types and versions the plugin implements.cfmp_metadata(optional) — returns static metadata (name, version, vendor, license) for the registry.
For each interface it advertises (e.g. hash), the plugin exports the
corresponding cfmp_\{interface\}_* symbols at the negotiated version.
Step 1: create the crate
cargo new --lib my-plugin
cd my-plugin
Edit Cargo.toml:
[package]
name = "my-plugin"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
confium-api = { path = "../confium/crates/confium-api" }
confium-macros = { path = "../confium/crates/confium-macros" }
Step 2: declare the plugin
Use the #[confium::plugin] attribute macro to emit the bootstrap
symbols. The macro takes metadata and the list of interfaces you
implement.
use confium_api::PluginMetadata;
use confium_macros::confium_plugin;
#[confium_plugin(
name = "my-hash",
version = "0.1.0",
vendor = "Example Corp",
license = "BSD-2-Clause",
interfaces = ["hash"]
)]
pub struct MyPlugin;
impl MyPlugin {
/// Called once on load. `cfm` is a host handle you can use to look up
/// other plugins (e.g. a dependency). `opts` carries string/uint
/// options from the loader.
pub fn init(cfm: *mut std::ffi::c_void, opts: &confium_api::Options) -> Result<(), confium_api::Error> {
// Read configuration, open files, allocate state, ...
Ok(())
}
/// Called once before unload. Release everything `init` acquired.
pub fn finalize(cfm: *mut std::ffi::c_void) {
// ...
}
}
Step 3: implement an interface
For the hash interface, implement the per-instance lifecycle: create,
update, finalize, reset, clone, and size queries. The
#[confium::interface] macro generates the cfmp_hash_* symbols that
wire your Rust impl into the C ABI.
use confium_macros::confium_interface;
pub struct Sha256 {
// your hasher state
}
#[confium_interface(name = "hash", version = 0)]
impl Sha256 {
/// Construct a new hasher. `opts` carries provider-specific options.
pub fn create(opts: &confium_api::Options) -> Result<Self, confium_api::Error> {
// ...
# ![allow(unimplemented)]
unimplemented!()
}
/// Feed bytes into the hasher.
pub fn update(&mut self, data: &[u8]) -> Result<(), confium_api::Error> {
// ...
unimplemented!()
}
/// Produce the final digest. `out` is caller-allocated and at least
/// `output_size` bytes long.
pub fn finalize(&mut self, out: &mut [u8]) -> Result<(), confium_api::Error> {
// ...
unimplemented!()
}
/// Reset to the initial state (reusable context).
pub fn reset(&mut self) -> Result<(), confium_api::Error> { unimplemented!() }
/// Digest length in bytes (e.g. 32 for SHA-256).
pub fn output_size(&self) -> u32 { 32 }
/// Internal block size in bytes (e.g. 64 for SHA-256).
pub fn block_size(&self) -> u32 { 64 }
}
Step 4: declare dependencies (optional)
If your plugin depends on another provider (e.g. you wrap a botan
symmetric primitive), declare it so Confium refuses to load you unless the
dependency is satisfied:
#[confium_plugin(
name = "my-aead",
// ...
dependencies = [
confium_api::Dependency::provider("botan", ">=3.0,<4.0"),
]
)]
pub struct MyPlugin;
Confium resolves dependencies before invoking cfmp_initialize. An unmet
dependency fails the load with Error::PluginDependencyUnmet.
Step 5: build
cargo build --release
The output is target/release/libmy_plugin.so (Linux),
libmy_plugin.dylib (macOS), or my_plugin.dll (Windows).
Step 6: load and test
For local testing, load the plugin via libloading in a test
binary, or link it into the daemon. See
crates/confium-mock-plugin/ for a reference implementation and
crates/confium-test-harness/ for the NIST-style test vectors
harness.
Once the plugin is published to the registry (Step 7 below), the CLI can install and exercise it:
confium install my-hash # from the registry
confium list
confium info my-hash
Step 7: publish to the registry
Once your plugin is ready, publish it to the Confium plugin registry so others can discover and verify it:
- Generate a manifest describing the plugin (name, version, interfaces, checksum, signature).
- Submit it as a PR against
sites/registry/plugins/. - The registry’s publisher-key and trust-root model is documented under
sites/registry/.
Reference: the bootstrap symbols
| Symbol | Role |
|---|---|
cfmp_interface_version |
Returns the plugin-contract ABI version this plugin targets. |
cfmp_initialize(cfm, opts) |
One-time setup. cfm is an opaque host handle; opts is the loader-supplied option bag. |
cfmp_finalize(cfm) |
One-time teardown. |
cfmp_query_interfaces |
Returns name\0version\0 pairs terminated by an empty name. Drives per-interface version negotiation. |
cfmp_metadata (optional) |
Returns a *const CFMPluginMetadata for the registry. Omit if you do not want to be listed. |
cfmp_query_dependencies (optional) |
Returns a *const CFMDependency list. Confium resolves these before cfmp_initialize. |
Reference: interface wire formats
Interface versioning is independent per interface. A single plugin can
implement hash v0 and cipher v1. The cfmp_query_interfaces payload
encodes this as:
b"hash\0\x00\x00cipher\0\x01\x00\0" // hash v0, cipher v1
Each entry is name + \0 + one version byte + \0. The list is
terminated by an empty name (a leading \0).
Troubleshooting
Plugin fails to load (Error::PluginDependencyUnmet):
A declared dependency is missing or out of range. Run confium list to
see loaded providers and check the version range in your
Dependency::provider(...) call.
Plugin loads but interface calls return Error::InterfaceNotSupported:
The interface name in cfmp_query_interfaces does not match what the
host requested, or the version byte is higher than the host supports.
Lower the version or fix a typo in the interface name.
cfmp_* symbols missing from the built library:
Ensure crate-type = ["cdylib"] is set in [lib] and that each
implementation is covered by #[confium::interface]. Run
nm -D target/release/libmy_plugin.so | grep cfmp to verify exports.