Create AiderDesk extensions by setting up extension files, defining metadata, implementing Extension interface methods, and updating documentation. Use when building a new extension, creating extension commands, tools, or event handlers.
68
84%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Create AiderDesk extensions that extend functionality through events, commands, tools, agents, and modes.
Use this skill when:
Do not use when:
When: Starting extension creation
Then: Ask the user where to install the extension
If: Working inside the AiderDesk project (the current project is the aider-desk repository)
Then: Offer three options:
.aider-desk/extensions/ in the current project (project-scoped)~/.aider-desk/extensions/ (available in all projects)packages/extensions/extensions/ (ships with AiderDesk app)If: Working outside the AiderDesk project
Then: Offer two options:
.aider-desk/extensions/ in the current project (project-scoped)~/.aider-desk/extensions/ (available in all projects)Must: Wait for user's choice before proceeding. The chosen target determines the entire workflow.
Reference: references/install-targets.md for full details on each target.
When: User has chosen an installation target
If: Target is Current Project or Global
Then: Follow the Project / Global Flow:
.aider-desk/extensions/ or ~/.aider-desk/extensions/)If: Target is In-Repo
Then: Follow the In-Repo Flow:
packages/extensions/extensions/packages/extensions/extensions.jsondocs-site/docs/extensions/extensions-gallery.mdWhen: Creating extension files (after target is chosen)
Then: Check if extension needs npm dependencies or multiple files
If: Extension needs dependencies or multiple files
Then: Create folder extension
If: Extension is simple with no dependencies
Then: Create single-file extension
When: Creating extension file
Then: Implement required methods from Extension interface
Must: Define static metadata property on the class with name, version, description, author, capabilities
Must: Export class as default (e.g., export default class MyExtension implements Extension)
Never: Use @/ imports in extension files
When: Extension needs to display UI elements in task page placements
Then: Implement getUIComponents() method
Must: Return array of UIComponentDefinition objects with id, placement, jsx
Must: Define components as JSX strings or load from .jsx files
If: Component needs data, set loadData: true and implement getUIExtensionData()
If: Component triggers actions, implement executeUIExtensionAction()
When: Extension UI components need third-party npm packages (charts, calendars, kanban boards, etc.)
Then: Implement getUIComponentsLibraries() returning a Record<string, string> mapping camelCase keys to npm package specs
Must: Access loaded libraries in JSX via props.libraries.<key>
Must: Handle the loading state — libraries are async and props.libraries.<key> will be undefined on first render
Never: Bundle or import React in library specs — AiderDesk's React instance is externalized automatically
Reference: external-libraries.md for full details, loading patterns, and examples
When: Extension needs user-configurable settings (shown in gear icon dialog)
Then: Implement three methods: getConfigComponent(), getConfigData(), saveConfigData()
Must: Return JSX string from getConfigComponent() — use external .jsx file for components > 20 lines
Must: Load/merge defaults in getConfigData(), persist merged data in saveConfigData()
Must: Store config file in extension directory via join(__dirname, 'config.json')
Must: Use ui.* components (ui.Input, ui.Checkbox, etc.) instead of raw HTML elements
Should: Avoid inner state (useState/useEffect) for simple form fields — read directly from config prop, call updateConfig on change. Only use local state for derived values or transient UI state.
Never: Use 'extension-settings' placement — it was removed; use the dedicated config API instead
When: Any UI or config component (.jsx file) was created or modified
Then: Run the validator script and fix all reported issues
Must: Run node <skill-dir>/assets/scripts/validate-extension-ui.mjs <file-or-dir> where <skill-dir> is this skill's directory
Must: Ensure every file passes — exit code 0, all lines say PASS
Must: Fix all [SYNTAX] issues (the component would crash at render time) and [TYPE] issues (typos in props, React API, or ui.* component names)
If: A file uses config-style props (config, updateConfig) but is not named Config*.jsx
Then: Add --type=config (conversely, --type=ui forces UI-component props)
Note: The script needs sucrase, typescript, and @types/react resolvable (they are, when the aider-desk repo is available); otherwise it degrades to whatever checks are possible and says so in a warning
When: Target is In-Repo and extension file is created
Then: Add entry to packages/extensions/extensions.json
Must: Include id, name, description, file or folder path, type, capabilities
Must: Set hasDependencies: true for folder extensions
Then: Add entry to docs-site/docs/extensions/extensions-gallery.md table
Must: Include extension name, description, capabilities, and type
When: Target is Project or Global
Then: Do NOT modify extensions.json or extensions-gallery.md — these are only for built-in extensions
When: Creating folder extension
Then: Include tsconfig.json with module: ES2020+
Must: Include package.json with name, version, main, dependencies
When: Extension needs persistent config
Then: Store config files in extension directory
Never: Store config outside extension directory
Between steps 3 and 5:
After any .jsx file is created:
node <skill-dir>/assets/scripts/validate-extension-ui.mjs <extension-dir> — all files must passpackages/extensions/extensions/packages/extensions/extensions.jsondocs-site/docs/extensions/extensions-gallery.mdnpm install in packages/extensions/ (folder extensions)Between steps 3 and 5:
After any .jsx file is created:
node <skill-dir>/assets/scripts/validate-extension-ui.mjs <extension-dir> — all files must passBefore using this skill, verify:
IMPORTANT: The reference files below are comprehensive but may lag behind the latest code. For the authoritative and complete API, always refer to these source files:
When you need a method or type that's not in the reference docs, fetch the raw source first before guessing.
Built-in extension examples are available at:
packages/extensions/extensions/[extension-name]/ — browse these for real-world patternsAfter completing this skill, verify:
metadata property on the class includes all required fields (name, version)@/ imports usedassets/scripts/validate-extension-ui.mjsSuccess metrics:
Situation: Extension needs to handle events
Pattern:
Situation: Extension needs to create subtasks and coordinate between them
Pattern:
const newTask = await projectContext.createTask({ parentId: currentTaskId, name: 'Subtask' })const subtaskContext = projectContext.getTask(newTask.id)await subtaskContext?.runCustomCommand('scope:start')const messages = await subtaskContext?.getContextMessages()const allTasks = await projectContext.getTasks()const allTasks = await projectContext.reloadTasks()await projectContext.forkTask(taskId, messageId)await projectContext.duplicateTask(taskId)await projectContext.deleteTask(taskId)Situation: Extension needs to make direct LLM calls without the full agent loop
Pattern:
const profile = await taskContext.getTaskAgentProfile(); const modelId = profile ? \${profile.provider}/${profile.model}` : 'openai/gpt-4o'`const result = await taskContext.generateText(modelId, 'You are a helpful assistant', 'Summarize this code')const result = await taskContext.generateObject(modelId, systemPrompt, prompt, z.object({ category: z.string() }))Situation: Extension needs to read conversation history
Pattern:
const messages = await taskContext.getContextMessages()function extractText(content: ContextMessage['content']): string {
if (typeof content === 'string') return content;
if (Array.isArray(content)) {
return content
.filter((part): part is TextPart => part.type === 'text')
.map(part => part.text)
.join('\n');
}
return '';
}Situation: Extension needs to run shell commands
Pattern: Node.js built-in modules (fs, path, child_process, os, etc.) are available in extensions.
import { execSync } from 'node:child_process'; const output = execSync('git status', { encoding: 'utf-8' });import { exec } from 'node:child_process'; import { promisify } from 'node:util'; const execAsync = promisify(exec);import { readFileSync, writeFileSync } from 'node:fs'; import { join } from 'node:path';Situation: Extension needs to manage todos
Pattern:
const todos = await taskContext.getTodos()const todos = await taskContext.addTodo('Implement feature X')const todos = await taskContext.updateTodo('Implement feature X', { completed: true })const todos = await taskContext.deleteTodo('Implement feature X')await taskContext.setTodos([{ name: 'Task 1', completed: false }])Situation: Extension needs to register commands
Pattern:
Situation: Extension needs to add UI components
Pattern:
props.ui.CodeBlock for syntax-highlighted code, JSON, and diffs; use props.ui.ExpandableMessageBlock for collapsible tool-style message renderersSituation: Extension needs to log messages
Pattern:
context.log(message, type) — logs to backend console onlycontext.getTaskContext()?.addLogMessage(level, message) — displays in task's chat UIaddLogMessage (user-visible). Use context.log only for internal diagnostics.context.log is always available; getTaskContext() returns null outside a task, so always use optional chaining (?.)Situation: Extension needs to manage resource cleanup (timers, watchers, child processes)
Pattern:
onLoad, onProjectStarted, or event handlers that need cleanupcontext.addDisposable() to co-locate setup and cleanup logicPromise) — async cleanups are awaited during unloadonUnload is called, so both patterns work togetherasync onLoad(context: ExtensionContext) {
// Setup and cleanup are co-located — no need to track in onUnload
context.addDisposable(() => {
const timer = setInterval(() => doWork(), 1000);
return () => clearInterval(timer);
});
}
// onUnload still works as a fallback for anything not registered via addDisposablevoid, no cleanup is registered — useful for fire-and-forget side effectsSituation: Extension needs to store or retrieve memories
Pattern:
context.getMemoryContext() to access the Memory APIisMemoryEnabled() before using memory operationsmemory.storeMemory(projectId, taskId, type, content) — returns the created memory IDmemory.retrieveMemories(projectId, query, limit?) — returns semantically similar memoriesMemoryEntryType enum ('task', 'user-preference', 'code-pattern')projectId/taskId if not applicableasync onAgentFinished(event: AgentFinishedEvent, context: ExtensionContext) {
const memory = context.getMemoryContext();
if (!memory.isMemoryEnabled()) return;
const projectId = context.getProjectDir();
const taskId = context.getTaskContext()?.data.id ?? '';
await memory.storeMemory(projectId, taskId, 'code-pattern', 'Always use clsx for conditional classes');
const memories = await memory.retrieveMemories(projectId, 'React class naming');
context.log(`Found ${memories.length} relevant memories`, 'info');
}Situation: Extension UI components need third-party npm packages
Pattern:
ui propgetUIComponentsLibraries() returning { key: 'package@^version' }props.libraries.<key> in JSX — check for undefined (async loading)getUIComponentsLibraries() { return { chart: 'recharts@^2.12.0' } }Situation: Extension needs to customize message rendering
Pattern:
task-message placement with a messageFilter to specify which messages to handlemessageFilter.types to the message types to match (e.g. 'user', 'response', 'assistant-group', 'tool', 'log', 'loading')messageFilter.serverName and/or messageFilter.toolNamemessage prop — for assistant-group type, access message.responseMessage and message.toolMessagesprops.ui.ExpandableMessageBlock for the standard collapsible tool-message layout and props.ui.CodeBlock for code or structured resultsMessageFilter type, and JSX examplesSituation: Extension needs a floating panel
Pattern:
floating placement and set name for the panel titlename on UIComponentDefinition — used as the floating panel title bar textloadData: true + getUIExtensionData() for panel data; implement executeUIExtensionAction() for actionscontext.triggerUIDataRefresh(componentId) to refresh panel data, context.triggerUIComponentsReload() to re-register components after state changesSituation: Extension needs config storage
Pattern:
Situation: Extension needs a settings UI (config component)
Pattern:
getConfigComponent(), getConfigData(), saveConfigData() methods.jsx file content via readFileSync(join(__dirname, './ConfigComponent.jsx'), 'utf-8'){ config, updateConfig, ui, icons, models, providers, ... }7e957a0
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.