Plugins
If none of the built-in sources fit, write a plugin. Plugins are compiled Rust cdylibs that implement a stable C ABI and live in ~/.appctl/plugins/.
When to use a plugin
Section titled “When to use a plugin”- The app exposes something that is not an HTTP API, a SQL database, an MCP server, or an HTML login form.
- You want to redistribute a sync source internally without upstreaming it.
- You need custom logic to rewrite, filter, or enrich a schema before it is handed to the agent.
The appctl-plugin-sdk crate exposes stable schema types and a declare_plugin! macro. The appctl host loads plugins with libloading, validates the ABI version, and calls your introspect function.
Minimum viable plugin
Section titled “Minimum viable plugin”use appctl_plugin_sdk::prelude::*;
fn introspect(_input: SyncInput) -> anyhow::Result<Schema> { let mut schema = Schema::new("airtable"); schema.resources.push(Resource::new("record")); Ok(schema)}
appctl_plugin_sdk::declare_plugin! { name: "airtable", version: "0.1.0", introspect,}Cargo:
[lib]crate-type = ["cdylib"]
[dependencies]appctl-plugin-sdk = "0.2"anyhow = "1"Build and install
Section titled “Build and install”cargo build --releaseappctl plugin install ./target/release/libappctl_airtable.dylibVerify:
appctl plugin listSync with the plugin:
appctl sync --plugin airtable --forceHost lifecycle
Section titled “Host lifecycle”appctlscans~/.appctl/plugins/at startup.- Each shared library is probed for an
appctl_plugin_sdkexport with a matching ABI version. - On sync, the host calls
introspectonce and writes the returned schema to.appctl/schema.json.
Known limits
Section titled “Known limits”- The ABI is still early. Pin to exact SDK minor versions; rebuild plugins when
appctlupgrades. - Plugins run in the host process. A panic crashes
appctl. ReturnErr(...)for recoverable failures. - Windows shared libraries are not covered by CI yet.
See also
Section titled “See also”appctl plugin(plugin management lives underappctl plugin)- Sync and schema