Skip to content

Latest commit

 

History

History
146 lines (109 loc) · 4.69 KB

File metadata and controls

146 lines (109 loc) · 4.69 KB

Plugin Development Reference

Quick reference for avoiding common pitfalls. For architecture details, see PLUGIN_ARCHITECTURE.md.

Critical Files & Their Quirks

registry.json

  • Must match Cargo feature name: "plugin-foo"plugin-foo feature
  • Typos here = silent build failures
  • compiledIn: false means plugin won't exist in binary (not just disabled)

manifest.js

  • packageName must match guest-js/package.json name exactly
  • uiComponent path is relative to ui/ directory
  • Missing manifest.js = plugin silently ignored

permissions/default.toml

  • Never manually edit capabilities/default.json - it's auto-generated
  • build.rs regenerates permissions on every build
  • Permission strings must follow exact format: plugin-name:command-name

Non-Obvious Gotchas

Build System

  • First build after adding plugin is slow (~28s) - this is normal
  • bun dev:plugin-name only works if plugin exists in registry
  • Cargo features are case-sensitive: plugin-fooplugin-Foo
  • Missing #[cfg(feature = "plugin-foo")] = compilation errors in release

Frontend Loading

  • UI components load lazily - $state initializers run on first render
  • Plugin JS API must be built before UI can import it
  • Import paths: @agent54/plugin-foo not ../plugins/...
  • Hot reload doesn't work for plugin manifest changes
  • TypeScript plugins compile automatically (no manual build needed)

Sidecar Binaries

  • Must be in binaries/ before build (git-ignored)
  • Binary names in externalBin must match exactly (no extensions)
  • Sidecars don't auto-download - run download script manually
  • Missing sidecar = runtime panic, not build error

Command Invocation

  • 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

Common Mistakes

"My plugin doesn't load"

  1. Check registry.json - is compiledIn: true?
  2. Verify Cargo feature matches registry key exactly
  3. Run bun plugin:sync to regenerate entries
  4. Check browser console for import errors

"Commands return permission denied"

  1. Permissions are auto-generated - don't edit capabilities manually
  2. Check command name matches exactly in Rust and permissions
  3. Rebuild after permission changes (build.rs must run)

"Sidecar command fails"

  1. Binary must be synced first: bun manage-sidecars sync
  2. Check tauri.conf.json includes binary in externalBin
  3. Verify platform-specific binary exists in binaries/
  4. Use .sidecar() not .command() in Rust

"State not accessible in commands"

  1. State must be managed by plugin's Builder
  2. Use app.state::<YourState>() not global statics
  3. State type must implement Send + Sync

"TypeScript plugin not working"

  1. Run bun run typecheck to ensure there are no type errors
  2. Check that main in package.json points to ./src/index.ts
  3. For production builds: run bun run build:prod
  4. TypeScript plugins work without compilation in development

Performance Traps

  • Don't import all plugins in frontend - use dynamic imports
  • Avoid heavy computation in $state initializers
  • Large plugin dependencies slow all builds (even dev:none)
  • Each enabled plugin adds ~50-200ms to startup

Essential Commands

# 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

Quick Debugging

// 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)
}

Links to Examples

  • Minimal plugin: tauri-plugin-settings
  • Sidecar usage: tauri-plugin-bun
  • Complex UI: tauri-plugin-system-info
  • State management: See any plugin's lib.rs