Volt LogoVolt
Plugin Development

Plugin Examples

Real-world plugin implementations and use cases

Plugin Examples

Learn from real examples

These production-ready plugin implementations demonstrate best practices and common patterns.

Calculator Plugin

A simple calculator plugin that evaluates mathematical expressions:

import {
  Plugin,
  PluginContext,
  PluginResult,
  PluginResultType,
} from "@/types/plugin";
import { evaluate } from "mathjs";

class CalculatorPlugin implements Plugin {
  id = "calculator";
  name = "Calculator";
  description = "Evaluate mathematical expressions";
  enabled = true;

  canHandle(context: PluginContext): boolean {
    // Check if query looks like a math expression
    const mathRegex = /^[\d\s+\-*/().^%]+$/;
    return mathRegex.test(context.query.trim());
  }

  async match(context: PluginContext): Promise<PluginResult[]> {
    try {
      const result = evaluate(context.query);

      return [
        {
          id: "1",
          title: String(result),
          subtitle: context.query,
          icon: "🔢",
          type: PluginResultType.Calculator,
          action: () => {
            navigator.clipboard.writeText(String(result));
          },
        },
      ];
    } catch (error) {
      return [];
    }
  }

  async execute(result: PluginResult): Promise<void> {
    if (result.action) {
      await result.action();
    }
  }
}

export default CalculatorPlugin;

Web Search Plugin

Search the web directly from Volt:

import {
  Plugin,
  PluginContext,
  PluginResult,
  PluginResultType,
} from "@/types/plugin";

class WebSearchPlugin implements Plugin {
  id = "web-search";
  name = "Web Search";
  description = "Search the web with various search engines";
  enabled = true;

  private searchEngines = {
    google: "https://google.com/search?q=",
    bing: "https://bing.com/search?q=",
    duckduckgo: "https://duckduckgo.com/?q=",
  };

  canHandle(context: PluginContext): boolean {
    return context.query.startsWith("search ") || context.query.startsWith("?");
  }

  async match(context: PluginContext): Promise<PluginResult[]> {
    const query = context.query.replace(/^(search |\?)/, "").trim();

    if (!query) return [];

    return Object.entries(this.searchEngines).map(([engine, url], index) => ({
      id: String(index),
      title: `Search "${query}" on ${engine}`,
      subtitle: `Open ${engine} in browser`,
      icon: "🔍",
      type: PluginResultType.WebSearch,
      metadata: { url: url + encodeURIComponent(query) },
      action: () => {
        window.open(url + encodeURIComponent(query), "_blank");
      },
    }));
  }

  async execute(result: PluginResult): Promise<void> {
    if (result.action) {
      await result.action();
    }
  }
}

export default WebSearchPlugin;

System Monitor Plugin

Display system information:

import {
  Plugin,
  PluginContext,
  PluginResult,
  PluginResultType,
} from "@/types/plugin";
import { invoke } from "@tauri-apps/api/core";

class SystemMonitorPlugin implements Plugin {
  id = "system-monitor";
  name = "System Monitor";
  description = "Display system information";
  enabled = true;

  canHandle(context: PluginContext): boolean {
    const triggers = ["system", "cpu", "memory", "disk"];
    return triggers.some((trigger) =>
      context.query.toLowerCase().includes(trigger)
    );
  }

  async match(context: PluginContext): Promise<PluginResult[]> {
    try {
      const systemInfo = await invoke("get_system_info");

      return [
        {
          id: "cpu",
          title: `CPU: ${systemInfo.cpu_usage}%`,
          subtitle: `${systemInfo.cpu_name}`,
          icon: "🖥️",
          type: PluginResultType.SystemMonitor,
        },
        {
          id: "memory",
          title: `Memory: ${systemInfo.memory_used}GB / ${systemInfo.memory_total}GB`,
          subtitle: `${systemInfo.memory_percent}% used`,
          icon: "💾",
          type: PluginResultType.SystemMonitor,
        },
        {
          id: "disk",
          title: `Disk: ${systemInfo.disk_used}GB / ${systemInfo.disk_total}GB`,
          subtitle: `${systemInfo.disk_percent}% used`,
          icon: "💿",
          type: PluginResultType.SystemMonitor,
        },
      ];
    } catch (error) {
      console.error("Failed to get system info:", error);
      return [];
    }
  }

  async execute(result: PluginResult): Promise<void> {
    // System monitor results are read-only
  }
}

export default SystemMonitorPlugin;

Timer Plugin

Create timers and countdowns:

import {
  Plugin,
  PluginContext,
  PluginResult,
  PluginResultType,
} from "@/types/plugin";

class TimerPlugin implements Plugin {
  id = "timer";
  name = "Timer";
  description = "Create timers and countdowns";
  enabled = true;

  canHandle(context: PluginContext): boolean {
    return /^timer\s+\d+[smh]?$/i.test(context.query);
  }

  async match(context: PluginContext): Promise<PluginResult[]> {
    const match = context.query.match(/^timer\s+(\d+)([smh])?$/i);
    if (!match) return [];

    const [, amount, unit = "s"] = match;
    const seconds = this.convertToSeconds(parseInt(amount), unit);

    return [
      {
        id: "1",
        title: `Set ${amount}${unit} timer`,
        subtitle: `Timer for ${this.formatDuration(seconds)}`,
        icon: "⏱️",
        type: PluginResultType.Timer,
        metadata: { seconds },
        action: () => {
          this.startTimer(seconds);
        },
      },
    ];
  }

  async execute(result: PluginResult): Promise<void> {
    if (result.action) {
      await result.action();
    }
  }

  private convertToSeconds(amount: number, unit: string): number {
    switch (unit.toLowerCase()) {
      case "m":
        return amount * 60;
      case "h":
        return amount * 3600;
      default:
        return amount;
    }
  }

  private formatDuration(seconds: number): string {
    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    const secs = seconds % 60;

    if (hours > 0) return `${hours}h ${minutes}m ${secs}s`;
    if (minutes > 0) return `${minutes}m ${secs}s`;
    return `${secs}s`;
  }

  private startTimer(seconds: number): void {
    const endTime = Date.now() + seconds * 1000;

    const interval = setInterval(() => {
      const remaining = Math.max(0, Math.floor((endTime - Date.now()) / 1000));

      if (remaining === 0) {
        clearInterval(interval);
        new Notification("Timer Complete!", {
          body: "Your timer has finished",
        });
      }
    }, 1000);
  }
}

export default TimerPlugin;

Clipboard History Plugin

Access clipboard history:

import {
  Plugin,
  PluginContext,
  PluginResult,
  PluginResultType,
} from "@/types/plugin";
import { invoke } from "@tauri-apps/api/core";

class ClipboardPlugin implements Plugin {
  id = "clipboard";
  name = "Clipboard History";
  description = "Access clipboard history";
  enabled = true;

  canHandle(context: PluginContext): boolean {
    return context.query.toLowerCase().startsWith("clip");
  }

  async match(context: PluginContext): Promise<PluginResult[]> {
    try {
      const history = await invoke<string[]>("get_clipboard_history");

      return history.slice(0, 10).map((item, index) => ({
        id: String(index),
        title: item.substring(0, 50),
        subtitle: `${item.length} characters`,
        icon: "📋",
        type: PluginResultType.Clipboard,
        metadata: { content: item },
        action: () => {
          navigator.clipboard.writeText(item);
        },
      }));
    } catch (error) {
      console.error("Failed to get clipboard history:", error);
      return [];
    }
  }

  async execute(result: PluginResult): Promise<void> {
    if (result.action) {
      await result.action();
    }
  }
}

export default ClipboardPlugin;

Backend Plugin Example (Rust)

Steam game scanner plugin:

use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Debug, Serialize, Deserialize)]
pub struct GameInfo {
    pub id: String,
    pub name: String,
    pub path: PathBuf,
    pub icon: Option<String>,
}

pub struct SteamPlugin {
    steam_path: PathBuf,
}

impl SteamPlugin {
    pub fn new() -> Self {
        let steam_path = Self::find_steam_path();
        Self { steam_path }
    }

    fn find_steam_path() -> PathBuf {
        #[cfg(target_os = "windows")]
        {
            PathBuf::from("C:\\Program Files (x86)\\Steam")
        }
        #[cfg(target_os = "macos")]
        {
            PathBuf::from("/Users/Shared/Steam")
        }
        #[cfg(target_os = "linux")]
        {
            PathBuf::from("~/.steam/steam")
        }
    }

    pub fn scan_games(&self) -> Vec<GameInfo> {
        let library_path = self.steam_path.join("steamapps");
        let mut games = Vec::new();

        if let Ok(entries) = std::fs::read_dir(&library_path) {
            for entry in entries.flatten() {
                if let Some(name) = entry.file_name().to_str() {
                    if name.starts_with("appmanifest_") && name.ends_with(".acf") {
                        if let Ok(game_info) = self.parse_manifest(&entry.path()) {
                            games.push(game_info);
                        }
                    }
                }
            }
        }

        games
    }

    fn parse_manifest(&self, path: &PathBuf) -> Result<GameInfo, Box<dyn std::error::Error>> {
        let content = std::fs::read_to_string(path)?;

        // Simple ACF parsing
        let id = Self::extract_value(&content, "appid")?;
        let name = Self::extract_value(&content, "name")?;
        let install_dir = Self::extract_value(&content, "installdir")?;

        let game_path = self.steam_path
            .join("steamapps")
            .join("common")
            .join(&install_dir);

        Ok(GameInfo {
            id,
            name,
            path: game_path,
            icon: None,
        })
    }

    fn extract_value(content: &str, key: &str) -> Result<String, Box<dyn std::error::Error>> {
        let pattern = format!("\"{}\"\\s+\"([^\"]+)\"", key);
        let re = regex::Regex::new(&pattern)?;

        if let Some(captures) = re.captures(content) {
            Ok(captures[1].to_string())
        } else {
            Err("Key not found".into())
        }
    }
}

#[tauri::command]
pub async fn scan_steam_games() -> Result<Vec<GameInfo>, String> {
    let plugin = SteamPlugin::new();
    Ok(plugin.scan_games())
}

For more information, check the API Reference or learn how to publish your plugin.

On this page