Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ const getTransformCommands = () =>
/* translators: %s: Block or block variation name. */
label: sprintf( __( 'Transform to %s' ), title ),
icon: blockIcon?.src,
category: 'command',
callback: ( { close } ) => {
onBlockTransform( name );
close();
Expand Down Expand Up @@ -326,6 +327,7 @@ const getQuickActionsCommands = () =>
commands: commands.map( ( command ) => ( {
...command,
name: 'core/block-editor/action-' + command.name,
category: 'command',
callback: ( { close } ) => {
command.callback();
close();
Expand Down
2 changes: 2 additions & 0 deletions packages/commands/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Add `category` property to command config, used to visually differentiate commands in the Command Palette.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a category like ### Enhancements

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do that in the follow up


## 1.39.0 (2026-01-29)

## 1.38.0 (2026-01-16)
Expand Down
16 changes: 16 additions & 0 deletions packages/commands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ There are two ways to register commands: static or dynamic. Both methods receive
- `label`: A human-readable label
Comment thread
senadir marked this conversation as resolved.
- `icon`: An SVG icon
- `callback`: A callback function that is called when the command is selected
- `category`: (Optional) The category of the command. See [Command categories](#command-categories) below
- `context`: (Optional) The context of the command
- `keywords`: (Optional) An array of keywords for search matching

Expand Down Expand Up @@ -37,6 +38,17 @@ As the usage of the Command Palette expands, more contexts will be added.

Attaching a command or command loader to a given context is as simple as adding the `context` property (with the right context value from the available contexts above) to the `useCommand` or `useCommandLoader` calls.

## Command categories

Each command can be assigned a `category` that describes what kind of action it performs. Categories are used by the Command Palette to visually differentiate commands. The following categories are available:

- `command`: Executes code or toggles a command (e.g., Add block, duplicating a block).
- `view`: Navigates to an area in the admin or opens a panel (e.g., "Go to: Templates").
- `edit`: Navigates to edit a document (e.g., editing a template, editing a page).
- `action`: A generic fallback for commands that don't fit the other categories. This is also the default when an invalid category is provided.

If no `category` is specified, the command will have `action` set. If an invalid value is provided, a warning is emitted in development mode and the category defaults to `action`.

## WordPress Data API

The Command Palette also offers a number of [selectors and actions](https://developer.wordpress.org/block-editor/reference-guides/data/data-core-commands/) to manipulate its state, which include:
Expand Down Expand Up @@ -104,6 +116,7 @@ useCommand( {
name: 'myplugin/my-command-name',
label: __( 'Add new post' ),
icon: plus,
category: 'command',
callback: ( { close } ) => {
document.location.href = 'post-new.php';
close();
Expand Down Expand Up @@ -160,6 +173,7 @@ function usePageSearchCommandLoader( { search } ) {
? record.title?.rendered
: __( '(no title)' ),
icon: page,
category: 'edit',
callback: ( { close } ) => {
const args = {
p: '/page',
Expand Down Expand Up @@ -203,6 +217,7 @@ useCommands( [
name: 'myplugin/add-post',
label: __( 'Add new post' ),
icon: plus,
category: 'command',
callback: ( { close } ) => {
document.location.href = 'post-new.php';
close();
Expand All @@ -212,6 +227,7 @@ useCommands( [
name: 'myplugin/edit-posts',
label: __( 'Edit posts' ),
icon: pencil,
category: 'view',
callback: ( { close } ) => {
document.location.href = 'edit.php';
close();
Expand Down
1 change: 1 addition & 0 deletions packages/commands/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@wordpress/icons": "file:../icons",
"@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts",
"@wordpress/private-apis": "file:../private-apis",
"@wordpress/warning": "file:../warning",
"clsx": "^2.1.1",
"cmdk": "^1.0.0"
},
Expand Down
1 change: 1 addition & 0 deletions packages/commands/src/hooks/use-command-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { store as commandsStore } from '../store';
* ? record.title?.rendered
* : __( '(no title)' ),
* icon: page,
* category: 'edit',
* callback: ( { close } ) => {
* const args = {
* p: '/page',
Expand Down
6 changes: 6 additions & 0 deletions packages/commands/src/hooks/use-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { store as commandsStore } from '../store';
* name: 'myplugin/my-command-name',
* label: __( 'Add new post' ),
* icon: plus,
* category: 'command',
* callback: ({ close }) => {
* document.location.href = 'post-new.php';
* close();
Expand All @@ -44,6 +45,7 @@ export function useCommand( command ) {
registerCommand( {
name: command.name,
context: command.context,
category: command.category,
label: command.label,
searchLabel: command.searchLabel,
icon: command.icon,
Expand All @@ -59,6 +61,7 @@ export function useCommand( command ) {
command.searchLabel,
command.icon,
command.context,
command.category,
command.keywords,
command.disabled,
registerCommand,
Expand All @@ -81,6 +84,7 @@ export function useCommand( command ) {
* name: 'myplugin/add-post',
* label: __( 'Add new post' ),
* icon: plus,
* category: 'command',
* callback: ({ close }) => {
* document.location.href = 'post-new.php';
* close();
Expand All @@ -90,6 +94,7 @@ export function useCommand( command ) {
* name: 'myplugin/edit-posts',
* label: __( 'Edit posts' ),
* icon: pencil,
* category: 'view',
* callback: ({ close }) => {
* document.location.href = 'edit.php';
* close();
Expand Down Expand Up @@ -124,6 +129,7 @@ export function useCommands( commands ) {
registerCommand( {
name: command.name,
context: command.context,
category: command.category,
label: command.label,
searchLabel: command.searchLabel,
icon: command.icon,
Expand Down
43 changes: 35 additions & 8 deletions packages/commands/src/store/actions.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
/** @typedef {import('@wordpress/keycodes').WPKeycodeModifier} WPKeycodeModifier */

/**
* @typedef {'command'|'view'|'edit'|'workflow'|'action'} WPCommandCategory
*/

/**
* Command categories allowed via registerCommand.
* The 'workflow' category is reserved for internal use
* and cannot be registered through this API.
*
* @type {Set<WPCommandCategory>}
*/
const REGISTERABLE_CATEGORIES = new Set( [
'command',
'view',
'edit',
'action',
] );

/**
* Configuration of a registered keyboard shortcut.
*
* @typedef {Object} WPCommandConfig
*
* @property {string} name Command name.
* @property {string} label Command label.
* @property {string=} searchLabel Command search label.
* @property {string=} context Command context.
* @property {React.JSX.Element} icon Command icon.
* @property {Function} callback Command callback.
* @property {boolean} disabled Whether to disable the command.
* @property {string[]=} keywords Command keywords for search matching.
* @property {string} name Command name.
* @property {string} label Command label.
* @property {string=} searchLabel Command search label.
* @property {string=} context Command context.
* @property {WPCommandCategory=} category Command category.
* @property {React.JSX.Element} icon Command icon.
* @property {Function} callback Command callback.
* @property {boolean} disabled Whether to disable the command.
* @property {string[]=} keywords Command keywords for search matching.
*/

/**
Expand All @@ -38,9 +57,17 @@
* @return {Object} action.
*/
export function registerCommand( config ) {
let { category } = config;

// Defaults to 'action' if no category is provided or if the category is invalid. Future versions will emit a warning.
if ( ! category || ! REGISTERABLE_CATEGORIES.has( category ) ) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if there's no category, we shouldn't be showing a warning

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it was by design from @fcoveram that having no category is a mistake. I will move the warning to future versions if you agree.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that we already have a lot of commands in the community like that. I don't want to flood the console with warnings.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I think if I'm not wrong, we're not doing this validation for command loaders (which don't use registercommand if I'm not wrong)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that we already have a lot of commands in the community like that. I don't want to flood the console with warnings.

Then we should probably reach a decision for "Action".

Also I think if I'm not wrong, we're not doing this validation for command loaders (which don't use registercommand if I'm not wrong)

Missed that, will do it in the follow up

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what we ended up deciding here, but showing warnings is definitely not ideal. I still believe that every action should belong to a category, even if that one is "uncategorized"

category = 'action';
}

return {
type: 'REGISTER_COMMAND',
...config,
category,
};
}

Expand Down
1 change: 1 addition & 0 deletions packages/commands/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function commands( state = {}, action ) {
label: action.label,
searchLabel: action.searchLabel,
context: action.context,
category: action.category,
callback: action.callback,
icon: action.icon,
keywords: action.keywords,
Expand Down
2 changes: 2 additions & 0 deletions packages/core-commands/src/admin-navigation-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const getViewSiteCommand = () =>
name: 'core/view-site',
label: __( 'View site' ),
icon: external,
category: 'view',
callback: ( { close } ) => {
close();
window.open( homeUrl, '_blank' );
Expand All @@ -54,6 +55,7 @@ export function useAdminNavigationCommands( menuCommands ) {
name: menuCommand.name,
label,
searchLabel: label,
category: 'view',
callback: ( { close } ) => {
document.location = menuCommand.url;
close();
Expand Down
8 changes: 8 additions & 0 deletions packages/core-commands/src/site-editor-navigation-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ const getNavigationCommandLoaderPerPostType = ( postType ) =>
? decodeEntities( record.title?.rendered )
: __( '(no title)' ),
icon: icons[ postType ],
category: 'edit',
};

if (
Expand Down Expand Up @@ -260,6 +261,7 @@ const getNavigationCommandLoaderPerTemplate = ( templateType ) =>
? record.title?.rendered
: __( '(no title)' ),
icon: icons[ templateType ],
category: 'edit',
callback: ( { close } ) => {
if ( isSiteEditor ) {
history.navigate(
Expand Down Expand Up @@ -288,6 +290,7 @@ const getNavigationCommandLoaderPerTemplate = ( templateType ) =>
name: 'core/edit-site/open-template-parts',
label: __( 'Go to: Template parts' ),
icon: symbolFilled,
category: 'view',
callback: ( { close } ) => {
if ( isSiteEditor ) {
history.navigate(
Expand Down Expand Up @@ -346,6 +349,7 @@ const getSiteEditorBasicNavigationCommands = () =>
name: 'core/edit-site/open-styles',
label: __( 'Go to: Styles' ),
icon: styles,
category: 'view',
callback: ( { close } ) => {
if ( isSiteEditor ) {
history.navigate( '/styles' );
Expand All @@ -365,6 +369,7 @@ const getSiteEditorBasicNavigationCommands = () =>
name: 'core/edit-site/open-navigation',
label: __( 'Go to: Navigation' ),
icon: navigation,
category: 'view',
callback: ( { close } ) => {
if ( isSiteEditor ) {
history.navigate( '/navigation' );
Expand All @@ -384,6 +389,7 @@ const getSiteEditorBasicNavigationCommands = () =>
name: 'core/edit-site/open-templates',
label: __( 'Go to: Templates' ),
icon: layout,
category: 'view',
callback: ( { close } ) => {
if ( isSiteEditor ) {
history.navigate( mapRoute( '/template' ) );
Expand All @@ -405,6 +411,7 @@ const getSiteEditorBasicNavigationCommands = () =>
name: 'core/edit-site/open-patterns',
label: __( 'Go to: Patterns' ),
icon: symbol,
category: 'view',
callback: ( { close } ) => {
if ( canCreateTemplate ) {
if ( isSiteEditor ) {
Expand Down Expand Up @@ -470,6 +477,7 @@ const getGlobalStylesOpenCssCommands = () =>
name: 'core/open-styles-css',
label: __( 'Open custom CSS' ),
icon: brush,
category: 'view',
callback: ( { close } ) => {
close();

Expand Down
1 change: 1 addition & 0 deletions packages/edit-post/src/commands/use-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default function useCommands() {
? __( 'Exit fullscreen' )
: __( 'Enter fullscreen' ),
icon: fullscreen,
category: 'command',
callback: ( { close } ) => {
toggle( 'core/edit-post', 'fullscreenMode' );
close();
Expand Down
Loading
Loading