tmonier / sylius-command-palette-plugin
Command palette (Ctrl+K / Cmd+K global search) for the Sylius 2 admin panel: orders, customers, products, promotions and admin pages
Package info
github.com/thibmonier/sylius-command-palette-plugin
Type:sylius-plugin
pkg:composer/tmonier/sylius-command-palette-plugin
Requires
- php: ^8.2
- sylius/sylius: ^2.2
Requires (Dev)
- nyholm/psr7: ^1.8
- phpstan/phpstan: ^2.1
- phpstan/phpstan-doctrine: ^2.0
- phpstan/phpstan-webmozart-assert: ^2.0
- phpunit/phpunit: ^10.5
- sylius-labs/coding-standard: ^4.4
- sylius/test-application: ^2.0.0@alpha
- symfony/browser-kit: ^6.4 || ^7.4 || ^8.0
- symfony/css-selector: ^6.4 || ^7.4 || ^8.0
- symfony/debug-bundle: ^6.4 || ^7.4 || ^8.0
- symfony/dom-crawler: ^6.4 || ^7.4 || ^8.0
- symfony/dotenv: ^6.4 || ^7.4 || ^8.0
- symfony/http-client: ^6.4 || ^7.4 || ^8.0
- symfony/intl: ^6.4 || ^7.4 || ^8.0
- symfony/runtime: ^6.4 || ^7.4 || ^8.0
- symfony/web-profiler-bundle: ^6.4 || ^7.4 || ^8.0
- symfony/webpack-encore-bundle: ^2.2
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-10 20:02:39 UTC
README
A command palette (Ctrl+K / ⌘K) for the Sylius 2 back office: find any order, customer, product, promotion or admin page from anywhere.
The Sylius 2 admin has no global search: the navbar field only searches products, the sidebar field only filters menu items, and there is no keyboard shortcut. Finding "order 1042" or "the customer who called" means opening the right grid and filtering it first.
This plugin adds a keyboard-first search dialog to every admin page, free and open source.
Features
| Need | What the plugin does |
|---|---|
| Open an order from its number | Orders: exact number match first, typed as 1042, #1042 or 000001042, then the latest orders of customers whose e-mail or last name starts with the query. Subtitle: customer, total, state; badge: payment state. Carts are never listed. |
| Find a customer | Customers: e-mail, first name, last name or phone number prefix, and "first last" (john do). Subtitle: e-mail and group. |
| Jump to a product or a variant | Products: name in the administrator's language (contains) or code (prefix), then variants by code, linked to the product / variant edit page. Disabled products are flagged. |
| Edit a promotion | Promotions by name or code, coupons by code (linked to the coupons page of their promotion). |
| Navigate without the sidebar | Pages: every page of the admin main menu (including the items added by other plugins), with its section, and actions (new product, taxon, customer, promotion…). Filtered instantly in the browser from 1 character. |
| Search other data | Your own sources: tag a service implementing SearchProviderInterface. |
Also included: keyboard navigation, accessible dialog (combobox / listbox pattern, focus trap, focus restored on close), light and dark themes, translations in English, French, German, Spanish, Italian, Dutch and Polish, no build step and no JavaScript dependency.
Screenshots
Requirements
| Version | |
|---|---|
| PHP | 8.2+ |
| Sylius | 2.2, 2.3 |
| Symfony | 6.4, 7.4, 8.0 |
Installation
-
Require the package:
composer require tmonier/sylius-command-palette-plugin
-
Register the bundle in
config/bundles.php(Symfony Flex does it automatically once a recipe exists):Tmonier\SyliusCommandPalettePlugin\TmonierSyliusCommandPalettePlugin::class => ['all' => true],
-
Import the configuration in
config/packages/tmonier_sylius_command_palette.yaml:imports: - { resource: "@TmonierSyliusCommandPalettePlugin/config/config.yaml" }
-
Import the admin route in
config/routes/tmonier_sylius_command_palette.yaml:tmonier_sylius_command_palette: resource: "@TmonierSyliusCommandPalettePlugin/config/routes.yaml"
-
Install the assets (a JavaScript and a CSS file, nothing to build):
bin/console assets:install
No migration: the plugin has no table.
Configuration
# config/packages/tmonier_sylius_command_palette.yaml tmonier_sylius_command_palette: limit_per_group: 5 # results per group (1 to 50) icons: [] # extra UX Icons names used by your own providers providers: orders: true customers: true products: true # products and variants promotions: true # promotions and promotion coupons pages: true # admin menu pages and "create" actions
Keyboard shortcuts
| Keys | Action |
|---|---|
| Ctrl+K / ⌘+K | Open / close the palette (from anywhere, even in a form field) |
| / | Open the palette when the focus is not in a form field |
| ↑ ↓ | Move between results |
| Enter | Open the selected result |
| Ctrl+Enter / ⌘+Enter | Open the selected result in a new tab |
| Esc | Close the palette and return to where you were |
The Search… ⌘K button in the navbar opens it too. The Sylius product search field is kept.
Add your own provider
Implement SearchProviderInterface and tag the service with tmonier_command_palette.provider.
The priority sets the position of the group (built-in: orders 500, customers 400, products 300,
promotions 200, coupons 100).
namespace App\CommandPalette; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Tmonier\SyliusCommandPalettePlugin\Search\LikePattern; use Tmonier\SyliusCommandPalettePlugin\Search\SearchProviderInterface; use Tmonier\SyliusCommandPalettePlugin\Search\SearchResult; final class ShipmentSearchProvider implements SearchProviderInterface { public function __construct( private EntityManagerInterface $entityManager, private UrlGeneratorInterface $urlGenerator, ) { } public function getKey(): string { return 'shipments'; // group label: translation "tmonier_command_palette.group.shipments" } /** $query is trimmed and 2 to 100 characters long. */ public function search(string $query, int $limit): array { $rows = $this->entityManager->createQueryBuilder() ->select('s.id AS id', 's.tracking AS tracking', 'o.number AS number') ->from(\App\Entity\Shipping\Shipment::class, 's') ->innerJoin('s.order', 'o') ->andWhere(sprintf('LOWER(s.tracking) LIKE :prefix %s', LikePattern::DQL_ESCAPE)) ->setParameter('prefix', LikePattern::prefix($query)) // escapes % and _ ->setMaxResults($limit) ->getQuery() ->getArrayResult(); return array_map(fn (array $row): SearchResult => new SearchResult( title: $row['tracking'], subtitle: '#' . $row['number'], url: $this->urlGenerator->generate('sylius_admin_shipment_index'), icon: 'tabler:truck', ), $rows); } }
# config/services.yaml services: App\CommandPalette\ShipmentSearchProvider: arguments: ['@doctrine.orm.entity_manager', '@router'] tags: [{ name: tmonier_command_palette.provider, priority: 150 }] # config/packages/tmonier_sylius_command_palette.yaml tmonier_sylius_command_palette: icons: ['tabler:truck']
# translations/messages.en.yaml tmonier_command_palette: group: shipments: Shipments
Guidelines:
- Bind every user value as a parameter and escape LIKE wildcards with
LikePattern(escape(),prefix(),contains(), used withLikePattern::DQL_ESCAPE). - Select scalar columns (one query per group, no lazy loading per result) and honour
$limit. - Return URLs of admin pages: the endpoint is only available to administrators, but your provider decides what it exposes.
- Icons are UX Icons names rendered
once in the layout: add the ones not used by the admin menu to the
iconsoption (an unknown icon falls back to an arrow). - An exception thrown by a provider is logged (channel
tmonier_command_palette) and its group is skipped; the other groups are still returned.
Admin menu items added through the sylius.menu.admin.main event (or a menu builder) appear
in the Pages group automatically.
Customisation
The admin parts are added with Twig Hooks:
| Hook | Content |
|---|---|
sylius_admin.common.component.navbar.menu → tmonier_command_palette |
Search… ⌘K button (before the product search) |
sylius_admin.base#stylesheets → tmonier_command_palette |
Stylesheet |
sylius_admin.base#javascripts → tmonier_command_palette |
Dialog, icons and script (administrators only) |
Templates can be overridden in templates/bundles/TmonierSyliusCommandPalettePlugin/. To hide
the navbar button and keep the shortcuts:
sylius_twig_hooks: hooks: 'sylius_admin.common.component.navbar.menu': tmonier_command_palette: enabled: false
The search endpoint is GET /admin/command-palette/search?q=… (route
tmonier_command_palette_admin_search), returning
{"query": "…", "groups": [{"key", "label", "results": [{"title", "subtitle", "url", "icon", "badge"}]}]}.
How it works
- Server-side search for orders, customers, products and promotions: requests are debounced (200 ms), the previous one is aborted, queries shorter than 2 characters are not sent and queries are cut to 100 characters (in the browser and on the server). Each provider runs one or two scalar queries with bound parameters, whatever the number of results.
- Client-side pages: the admin menu and the actions are rendered once in the page as JSON and filtered in the browser, so they appear on the first keystroke.
- Plain JavaScript, no build:
command-palette.js(an IIFE, no dependency) andcommand-palette.cssare installed byassets:installinpublic/bundles/tmoniersyliuscommandpaletteplugin/. Every value is rendered withtextContent; icons are cloned from<template>elements rendered by Twig. - Security: the endpoint is behind the admin firewall (
ROLE_ADMINISTRATION_ACCESS, checked again in the controller), responses areprivate, no-store. An expired session shows a "reload the page" message in the palette.
Limitations
- Matching uses
LIKE(prefix for codes, e-mails and names, contains for product and promotion names), not a full-text index: on very large catalogues the "contains" searches scan the translation tables. Plug a provider backed by your search engine if needed. - Product names are searched in the administrator's locale only.
- Order numbers are matched exactly; the leading-zero shortcut assumes the default Sylius padding (9 digits).
- Sylius Plus RBAC is not integrated: every administrator can find every resource (the pages they open still apply their own permissions).
- Catalog promotions, shipments, payments and taxons are not searched in v1 (only reachable as pages / actions).
Development
docker compose up -d mysql # or your own MySQL composer install (cd vendor/sylius/test-application && yarn install && yarn build) vendor/bin/console assets:install vendor/bin/console doctrine:database:create vendor/bin/console doctrine:migrations:migrate -n vendor/bin/console sylius:fixtures:load -n vendor/bin/phpunit --testsuite unit APP_ENV=test vendor/bin/phpunit --testsuite non-unit # needs a test database with fixtures vendor/bin/phpstan analyse vendor/bin/ecs check node --check Resources/public/command-palette.js
License
MIT. See LICENSE.
