How to Add Custom Commands to Folia's Command Palette: A Complete Guide
You can extend Folia's command palette by appending a CommandPaletteCommand object to the COMMAND_PALETTE_COMMANDS array exported from src/components/command-palette/commandRegistry.ts, which the palette UI consumes automatically via the useCommandPalette hook.
Folia's command palette is built around a centralized registry pattern that separates command definitions from UI rendering. By modifying the exported command array in the registry module, developers can inject new actions—ranging from simple toggles to complex input-driven operations—without altering the core fuzzy-search interface or match-scoring logic.
Understanding the Command Registry Architecture
The command palette relies on a centralized registry defined in src/components/command-palette/commandRegistry.ts. This file exports the constant COMMAND_PALETTE_COMMANDS, which serves as the single source of truth for all available actions.
The palette UI consumes this registry through useCommandPalette.ts. When users type queries, the system calls getCommandPaletteMatches (also exported from commandRegistry.ts) to filter and score commands based on keyword matches.
The CommandPaletteCommand Interface
Each command must implement the interface defined in src/components/command-palette/types.ts:
id: Unique identifier used for persistence and recent-command tracking.group: Category grouping (search,settings,navigation,panel,playback, orvisualizer).title: Human-readable name displayed in the UI.description: Tooltip text shown on hover.keywords: Array of search terms that trigger the command, including translations.placeholder?: Hint text displayed when the command expects user input.requiresInput?: Boolean indicating whether the command needs arguments.getPreview?: Function returning live preview text while the user types.execute: Function receiving(input: string, ctx: CommandPaletteContext)and returningboolean | Promise<boolean>.
The execute function receives the current user input (empty string if requiresInput is false) and the CommandPaletteContext, which exposes UI control methods like setIsUserGuideModalOpen or toggleTransparentBackground.
Step-by-Step Implementation
Minimal Inline Command
For actions requiring no user input, append an object directly to the exported array:
// src/components/command-palette/commandRegistry.ts
export const COMMAND_PALETTE_COMMANDS: CommandPaletteCommand[] = [
// ...existing commands...
{
id: 'custom-toggle-foo',
group: 'settings',
title: 'Toggle Foo Feature',
description: 'Enable or disable the experimental Foo mode',
keywords: ['foo', 'toggle foo', 'experimental'],
execute: (_input, ctx) => {
ctx.toggleFooMode?.();
return true;
},
},
];
This command appears under the Settings section and executes immediately when selected.
Input-Enabled Commands
For commands requiring arguments, set requiresInput: true and implement getPreview:
// src/components/command-palette/commandRegistry.ts
const createGoToTimestampCommand = (): CommandPaletteCommand => ({
id: 'goto-timestamp',
group: 'navigation',
title: 'Go to timestamp',
description: 'Jump to a specific time (seconds) in the current track',
keywords: ['timestamp', 'seek', 'jump'],
placeholder: 'Enter seconds, e.g. 42',
requiresInput: true,
getPreview: (input) => `Will jump to ${input}s`,
execute: (input, ctx) => {
const seconds = Number(input);
if (Number.isNaN(seconds)) return false;
ctx.seekTo?.(seconds);
return true;
},
});
export const COMMAND_PALETTE_COMMANDS: CommandPaletteCommand[] = [
// ...existing commands...
createGoToTimestampCommand(),
];
The palette displays a text field when this command is selected, updating the preview dynamically as the user types.
Reusing Factory Functions
The registry includes helper factories like createSettingsCommand for consistent settings-related commands:
// src/components/command-palette/commandRegistry.ts
const customSettingsCommand = createSettingsCommand(
'settings-custom-widget',
'Custom Widget',
'Open the custom widget configuration panel',
['custom', 'widget', 'configure'],
'options',
'customWidget' // SettingsSubviewId from useSettingsUiStore
);
export const COMMAND_PALETTE_COMMANDS: CommandPaletteCommand[] = [
// ...existing commands...
customSettingsCommand,
];
Using existing factories ensures consistent behavior and reduces boilerplate when adding settings sub-views.
Testing Your Custom Commands
The repository includes unit tests at test/unit/command-palette/commandRegistry.test.ts. When adding custom commands, update this suite to verify:
- The command appears in the registry array.
- The
executefunction handles edge cases (invalid input, missing context methods). getPreviewreturns expected strings for given inputs.
Testing ensures your command integrates correctly with the palette's matching and execution logic.
Summary
- Register commands by appending
CommandPaletteCommandobjects to theCOMMAND_PALETTE_COMMANDSarray insrc/components/command-palette/commandRegistry.ts. - Define metadata including
id,group,title,description, andkeywordsfor search indexing. - Handle input by setting
requiresInput: trueand implementinggetPreviewfor live feedback. - Access context through the
executefunction'sctxparameter to interact with UI state or services. - Leverage factories like
createSettingsCommandfor standard command patterns. - Test changes in
test/unit/command-palette/commandRegistry.test.tsto maintain reliability.
Frequently Asked Questions
Can I add commands without modifying the core registry file?
Currently, Folia requires commands to be added directly to the COMMAND_PALETTE_COMMANDS array in src/components/command-palette/commandRegistry.ts. There is no dynamic plugin API exposed at runtime; all commands must be defined at build time within the registry module.
What happens if two commands have the same ID?
The palette uses the id property for persistence and recent-command tracking. Duplicate IDs will cause collisions in the recent commands list and may lead to unpredictable behavior in the UI. Always ensure your command IDs are unique across the entire registry.
How do I make a command available only in specific contexts?
The execute function receives the full CommandPaletteContext, which you can use to conditionally disable commands. Return false from execute if the required context is unavailable, or check context properties before performing actions. However, the palette currently shows all registered commands regardless of context; filtering visibility requires modifying the getCommandPaletteMatches function.
Can commands return Promises?
Yes, the execute function signature allows returning boolean | Promise<boolean>. This is useful for asynchronous operations like API calls or delayed UI updates. The palette will handle the promise resolution appropriately, though the UI does not currently show loading states for pending executions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →