# How to Add Custom Commands to Folia's Command Palette: A Complete Guide

> Easily add custom commands to Folia's command palette. This guide shows you how to extend Folia by modifying the COMMAND_PALETTE_COMMANDS array for seamless integration.

- Repository: [冬霧/folia-major](https://github.com/chthollyphile/folia-major)
- Tags: how-to-guide
- Published: 2026-07-06

---

**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`](https://github.com/chthollyphile/folia-major/blob/main/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`](https://github.com/chthollyphile/folia-major/blob/main/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`](https://github.com/chthollyphile/folia-major/blob/main/useCommandPalette.ts)**. When users type queries, the system calls **`getCommandPaletteMatches`** (also exported from [`commandRegistry.ts`](https://github.com/chthollyphile/folia-major/blob/main/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`](https://github.com/chthollyphile/folia-major/blob/main/src/components/command-palette/types.ts):

- **`id`**: Unique identifier used for persistence and recent-command tracking.
- **`group`**: Category grouping (`search`, `settings`, `navigation`, `panel`, `playback`, or `visualizer`).
- **`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 returning `boolean | 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:

```typescript
// 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`:

```typescript
// 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:

```typescript
// 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`](https://github.com/chthollyphile/folia-major/blob/main/test/unit/command-palette/commandRegistry.test.ts)**. When adding custom commands, update this suite to verify:

- The command appears in the registry array.
- The `execute` function handles edge cases (invalid input, missing context methods).
- `getPreview` returns expected strings for given inputs.

Testing ensures your command integrates correctly with the palette's matching and execution logic.

## Summary

- **Register commands** by appending `CommandPaletteCommand` objects to the `COMMAND_PALETTE_COMMANDS` array in [`src/components/command-palette/commandRegistry.ts`](https://github.com/chthollyphile/folia-major/blob/main/src/components/command-palette/commandRegistry.ts).
- **Define metadata** including `id`, `group`, `title`, `description`, and `keywords` for search indexing.
- **Handle input** by setting `requiresInput: true` and implementing `getPreview` for live feedback.
- **Access context** through the `execute` function's `ctx` parameter to interact with UI state or services.
- **Leverage factories** like `createSettingsCommand` for standard command patterns.
- **Test changes** in [`test/unit/command-palette/commandRegistry.test.ts`](https://github.com/chthollyphile/folia-major/blob/main/test/unit/command-palette/commandRegistry.test.ts) to 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`](https://github.com/chthollyphile/folia-major/blob/main/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.