Best Practices
Tips and guidelines for creating great Volt plugins
Plugin Best Practices
Build high-quality plugins
Follow these best practices to create performant, reliable, and user-friendly Volt plugins.
Performance
Implement Efficient canHandle()
The canHandle() method is called for every query. Keep it fast:
canHandle(context: PluginContext): boolean {
return /^(search|find|lookup)\s+.+$/i.test(context.query);
}canHandle(context: PluginContext): boolean {
return context.query.startsWith('search ');
}Use Debouncing
For plugins that make API calls, debounce requests to avoid overwhelming the API:
import { debounce } from "lodash";
class MyPlugin implements Plugin {
private debouncedMatch = debounce(this.match.bind(this), 300);
async match(context: PluginContext): Promise<PluginResult[]> {
// Your implementation
return await this.fetchResults(context.query);
}
}300ms is a good balance between responsiveness and API efficiency.
Implement Caching
Cache results to improve performance and reduce API calls:
class MyPlugin implements Plugin {
private cache = new Map<string, PluginResult[]>();
private cacheTimeout = 5000; // 5 seconds
async match(context: PluginContext): Promise<PluginResult[]> {
const cached = this.cache.get(context.query);
if (cached) return cached;
const results = await this.fetchResults(context.query);
this.cache.set(context.query, results);
setTimeout(() => this.cache.delete(context.query), this.cacheTimeout);
return results;
}
}Respect Timeouts
Frontend plugins have a 500ms timeout. Ensure your plugin responds quickly:
async match(context: PluginContext): Promise<PluginResult[]> {
const timeout = new Promise<PluginResult[]>((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 450)
);
const results = this.fetchResults(context.query);
return Promise.race([results, timeout]).catch(() => []);
}Always handle timeout errors gracefully to prevent crashes.
Error Handling
User Experience
Provide Clear Titles and Subtitles
Make results easy to understand at a glance:
{
title: 'Result',
subtitle: 'Click to open',
}{
title: 'Search "React hooks" on Google',
subtitle: 'Opens google.com in your browser',
}Use Appropriate Icons
Choose icons that clearly represent your results:
// Use emojis for simplicity
icon: '🔍' // For search
icon: '📊' // For data/analytics
icon: '⚙️' // For settings
icon: '📁' // For files
icon: '🚀' // For launch/execute
// Or custom icon URLs
icon: 'https://example.com/icon.png'Emojis work great for quick prototyping. Use custom icons for professional plugins.
Provide Keyboard Shortcuts
Add keyboard shortcuts for common actions:
{
id: '1',
title: 'Search on Google',
subtitle: 'Press Enter to open',
shortcut: 'Enter',
action: () => window.open(url),
}Show Loading States
Indicate when results are loading to provide feedback:
async match(context: PluginContext): Promise<PluginResult[]> {
// Return loading state immediately
const loadingResult = {
id: 'loading',
title: 'Loading...',
icon: '⏳',
type: PluginResultType.Info,
};
// Fetch actual results
const results = await this.fetchResults(context.query);
return results.length > 0 ? results : [loadingResult];
}Security
Security is critical
Always follow security best practices to protect users and their data.
Code Quality
Use TypeScript
Always use TypeScript for type safety and better IDE support:
interface MyPluginConfig {
apiKey: string;
endpoint: string;
timeout: number;
}
class MyPlugin implements Plugin {
constructor(private config: MyPluginConfig) {}
}TypeScript helps catch errors early and provides better autocomplete.
Follow Naming Conventions
Use consistent naming throughout your plugin:
// Plugin ID: kebab-case
id = 'web-search';
// Class names: PascalCase
class WebSearchPlugin implements Plugin {}
// Methods: camelCase
async match(context: PluginContext) {}
// Constants: UPPER_SNAKE_CASE
const DEFAULT_TIMEOUT = 5000;Document Your Code
Add JSDoc comments for better maintainability:
/**
* Searches the web using multiple search engines
* @param query - The search query
* @returns Array of search results
*/
async search(query: string): Promise<SearchResult[]> {
// Implementation
}Keep Functions Small
Break down complex logic into smaller, focused functions:
async match(context: PluginContext): Promise<PluginResult[]> {
// 100 lines of code doing everything
}async match(context: PluginContext): Promise<PluginResult[]> {
const query = this.parseQuery(context.query);
const results = await this.fetchResults(query);
return this.formatResults(results);
}Testing
Test your plugins
Comprehensive testing ensures reliability and prevents regressions.
Test Edge Cases
Test with unusual and boundary inputs:
describe("CalculatorPlugin", () => {
it("handles empty input", () => {
const result = plugin.canHandle({ query: "" });
expect(result).toBe(false);
});
it("handles invalid expressions", () => {
const result = plugin.canHandle({ query: "1 / 0" });
expect(result).toBe(true);
});
it("handles very long input", () => {
const longQuery = "1+".repeat(1000) + "1";
const result = plugin.canHandle({ query: longQuery });
expect(result).toBeDefined();
});
});Mock External Dependencies
Use mocks for API calls to ensure consistent tests:
import { vi } from "vitest";
vi.mock("@tauri-apps/api/core", () => ({
invoke: vi.fn().mockResolvedValue({ data: [] }),
}));
// Test with mocked data
it("handles API responses", async () => {
const results = await plugin.match({ query: "test" });
expect(results).toHaveLength(0);
});Test Performance
Ensure your plugin meets the 500ms timeout requirement:
it("responds within timeout", async () => {
const start = Date.now();
await plugin.match({ query: "test" });
const duration = Date.now() - start;
expect(duration).toBeLessThan(500);
});Plugins exceeding 500ms will be terminated automatically.
Configuration
Accessibility
Make plugins accessible to everyone
Accessibility ensures all users can effectively use your plugin.
Documentation
Good documentation is essential
Well-documented plugins are easier to use, maintain, and contribute to.
Community
📬 Be Responsive
Respond to issues and pull requests promptly. Good communication builds trust.
🤝 Welcome Contributions
Make it easy for others to contribute with clear guidelines and a welcoming attitude.
💜 Follow Code of Conduct
Be respectful, inclusive, and professional in all interactions.
Contributing Example
## Contributing
We welcome contributions! Please:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing`)
5. Open a Pull RequestReady to build amazing plugins?
Following these best practices will help you create plugins that are fast, reliable, secure, and user-friendly. Happy coding! 🚀