Quick reference for avoiding common pitfalls. For architecture details, see PLUGIN_ARCHITECTURE.md.
- Must match Cargo feature name:
"plugin-foo"→plugin-foofeature - Typos here = silent build failures
compiledIn: falsemeans plugin won't exist in binary (not just disabled)
packageNamemust matchguest-js/package.jsonname exactlyuiComponentpath is relative toui/directory- Missing
manifest.js= plugin silently ignored
- Never manually edit
capabilities/default.json- it's auto-generated build.rsregenerates permissions on every build- Permission strings must follow exact format:
plugin-name:command-name
- First build after adding plugin is slow (~28s) - this is normal
bun dev:plugin-nameonly works if plugin exists in registry- Cargo features are case-sensitive:
plugin-foo≠plugin-Foo - Missing
#[cfg(feature = "plugin-foo")]= compilation errors in release
- UI components load lazily -
$stateinitializers run on first render - Plugin JS API must be built before UI can import it
- Import paths:
@agent54/plugin-foonot../plugins/... - Hot reload doesn't work for plugin manifest changes
- TypeScript plugins compile automatically (no manual build needed)
- Must be in
binaries/before build (git-ignored) - Binary names in
externalBinmust match exactly (no extensions) - Sidecars don't auto-download - run download script manually
- Missing sidecar = runtime panic, not build error
- Command format:
plugin:plugin-name|command_name(note the pipe) - Rust snake_case becomes camelCase in JS automatically
- Async commands must use
#[tauri::command]not custom macros - State injection happens at plugin init, not command call
- Check
registry.json- iscompiledIn: true? - Verify Cargo feature matches registry key exactly
- Run
bun plugin:syncto regenerate entries - Check browser console for import errors
- Permissions are auto-generated - don't edit capabilities manually
- Check command name matches exactly in Rust and permissions
- Rebuild after permission changes (
build.rsmust run)
- Binary must be synced first:
bun manage-sidecars sync - Check
tauri.conf.jsonincludes binary inexternalBin - Verify platform-specific binary exists in
binaries/ - Use
.sidecar()not.command()in Rust
- State must be managed by plugin's
Builder - Use
app.state::<YourState>()not global statics - State type must implement
Send + Sync
- Run
bun run typecheckto ensure there are no type errors - Check that
mainin package.json points to./src/index.ts - For production builds: run
bun run build:prod - TypeScript plugins work without compilation in development
- Don't import all plugins in frontend - use dynamic imports
- Avoid heavy computation in
$stateinitializers - Large plugin dependencies slow all builds (even
dev:none) - Each enabled plugin adds ~50-200ms to startup
# Fast iteration (0.9s rebuilds)
bun dev:plugin-name # Your plugin only
bun dev:none # No plugins (debugging core)
# Plugin management
bun create-plugin foo # Scaffolds everything correctly (prompts for JS/TS)
bun plugin:sync # Fix registry/filesystem mismatches
# TypeScript plugin development
cd plugins/tauri-plugin-foo/guest-js
bun run build # Compile TypeScript
bun run dev # Watch mode for TypeScript
# When things go wrong
rm -rf target/analyzer # Fix rust-analyzer lock issues
cargo clean # Nuclear option// See what features are active
#[cfg(feature = "plugin-foo")]
println!("Plugin foo is compiled in!");
// Check if plugin loaded at runtime
if app.state::<PluginConfig>().is_enabled("plugin-foo") {
println!("Plugin foo is enabled!");
}// Check if plugin API is available
if (window.__TAURI__) {
console.log('Plugin commands:', Object.keys(window.__TAURI__.invoke))
}
// Debug permission issues
try {
await invoke('plugin:foo|my_command')
} catch (err) {
console.error('Permission error:', err)
}- Minimal plugin:
tauri-plugin-settings - Sidecar usage:
tauri-plugin-bun - Complex UI:
tauri-plugin-system-info - State management: See any plugin's
lib.rs