BodegaAPI reference
The exhaustive method index for the v1 BodegaAPI object your plugin receives in activate(api) — every namespace, signature, parameter, return, cap, and permission.
This page is the complete method index for BodegaAPI (apiVersion: "v1") — the single mediated object the host passes to your plugin's activate(api). Plugins extend the desktop app through this typed RPC façade only: no DOM access and no Node built-ins unless a matching capability is declared. Import the types from @bodegasuite/plugin-sdk (or use the scaffold's offline snapshot at src/types/bodega-api.d.ts).
For the contribution surfaces (ui.registerPanel / ui.registerTheme) in depth, see Contributions. For when hooks fire and their payloads, see Hooks and BodegaAPI. For manifest fields, permissions, and capabilities, see Plugin manifest.
The shape
import type { BodegaAPI } from '@bodegasuite/plugin-sdk'
export async function activate(api: BodegaAPI): Promise<void> {
// api.pluginId, api.hooks, api.filters, api.config, api.db, api.ui, api.i18n
}
export async function deactivate(): Promise<void> {
// optional — dispose tracked subscriptions here
}
The top-level object:
| Member | Type | Notes |
|---|---|---|
pluginId | string | Your plugin's manifest id (attribution + storage scope). |
hooks | BodegaHooksAPI | Subscribe to core action hooks. |
filters | BodegaFiltersAPI | Register value transforms (sale:receiptTitle is live — see below). |
config | BodegaPluginConfigAPI | Per-plugin persisted JSON settings. |
db | BodegaDatabaseAPI | Read-only, business-scoped data (permission-gated). |
ui | BodegaUIAPI | Panels, themes, toasts, theme toggle, nav clicks. |
i18n | BodegaI18nAPI | Read the host's live UI locale. |
Every register* / addAction / addFilter / subscribe-style call returns a disposer — () => void. Track them and call them in deactivate().
pluginId
api.pluginId // => 'com.example.my-plugin' (matches manifest.id)
A plain string equal to your manifest.json id. The host uses it for attribution, config storage scoping, rate-limit buckets, and contribution caps.
hooks
hooks.addAction(name, callback, priority?)
addAction<K extends PluginHookName>(
name: K,
callback: (payload: PluginHookPayload<K>) => void | Promise<void>,
priority?: number
): () => void
Subscribes to a core action hook. name is narrowed to the hook catalog, and the callback's payload is typed to match. Returns a disposer.
Action hooks fired by core today (only these):
| Name | Payload |
|---|---|
app:ready | AppReadyEvent |
sale:afterComplete | SaleWithDetails |
sale:afterRefund | SaleWithDetails |
inventory:afterAdjust | InventoryAdjustEvent |
inventory:lowStock | InventoryLowStockEvent |
cashClosure:afterSubmit | CashClosureWithDetails |
const off = api.hooks.addAction('sale:afterComplete', async (sale) => {
api.ui.notify(`Sale ${sale.id} completed`, { type: 'success' })
})
// later, in deactivate(): off()
priority (optional, lower runs earlier) orders listeners. A callback that throws ~5 consecutive times auto-disables your plugin. See Hooks and BodegaAPI for payload shapes and fire timing.
filters
filters.addFilter(name, callback, priority?)
addFilter<TValue>(
name: string,
callback: (value: TValue, ...args: unknown[]) => TValue | Promise<TValue>,
priority?: number
): () => void
Registers a value transform and returns a disposer.
First core filter is live. Core now invokes
applyFilterforsale:receiptTitlewhen an operator exports a sale ticket PDF: your callback receives the localized default heading (and{ sale }as context) and may return a replacement string. The host re-validates the returned value before using it — it must be a non-empty string, is reduced to a single line (control characters/newlines stripped), and is clamped to 120 characters; anything else falls back to the default heading. Other filter names are not wired yet, so a filter you register under a different name will not fire.
config
Per-plugin JSON persisted under userData/plugin-config/<pluginId>/settings.json. Survives restarts and is scoped to your pluginId.
config.get(key)
get(key: string): Promise<unknown | undefined>
Resolves to the stored value, or undefined if the key was never set.
config.set(key, value)
set(key: string, value: unknown): Promise<void>
Persists a JSON-serializable value under key.
await api.config.set('theme', 'dark')
const theme = (await api.config.get('theme')) as 'light' | 'dark' | undefined
db
Read-only data façade. Results are auto-scoped to the host's active business — businessId is intentionally not a parameter you can pass (cross-tenant guard). Each namespace requires the matching manifest permission, or the call rejects.
| Namespace | Required permission |
|---|---|
db.products | ipc:products:read |
db.sales | ipc:sales:read |
db.products.getById(id)
getById(id: string): Promise<Product | null>
Resolves the product, or null if not found in the active business.
db.products.list(options?)
list(options?: {
categoryId?: string
includeInactive?: boolean
limit?: number
}): Promise<Product[]>
db.sales.getById(id)
getById(id: string): Promise<SaleWithDetails | null>
db.sales.list(options?)
list(options?: {
limit?: number
status?: SaleWithDetails['status']
from?: string
to?: string
}): Promise<SaleWithDetails[]>
// manifest.permissions: ['ipc:products:read']
const lowStock = await api.db.products.list({ limit: 50 })
ui
Panels, themes, toasts, the theme toggle, and nav-click routing. Plugins never touch the DOM; the host renders on their behalf. See Contributions for the full contribution-surface depth.
ui.registerPanel(panel)
registerPanel(panel: {
id: string
title: string
location: 'sidebar' | 'settings' | 'navbar'
icon?: string
}): () => void
Contributes a UI item and returns a disposer.
location: 'navbar'renders an icon button in the desktop topbar; clicks route back viaui.onNavItemClick.location: 'sidebar'/'settings'are accepted and recorded but not rendered yet (reserved).
Caps: ≤32 panels per plugin; id ≤64 chars from [a-zA-Z0-9._-]; title ≤120 chars; icon ≤40 chars. An over-cap or invalid registration throws (in-process) or is logged and skipped (worker-isolated). Supported icon names (lucide; unknown falls back to a generic glyph):
moon, sun, contrast, palette, paintbrush, sparkles, settings, cloud, wallet,
package, activity, store, puzzle, star, bell, bookmark, tag, box, wrench, zap,
plug, gift, heart, globe, calendar, clock, receipt, printer, link, download, upload
const off = api.ui.registerPanel({
id: 'toggle',
title: 'Toggle theme',
location: 'navbar',
icon: 'contrast'
})
ui.registerTheme(themeSlug, definition)
registerTheme(themeSlug: string, definition: {
label?: string
tokens?: Record<string, string>
}): () => void
Reserves a theme slug and returns a disposer. tokens is optional and NOT applied today — the desktop ships authoritative [data-theme] palettes; you select one via ui.setTheme. Cap: ≤16 themes per plugin.
const off = api.ui.registerTheme('dark', { label: 'Dark mode' })
ui.notify(message, options?)
notify(message: string, options?: {
type?: 'info' | 'success' | 'warning' | 'error'
}): void
Shows a transient toast to the operator. Fire-and-forget (no return, no delivery ack). Rate-limited to ~5 per 10s per plugin (excess dropped and summarized); message clamped to ~500 chars.
api.ui.notify('Backup complete', { type: 'success' })
ui.setTheme(slug)
setTheme(slug: 'light' | 'dark'): Promise<void>
Applies a theme app-wide — the host toggles [data-theme] on the document root and the renderer's CSS variables flip.
ui.getActiveTheme()
getActiveTheme(): Promise<'light' | 'dark'>
Resolves the theme the host currently has applied.
const next = (await api.ui.getActiveTheme()) === 'dark' ? 'light' : 'dark'
await api.ui.setTheme(next)
ui.onNavItemClick(handler)
onNavItemClick(handler: (itemId: string) => void): () => void
Subscribes to clicks on this plugin's navbar items (the host only delivers clicks for items you actually registered — spoof-guarded). Returns a disposer.
const off = api.ui.onNavItemClick(async (itemId) => {
if (itemId === 'toggle') {
const current = await api.ui.getActiveTheme()
await api.ui.setTheme(current === 'dark' ? 'light' : 'dark')
}
})
i18n
i18n.getLocale()
getLocale(): string
Returns the host's current UI locale short tag (e.g. 'es' | 'en'), read live at call time — not captured at activate() — so it reflects a runtime language switch. The host cannot translate plugin-authored strings; use this to pick your own translations.
const label = api.i18n.getLocale() === 'es' ? 'Alternar tema' : 'Toggle theme'
Disposal pattern
Track every disposer and release them in deactivate():
const disposers: Array<() => void> = []
export async function activate(api) {
disposers.push(
api.ui.registerPanel({
id: 'toggle',
title: 'Toggle theme',
location: 'navbar',
icon: 'contrast'
}),
api.ui.registerTheme('dark', { label: 'Dark mode' }),
api.ui.onNavItemClick(async (id) => {
/* ... */
}),
api.hooks.addAction('app:ready', () => {
/* ... */
})
)
}
export async function deactivate() {
for (const off of disposers.splice(0)) off()
}
Related
- Contributions — panel/theme contribution surface in depth
- Hooks and BodegaAPI — when hooks fire and their payloads
- Plugin manifest — permissions, capabilities, and fields
- Developer quickstart
- Plugins overview