Volt LogoVolt
Extension Development

Development Workflow

Set up your local development environment for Volt extensions

Development Workflow

Learn how to set up your local environment for developing and testing Volt extensions.

Project Setup

Create Extension Directory

Create a new directory for your extension:

mkdir my-extension
cd my-extension

Initialize Project Structure

Create the essential files:

manifest.json
index.ts
types.ts
manifest.json
{
  "id": "my-extension",
  "name": "My Extension",
  "version": "1.0.0",
  "description": "My awesome extension",
  "author": { "name": "Your Name" },
  "main": "index.ts",
  "category": "utilities",
  "minVoltVersion": "0.4.0",
  "permissions": []
}

Optional: Set up TypeScript (for IDE support)

While Volt handles TypeScript compilation, you can set up a tsconfig.json for better IDE support:

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "noEmit": true
  },
  "include": ["*.ts", "**/*.ts"]
}
package.json
{
  "name": "my-extension",
  "private": true,
  "devDependencies": {
    "typescript": "^5.3.0"
  }
}

Type Definitions

Create a types.ts file for better TypeScript support:

types.ts
// Plugin interfaces (copied from Volt's type system)
export interface Plugin {
  id: string;
  name: string;
  description: string;
  enabled: boolean;
  canHandle(context: PluginContext): boolean;
  match(context: PluginContext): Promise<PluginResult[]> | PluginResult[] | null;
  execute(result: PluginResult): Promise<void> | void;
}

export interface PluginContext {
  query: string;
  settings?: Record<string, unknown>;
}

export interface PluginResult {
  id: string;
  type: PluginResultType;
  title: string;
  subtitle?: string;
  icon?: string;
  badge?: string;
  score: number;
  data?: Record<string, unknown>;
  pluginId?: string;
}

export enum PluginResultType {
  Calculator = "calculator",
  WebSearch = "websearch",
  SystemCommand = "systemcommand",
  FileExplorer = "fileexplorer",
  Timer = "timer",
  SystemMonitor = "systemmonitor",
  Steam = "steam",
  Game = "game",
  Clipboard = "clipboard",
  Emoji = "emoji",
  Info = "info",
  Password = "password",
}

// VoltAPI type definition for IDE support
export interface VoltAPI {
  types: {
    PluginResultType: typeof PluginResultType;
  };
  utils: {
    fuzzyScore(query: string, target: string): number;
    copyToClipboard(text: string): Promise<boolean>;
    openUrl(url: string): Promise<void>;
    formatNumber(num: number): string;
  };
  invoke<T>(cmd: string, args?: Record<string, unknown>): Promise<T>;
  events: {
    emit(event: string, detail?: unknown): void;
    on(event: string, handler: (detail: unknown) => void): () => void;
  };
  notify(message: string, type?: "info" | "success" | "error"): void;
}

// Declare global VoltAPI
declare global {
  interface Window {
    VoltAPI: VoltAPI;
  }
}

Local Testing

Like Raycast!

Link your extension folder directly to Volt for instant testing with hot reload. No packaging required!

Open Volt Settings

  1. Press Ctrl+Shift+Space to open Volt
  2. Type settings and press Enter
  3. Go to Extensions tab
  1. Click "Link Dev Extension"
  2. Select your extension folder (e.g., D:\dev\my-extension)
  3. Your extension is instantly loaded with a DEV badge!

Test and Iterate

  • Type your trigger keyword to test
  • Edit your code and save
  • Press Ctrl+R to refresh Volt
  • Changes are immediately visible!

Dev Mode Commands:

CommandDescription
Link Dev ExtensionLink a local folder
Unlink Dev ExtensionRemove a dev extension
Refresh Dev ExtensionReload from disk

Hot Reload

Dev extensions automatically re-read manifest.json on each query, so metadata changes are instant. For code changes, use Refresh or Ctrl+R.

Method 2: Install from ZIP

For testing the final packaged version:

Package Your Extension

Create a ZIP file with your extension files:

Compress-Archive -Path .\* -DestinationPath ..\my-extension-v1.0.0.zip
zip -r ../my-extension-v1.0.0.zip .

Install in Volt

  1. Open Volt
  2. Go to SettingsExtensions
  3. Click Install from file
  4. Select your ZIP file

Test Your Extension

Type your trigger keyword in Volt to test the extension.

Method 3: Direct Folder Installation

For manual installation (advanced):

Find Extension Directory

Volt stores extensions in the app data directory:

%APPDATA%\volt\extensions\
~/Library/Application Support/volt/extensions/
~/.config/volt/extensions/

Copy Your Extension

Copy your extension folder directly:

# Example for Windows
cp -r ./my-extension "$env:APPDATA/volt/extensions/"

Update installed.json

Add your extension to installed.json in the extensions directory:

{
  "extensions": [
    {
      "id": "my-extension",
      "name": "My Extension",
      "version": "1.0.0",
      "enabled": true,
      "installedAt": "2024-01-01T00:00:00Z"
    }
  ]
}

Reload Volt

Restart Volt or use the reload command to load your extension.

Debugging

Browser DevTools

Volt runs in a Tauri webview, which supports Chrome DevTools:

Enable DevTools

In development builds, press F12 or Ctrl+Shift+I to open DevTools.

View Console Output

Your console.log() statements will appear in the Console tab.

canHandle(context: PluginContext): boolean {
  console.log('Query received:', context.query);
  return context.query.startsWith('my ');
}

Debug Errors

Check the Console for any errors in your extension code.

Common Debugging Techniques

class MyPlugin implements Plugin {
  // ...

  canHandle(context: PluginContext): boolean {
    // Debug: Log every query
    console.log("[MyPlugin] canHandle called with:", context.query);

    const result = context.query.startsWith("my ");
    console.log("[MyPlugin] canHandle result:", result);

    return result;
  }

  match(context: PluginContext): PluginResult[] {
    console.log("[MyPlugin] match called");

    try {
      // Your matching logic
      const results = this.generateResults(context);
      console.log("[MyPlugin] Generated results:", results);
      return results;
    } catch (error) {
      console.error("[MyPlugin] Error in match:", error);
      return [];
    }
  }

  async execute(result: PluginResult): Promise<void> {
    console.log("[MyPlugin] execute called with:", result);

    try {
      // Your execution logic
      await this.performAction(result);
      console.log("[MyPlugin] Action completed successfully");
    } catch (error) {
      console.error("[MyPlugin] Error in execute:", error);
      window.VoltAPI.notify("An error occurred", "error");
    }
  }
}

Extension Loading Process

Understanding how Volt loads extensions helps with debugging:

1. Volt starts

2. Backend reads installed.json

3. For each enabled extension:
   a. Read all source files (.ts, .js, .json)
   b. Bundle files into single JavaScript
   c. Transform TypeScript → JavaScript (via Sucrase)
   d. Transform imports/exports to module system
   e. Execute the bundle
   f. Get default export (your Plugin class)
   g. Instantiate and register with Plugin Registry

4. Extension ready to handle queries

Key Points

TypeScript Transformation

Your TypeScript is automatically compiled to JavaScript. You don't need a build step. However, only TypeScript syntax is supported—not Node.js APIs.

  • Entry point: The file specified in manifest.main
  • Default export: Must be a class implementing Plugin
  • No Node.js: Browser APIs only (no fs, path, etc.)
  • Web Crypto: Use crypto.getRandomValues() instead of Node's crypto

Troubleshooting

Development Tips

1. Start Simple

Begin with a minimal extension and add features incrementally:

class MinimalPlugin implements Plugin {
  id = "minimal";
  name = "Minimal";
  description = "A minimal extension";
  enabled = true;

  canHandle(context: PluginContext): boolean {
    return context.query === "test";
  }

  match(context: PluginContext): PluginResult[] {
    return [
      {
        id: "1",
        type: window.VoltAPI.types.PluginResultType.Info,
        title: "It works!",
        score: 100,
      },
    ];
  }

  execute(): void {
    window.VoltAPI.notify("Executed!", "success");
  }
}

export default MinimalPlugin;

2. Use Descriptive Logging

const LOG_PREFIX = "[MyExtension]";

console.log(`${LOG_PREFIX} Initializing...`);
console.log(`${LOG_PREFIX} Query: "${context.query}"`);
console.error(`${LOG_PREFIX} Error:`, error);

3. Handle Errors Gracefully

match(context: PluginContext): PluginResult[] {
  try {
    return this.generateResults(context);
  } catch (error) {
    console.error('Error generating results:', error);
    return [{
      id: 'error',
      type: window.VoltAPI.types.PluginResultType.Info,
      title: 'An error occurred',
      subtitle: 'Check console for details',
      icon: '⚠️',
      score: 0
    }];
  }
}

4. Test Edge Cases

  • Empty query
  • Very long queries
  • Special characters
  • Unicode input
  • Rapid typing (debouncing)

Using the Extension Templates

Clone the official templates for a quick start:

# Clone the volt-extensions repository
git clone https://github.com/VoltLaunchr/volt-extensions.git

# Copy a template
cp -r volt-extensions/templates/typescript-plugin ./my-extension

# Start developing
cd my-extension

The template includes:

  • Pre-configured manifest.json
  • Type definitions
  • Example plugin structure
  • README template

Next Steps

On this page