Actions
9. Actions — The ⌘K Panel
Actions are keyboard-accessible commands that appear in Asyar's Action Drawer when the user presses ⌘K. They are contextual — what appears depends on where the user is and what your extension has registered.
There are two ways to contribute actions:
| Approach | When to use |
|---|---|
| Manifest-declared actions | Root search — appear when the user selects your command in the main launcher, before opening any view. Declared in manifest.json. |
| Programmatic actions | View-level — appear while your extension panel is open. Registered in code via actionService.registerAction(). |
What actions are for
Use actions for secondary operations relevant while the user is looking at your view: "Refresh", "Export CSV", "Toggle Filter", "Clear All", "Copy Link". They complement, rather than replace, the UI controls inside your view.
How the execute function survives the iframe boundary
The execute function is a live JavaScript closure. It cannot be serialized over postMessage. The SDK uses a two-registry approach:
- When you call
registerAction({ id, ..., execute }), the SDK stores the closure locally in the iframe'sExtensionBridge.actionRegistry. Only the metadata (id,title,icon, etc.) is sent to the host. - When the user activates an action from the ⌘K Drawer, the host sends
asyar:action:executeto the correct iframe. - The SDK receives the message, looks up the
executeclosure inactionRegistry, and calls it.
Registering actions
Register actions after your view is mounted:
import { ActionContext, ActionCategory } from 'asyar-sdk';
import type { IActionService, ExtensionAction } from 'asyar-sdk';
const actionService = context.getService<IActionService>('actions');
const refreshAction: ExtensionAction = {
id: 'com.yourname.myext:refresh', // Must be globally unique. Use your ext ID as namespace.
title: 'Refresh',
description: 'Re-fetch data from source',
icon: '↻',
extensionId: 'com.yourname.myext',
category: ActionCategory.PRIMARY,
context: ActionContext.EXTENSION_VIEW,
execute: async () => {
await loadData();
},
};
actionService.registerAction(refreshAction);
Svelte 5 lifecycle pattern — register and cleanup
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { ActionContext, ActionCategory } from 'asyar-sdk';
import type { IActionService } from 'asyar-sdk';
interface Props {
actionService: IActionService;
}
let { actionService }: Props = $props();
const ACTION_ID = 'com.yourname.myext:refresh';
onMount(() => {
actionService.registerAction({
id: ACTION_ID,
title: 'Refresh',
description: 'Reload the data',
icon: '↻',
extensionId: 'com.yourname.myext',
category: ActionCategory.PRIMARY,
context: ActionContext.EXTENSION_VIEW,
execute: () => reload(),
});
});
// Critical: always unregister on unmount.
// If you leave actions registered, they pollute the ⌘K panel for other views.
onDestroy(() => {
actionService.unregisterAction(ACTION_ID);
});
function reload() { /* ... */ }
</script>
⚠️ Always unregister actions when the view unmounts. Registered actions survive view navigation. If you forget
onDestroycleanup, your actions accumulate and appear in the ⌘K drawer for completely unrelated views.
⚠️ Pass the bare action ID to
unregisterAction()— the exact string you passed toidinregisterAction. Do not add any prefix.
Action field reference
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
✅ | Globally unique. Namespace with your extension ID: com.yourname.ext:action-name |
title |
string |
✅ | Label shown in the Action Drawer. |
extensionId |
string |
✅ | Your extension's id from manifest.json. |
execute |
() => void | Promise<void> |
✅ | Called when the user activates the action. |
description |
string |
❌ | Secondary text shown below the title. |
icon |
string |
❌ | Emoji or "icon:<name>" shown next to the title. |
category |
string |
❌ | Group label. Use ActionCategory constants for standard groups. |
context |
ActionContext |
❌ | When this action is visible. Default: always visible. |
Action context reference
ActionContext |
When shown |
|---|---|
GLOBAL |
Always — regardless of what's open |
EXTENSION_VIEW |
Only while an extension panel is open |
SEARCH_VIEW |
While the main search result list is active |
RESULT |
When a specific result item is highlighted |
COMMAND_RESULT |
After a command has returned a result |
CORE |
Built-in Asyar actions — do not use in extensions |
Manifest-declared actions
Manifest-declared actions let your extension contribute entries to the ⌘K drawer directly from the root search — without the user opening your extension view first. This is unique to Asyar: Raycast confines extension actions to their own command views.
How it works
- Declare actions in
manifest.jsonunder the rootactionsarray (extension-level) or inside an individual command'sactionsarray (command-level). - Register a handler in your extension code using
context.actions.registerActionHandler(actionId, handler). - When the user highlights your command in the main search list and presses ⌘K, the declared actions appear automatically.
- When the user selects an action, Asyar relays the request to your extension. The registered handler runs in your extension context.
Visibility rules
| Declaration | Visible when |
|---|---|
Extension-level actions[] |
Any command from your extension is highlighted in the root search |
Command-level actions[] |
That specific command is highlighted |
Both levels combine when the highlighted command has its own actions — the user sees extension-level actions plus that command's actions together.
Declaring actions in manifest.json
{
"id": "com.example.github",
"name": "GitHub",
"actions": [
{
"id": "open-settings",
"title": "Extension Settings",
"icon": "icon:settings",
"shortcut": "⌘,",
"category": "System"
}
],
"commands": [
{
"id": "search-repos",
"name": "Search Repositories",
"description": "Find GitHub repositories",
"mode": "view",
"component": "RepoSearch",
"actions": [
{
"id": "clone-repo",
"title": "Clone Repository",
"icon": "icon:download",
"shortcut": "⌘⇧C",
"category": "Primary"
}
]
}
]
}
Registering handlers in code
Register handlers in your extension's initialize() or activate() method:
import type { Extension, ExtensionContext } from 'asyar-sdk';
class GitHubExtension implements Extension {
async initialize(context: ExtensionContext): Promise<void> {
// Extension-level action — runs whenever user selects any GitHub command
context.actions.registerActionHandler('open-settings', async () => {
// Open settings panel, navigate, etc.
});
// Command-level action — runs when user selects "search-repos" and activates "clone-repo"
context.actions.registerActionHandler('clone-repo', async () => {
// Clone the currently-relevant repository
});
}
// ...
}
registerActionHandler(actionId, handler) — actionId is the short local ID you declared in manifest.json (e.g. "clone-repo"), not the full internal ID. The host constructs the full ID as act_{extensionId}_{actionId} internally.
Validation
The Rust extension loader validates action declarations at discovery time. Invalid extensions are skipped with a warning:
- ID regex:
/^[a-zA-Z][a-zA-Z0-9_-]*$/— must start with a letter, contain only letters, digits, underscores, or hyphens - Non-empty title: Every declared action must have a non-empty
title - Unique IDs within extension: Action IDs must be unique across both extension-level and command-level declarations in the same extension (no cross-scope duplicates)
ManifestAction field reference
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
✅ | Local identifier. Must be unique within the extension. |
title |
string |
✅ | Label shown in the ⌘K action drawer. |
description |
string |
❌ | Secondary text below the title. |
icon |
string |
❌ | Emoji or "icon:<name>". |
shortcut |
string |
❌ | Keyboard hint displayed in the drawer (display-only). |
category |
string |
❌ | Groups related actions under a heading. |
Standard action categories
| Constant | Display String | Use for |
|---|---|---|
ActionCategory.PRIMARY |
Primary | Main operations for the current view |
ActionCategory.NAVIGATION |
Navigation | Opening views, going back, drill-down |
ActionCategory.EDIT |
Edit | Create, update, delete operations |
ActionCategory.SHARE |
Share | Export, copy to clipboard, send |
ActionCategory.DESTRUCTIVE |
Destructive | Irreversible actions — delete, clear, reset |
ActionCategory.SYSTEM |
System | Reserved for built-in Asyar actions |
Custom category strings are fully supported — use them for domain-specific grouping.