| name | create-extension |
| description | Generate and implement extensions for Accounted: scaffold files, configure manifests, write event handlers, API routes, services, workspace UIs, settings panels, and testing. Use when creating new extensions, adding surfaces to existing extensions, or understanding the extension architecture. Covers the full lifecycle from scaffolding to registration. |
Extension Generator
Quick Start
1. Scaffold:
npx tsx scripts/create-extension.ts \
--name my-extension --sector general --category operations \
--description "Short description"
Sectors: general, restaurant, construction, hotel, tech, ecommerce, export
Categories: import, operations, reports, accounting
2. Enable: add "my-extension" to extensions.config.json
3. Regenerate: npm run setup:extensions (auto-runs on dev/build)
4. Implement: edit index.ts to add surfaces
Extension Object Template
import type { Extension, ExtensionContext } from '@/lib/extensions/types'
import type { EventPayload } from '@/lib/events/types'
import { myExtApiRoutes } from './api-routes'
interface MyExtSettings { featureEnabled: boolean; threshold: number }
const DEFAULT_SETTINGS: MyExtSettings = { featureEnabled: true, threshold: 0.8 }
async function handleSomeEvent(
payload: EventPayload<'transaction.synced'>, ctx?: ExtensionContext
): Promise<void> {
const { transactions, userId } = payload
const log = ctx?.log ?? console
const settings = ctx
? { ...DEFAULT_SETTINGS, ...(await ctx.settings.get<Partial<MyExtSettings>>() || {}) }
: DEFAULT_SETTINGS
if (!settings.featureEnabled) return
try {
const supabase = ctx?.supabase ?? await (await import('@/lib/supabase/server')).createClient()
} catch (error) { log.error('Handler failed:', error) }
}
export const myExtensionExtension: Extension = {
id: 'my-extension',
name: 'My Extension',
version: '1.0.0',
sector: 'general',
apiRoutes: myExtApiRoutes,
eventHandlers: [
{ eventType: 'transaction.synced', handler: handleSomeEvent },
],
settingsPanel: { label: 'My Extension', path: '/settings/extensions/my-extension' },
async onInstall(ctx) { await ctx.settings.set('settings', DEFAULT_SETTINGS) },
}
Export name convention: {camelCaseId}Extension (e.g., myExtensionExtension).
Manifest (Minimal)
{
"id": "my-extension",
"sector": "general",
"exportName": "myExtensionExtension",
"entryPoint": "@/extensions/general/my-extension",
"workspace": null,
"requiredEnvVars": [],
"optionalEnvVars": [],
"npmDependencies": [],
"definition": {
"name": "My Extension",
"category": "operations",
"icon": "Box",
"dataPattern": "core",
"description": "Short marketplace description",
"longDescription": "Longer detail page description."
}
}
For a workspace page, set "workspace": "@/components/extensions/general/MyExtensionWorkspace". See Manifest Reference.
Extension Context
interface ExtensionContext {
userId: string; extensionId: string
supabase: SupabaseClient
emit(event: CoreEvent): void
settings: ExtensionSettings
storage: ExtensionStorage
log: ExtensionLogger
services: ExtensionServices
}
Available Surfaces
Common Mistakes
- Forgetting
npm run setup:extensions after config change: extension won't load
- Editing
_generated/ files: overwritten on next setup
- Not handling
ctx = undefined: context is undefined in cron jobs; always fallback
- Wrong export name: must match
exportName in manifest exactly
- Importing from other extensions: only import from core (
@/lib/, @/types)
- Lazy imports in module scope: static imports for extension code;
await import() only inside handler functions