The Plugin Manifest, Field by Field
The manifest (plugins/{entity_slug}/manifest.ts) is your plugin's single declaration — the one file the core reads to know your plugin exists, what it's called, where its routes live, what statuses it has, and which functions to call at the extension points. This is the field-by-field reference, using the default plugin's manifest as the example.
Identity
entityName: 'Thing',
entitySlug: 'default_plugin',
entityObjectTable: 'default_plugin_thing',
description: 'Default reference plugin…',
entityName— the human-readable name of your entity (e.g. "Thing", "Video").entitySlug— the machine identifier, lowercase snake_case; it prefixes your table names and migration paths and must be unique across plugins.entityObjectTable— the name of your entity object table (must exist in the DB).description— a short description of the plugin.
Database names
joinTable: 'default_plugin_workspace',
joinTableEntityFk: 'default_plugin_thing_id',
joinTable— the{entity_slug}_workspacejoin table linking your entity to core workspaces.joinTableEntityFk— the FK column on that join table pointing to your entity object.
Workspace statuses
workspaceStatuses: [
{ slug: 'new', humanReadableLabel: 'New' },
{ slug: 'completed', humanReadableLabel: 'Completed' },
// …
],
initialWorkspaceStatus: 'new',
finalizableWorkspaceStatus: 'completed',
workspaceStatuses— every status your entity can be in (slug + label). The loader seeds these intoworkspace_status, scoped to your entity.initialWorkspaceStatus— the status a new workspace starts in.finalizableWorkspaceStatus— the status at which the operator may mark an entity "final" (an optional capability). Both the initial and finalizable statuses are manifest-driven, not hardcoded in the core.
Routes and menu
apiRoutePrefix: '/default-plugin',
createRouter: createDefaultPluginRouter,
menu: { label: 'Things', icon: 'list', listRoute: '/things', manageRoute: '/things/:id', sortOrder: 100 },
apiRoutePrefix— your API mounts at/api{apiRoutePrefix}.createRouter— a factory returning your Express router.menu— the sidebar entry: label, icon, list and manage routes, and sort order.
The four extension points
resultHandler: handleDefaultPluginResult,
workspaceSnapshotProvider: buildDefaultPluginSnapshot,
dashboardStatsProvider: getDefaultPluginDashboardStats,
workspaceDisplayResolver: resolveDefaultPluginDisplay,
Each is an async function the core calls at a defined moment; they have their own article.
A couple of import rules
The PluginManifest type comes from @core/core/pluginTypes.js — via the @core/* alias, never a relative path. Local imports within your plugin (your routes, your services) use relative paths with .js extensions. (The @core alias and why relative paths break in Docker are covered in Consuming Core Services.)
The manifest is the whole contract surface in one object — get it right and the loader does the rest: registers your entity, seeds your statuses, mounts your routes, and wires your extension points.