Command Palette
Contribute searchable destinations and commands while governed Resource Search supplies authorized record results.
Make an App screen discoverable with Cmd+K. A searchable Add a menu destination already supplies this behavior; it needs no second registration.
Publish and find the destination
Set searchable: true on the existing navigation entry (the default), use a translated labelKey, and retain its read permission. Refresh discovery after adding a contribution class, then open the palette with an authorized actor and search the localized screen name.
Confirm the result
Selecting the result opens the registered App screen. An actor without its permission does not discover it, and direct API access remains protected. searchable: false suppresses search without granting or revoking authority.
For record results, publish ordinary Resource metadata and searchable model fields through Create a Resource; Core performs governed candidate retrieval and reauthorization. Use SDK PaletteCommandContribution only for an executable command, and the locate helper below for agent navigation.
Available contribution paths
| Capability | Available to an App Package | Mechanism |
|---|---|---|
| Destination search | Yes | searchable on a navigation entry |
Record search (find Order #123) | Through a published Resource | Core governed Search over eligible Resource models |
| Executable commands | Yes | SDK PaletteCommandContribution |
| Agent locate actions | Yes | ShellNavigationLocateAction |
Import executable-command contracts from
Nexia\Palette.App\Palette\*remains a Core implementation namespace and is not an App Package boundary. There is no App-owned Palette record-search contribution.
Record hits come from CoreSearchService, the same governed Search runtime used
by the expanded Search surface. It selects eligible Resource models, asks the
configured candidate gateway only for identifiers, then reloads those records
from the tenant database and applies current visibility and Policy checks. An
App's part is to publish its normal Resource contract and searchable model
fields. Core owns eligibility, App installation checks, and final authorization.
NexiaEntityModel already uses UsesFilterableScoutSearch;
fields marked searchable: true in resourceListFields() are the source
for its Search projection. External
indexing remains fail-closed behind Core's Resource allowlist. Do not create a
second Palette-specific query or return display data directly from an external
engine.
Use PaletteCommand::make() for command payloads. Do not declare an App Family,
App key, or hierarchy on individual commands; Core derives ownership from the
registered contribution location.
Result grouping
Core returns an explicit placement on every navigation, command, and entity
result. Clients consume that field directly rather than inferring ownership from
an id, route, context, or the current result set.
| Result origin | Rendered as |
|---|---|
| App-owned | App Family → App → destination, command, or record |
| Host | Localized Platform categories |
Your App contributes app_family membership and in-family launcher_order in
composer.json. Core contributes the Family label and Family order through
AppFamilyCatalog, shared with the App Launcher and App Catalog. The App's
overview destination is identified by overview_navigation_id, so it remains
first even when a search result contains only part of the App catalog.
Agent locate actions
A locate action lets an agent navigate to a list surface and pre-apply search,
filters, and sort. In an existing NavigationContribution, use
Nexia\Navigation\Concerns\ContributesNavigationDestinationActions and import
Nexia\Navigation\ShellNavigationLocateAction. The following method excerpt
belongs inside that class; the trait exposes it as agentNavigationActions():
protected static function agentNavigationActionDefinitions(): array
{
return [
ShellNavigationLocateAction::make(
action: 'assets.asset.locate',
url: '/apps/assets/assets',
permission: 'assets.asset.read',
),
];
}The helper supplies these defaults; the App still owns the correct route and permission:
| Field | Fixed value |
|---|---|
intent | locate |
effect | read_only |
requires_user_submit | false |
input_schema | q (free text), filters (declared keys only), sort (prefix - for descending) |
The action carries label_key and description_key built from the
agent-navigation i18n prefix — catalog keys, not copy.
It is read-only by construction. A locate action cannot mutate anything, and
the permission still gates it. filters accepts declared filter keys only — read
the registered locate action schema for the exact keys and option values.
Boundaries
searchable is indexing, not authorization. A palette result still respects the
destination's permission gate; an actor without it never sees the entry. Setting
searchable: false is a noise decision, not a security control.
Only permission-visible entries are indexed. The palette indexes the filtered stream, so it cannot reveal the existence of a destination an actor may not use.
Destination labels come from the locale catalog. A hard-coded label is what the
operator searches against in every locale — pass an exact catalog key as labelKey.
Workbench hierarchy stays flat so the backend can permission-filter every child independently and the palette can index every destination. Do not model depth you do not need.
The locate-action excerpt follows packages/assets/src/Contribution/Resources/AssetModule.php. Exact executable command fields are defined in packages/app-sdk/packages/laravel/src/Palette/PaletteCommand.php.