Examples

Common patterns and integration examples for cmdk-engine.

React Router Integration

Auto-discover routes from your React Router config and register them as commands.

App.tsx
import { CommandEngineProvider, useCommandRegister } from 'cmdk-engine/react'
import { CommandPalette } from 'cmdk-engine/adapters/cmdk'
import { scanRoutes } from 'cmdk-engine/adapters/react-router'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'

const routes = [
  {
    path: '/dashboard',
    element: <Dashboard />,
    handle: {
      command: { label: 'Dashboard', group: 'Navigation' }
    }
  },
  {
    path: '/billing',
    element: <Billing />,
    handle: {
      command: {
        label: 'Billing',
        keywords: ['payment', 'invoice'],
        group: 'Navigation'
      }
    }
  },
]

const router = createBrowserRouter(routes)
// Scan with options — exclude paths, skip dynamic routes by default
const commands = scanRoutes(routes, {
  exclude: ['/admin/*'],  // string, glob, or regex
})

function App() {
  return (
    <CommandEngineProvider config={{
      onSelect: (item) => {
        if (item.href) navigate(item.href)
      },
      frecency: { showRecent: true },
    }}>
      <RegisterRoutes />
      <CommandPalette dialog />
      <RouterProvider router={router} />
    </CommandEngineProvider>
  )
}

function RegisterRoutes() {
  useCommandRegister(commands)
  return null
}

With RBAC

Filter commands based on user permissions. Commands with restricted permissions are automatically hidden.

tsx
import { createSimpleAccessProvider } from 'cmdk-engine'
import { CommandEngineProvider } from 'cmdk-engine/react'

// User permissions from your auth system
const userPermissions = ['billing.read', 'settings.read']

function App() {
  return (
    <CommandEngineProvider config={{
      accessControl: createSimpleAccessProvider(userPermissions),
      accessCheckMode: 'any',
    }}>
      <YourApp />
    </CommandEngineProvider>
  )
}

// Register a command that requires admin permission
useCommandRegister([{
  id: 'admin-panel',
  label: 'Admin Panel',
  permissions: ['admin.access'], // hidden from non-admin users
  href: '/admin',
}])

Custom UI (without cmdk)

Build a fully custom command palette UI using only the headless hooks. Use groupedResults for pre-grouped items and select() as a one-call handler.

tsx
import { useCommandPalette } from 'cmdk-engine/react'

function CustomPalette() {
  const { search, setSearch, groupedResults, isOpen, toggle, select } =
    useCommandPalette()

  if (!isOpen) return null

  return (
    <div className="palette-overlay" onClick={() => toggle()}>
      <div className="palette-content" onClick={e => e.stopPropagation()}>
        <input
          value={search}
          onChange={e => setSearch(e.target.value)}
          placeholder="Search..."
          autoFocus
        />
        {groupedResults.map(({ group, items }) => (
          <div key={group.id}>
            <h3>{group.label}</h3>
            <ul>
              {items.map(({ item }) => (
                <li key={item.id} onClick={() => select(item)}>
                  {item.icon && <span>{item.icon}</span>}
                  <span>{item.label}</span>
                  {item.description && (
                    <span className="desc">{item.description}</span>
                  )}
                </li>
              ))}
            </ul>
          </div>
        ))}
        {groupedResults.length === 0 && (
          <p className="empty">No results</p>
        )}
      </div>
    </div>
  )
}

CLI: Pre-commit Hook

Auto-scan routes on every commit to keep your command sitemap up to date.

bash
# Install husky
npx husky install

# Add pre-commit hook
npx husky add .husky/pre-commit "npx cmdk-engine scan && git add src/generated/command-routes.json"

Or with lint-staged in package.json:

package.json
{
  "lint-staged": {
    "src/routes/**/*.{ts,tsx}": [
      "npx cmdk-engine scan",
      "git add src/generated/command-routes.json"
    ]
  }
}