Step-by-step workflow for creating a new Baseplate plugin package, including root configuration files, source structure, and platform modules.
Use this skill when creating a new Baseplate plugin package from scratch.
This skill guides you through creating a new plugin by copying plugin-queue as a template and customizing it. Plugins support two architectures:
cp -r plugins/plugin-queue plugins/plugin-<name>
Edit plugins/plugin-<name>/package.json:
"name" to "@baseplate-dev/plugin-<name>""description""version" to "0.1.0""keywords" to match the new pluginChange the federation name:
federation({
name: 'plugin-<name>', // <-- Update this
filename: 'remoteEntry.js',
...
})
Replace content with appropriate description for the new plugin.
Create a fresh changelog:
# @baseplate-dev/plugin-<name>
Run from the repository root to auto-configure tsconfig.build.json references:
pnpm metadata:sync
This command automatically updates TypeScript project references based on package dependencies.
Update packages/project-builder-common/package.json to include the new plugin:
{
"dependencies": {
"@baseplate-dev/plugin-<name>": "workspace:*"
}
}
STOP HERE and inform the user:
The new plugin directory has been created. Before continuing:
- Run
pnpm installto register the new package in the workspace- Restart your watch process (
pnpm watchor similar) to pick up the new plugin- The pnpm workspace needs to recognize
plugins/plugin-<name>before we can continue- Restart Claude Code plugin (the MCP server needs to be restarted to pick up the new plugin)
Once you've restarted the watch process, let me know and we'll continue with the src/ customization.
Do NOT proceed until user confirms they have restarted the watch process.
Remove the template implementation directories (we'll create fresh ones):
rm -rf plugins/plugin-<name>/src/queue
rm -rf plugins/plugin-<name>/src/pg-boss
rm -rf plugins/plugin-<name>/src/bullmq
rm -rf plugins/plugin-<name>/src/common
mkdir -p plugins/plugin-<name>/src/<plugin-name>/core
mkdir -p plugins/plugin-<name>/src/<plugin-name>/core/schema
mkdir -p plugins/plugin-<name>/src/<plugin-name>/core/components
mkdir -p plugins/plugin-<name>/src/<plugin-name>/core/generators
mkdir -p plugins/plugin-<name>/src/<plugin-name>/static
Update the CSS prefix in plugins/plugin-<name>/src/styles.css:
@layer theme, base, components, utilities;
@import 'tailwindcss/theme.css' layer(theme) prefix(<plugin-name>);
@import 'tailwindcss/utilities.css' layer(utilities) prefix(<plugin-name>);
@import '@baseplate-dev/ui-components/theme.css';
Create exports for your plugin:
export * from './<plugin-name>/index.js';
Create plugins/plugin-<name>/src/<plugin-name>/plugin.json:
{
"name": "<plugin-name>",
"displayName": "<Plugin Display Name>",
"icon": "icon.svg",
"description": "Description of what this plugin does",
"version": "0.1.0",
"moduleDirectories": ["core"]
}
Create or copy an SVG icon to plugins/plugin-<name>/src/<plugin-name>/static/icon.svg
common.ts - Schema registration:
import {
createPluginModule,
pluginConfigSpec,
} from '@baseplate-dev/project-builder-lib';
import { create<PluginName>PluginDefinitionSchema } from './schema/plugin-definition.js';
export default createPluginModule({
name: 'common',
dependencies: {
pluginConfig: pluginConfigSpec,
},
initialize: ({ pluginConfig }, { pluginKey }) => {
pluginConfig.schemas.set(pluginKey, create<PluginName>PluginDefinitionSchema);
},
});
web.ts - UI registration:
import {
createPluginModule,
webConfigSpec,
} from '@baseplate-dev/project-builder-lib';
import { <PluginName>DefinitionEditor } from './components/<plugin-name>-definition-editor.js';
import '../../styles.css';
export default createPluginModule({
name: 'web',
dependencies: {
webConfig: webConfigSpec,
},
initialize: ({ webConfig }, { pluginKey }) => {
webConfig.components.set(pluginKey, <PluginName>DefinitionEditor);
},
});
node.ts - Generator registration:
import {
appCompilerSpec,
backendAppEntryType,
createPluginModule,
pluginAppCompiler,
} from '@baseplate-dev/project-builder-lib';
import { <pluginName>Generator } from './generators/<plugin-name>/<plugin-name>.generator.js';
export default createPluginModule({
name: 'node',
dependencies: {
appCompiler: appCompilerSpec,
},
initialize: ({ appCompiler }, { pluginKey }) => {
appCompiler.compilers.push(
pluginAppCompiler({
pluginKey,
appType: backendAppEntryType,
compile: ({ appCompiler }) => {
appCompiler.addRootChildren({
<pluginName>: <pluginName>Generator({}),
});
},
}),
);
},
});
index.ts - Barrel exports:
export * from './core/index.js';
core/index.ts:
// Re-export schema types
export type * from './schema/plugin-definition.js';
Create plugins/plugin-<name>/src/<plugin-name>/core/schema/plugin-definition.ts:
import { definitionSchema } from '@baseplate-dev/project-builder-lib';
import { z } from 'zod';
export const create<PluginName>PluginDefinitionSchema = definitionSchema(() =>
z.object({
// Add your plugin configuration fields here
// For spec-implementation pattern:
// implementationPluginKey: z.string().min(1, 'Implementation must be selected'),
}),
);
export type <PluginName>PluginDefinition = z.infer<
ReturnType<typeof create<PluginName>PluginDefinitionSchema>
>;
Create plugins/plugin-<name>/src/<plugin-name>/core/components/<plugin-name>-definition-editor.tsx:
import type { WebConfigProps } from '@baseplate-dev/project-builder-lib';
import {
useProjectDefinition,
useResettableForm,
} from '@baseplate-dev/project-builder-lib/web';
import { Button } from '@baseplate-dev/ui-components';
import { zodResolver } from '@hookform/resolvers/zod';
import React from 'react';
import {
create<PluginName>PluginDefinitionSchema,
type <PluginName>PluginDefinition,
} from '../schema/plugin-definition.js';
export function <PluginName>DefinitionEditor({
definition: pluginMetadata,
metadata,
onSave,
}: WebConfigProps): React.ReactElement {
const { saveDefinitionWithFeedback } = useProjectDefinition();
const schema = create<PluginName>PluginDefinitionSchema();
const form = useResettableForm<<PluginName>PluginDefinition>({
resolver: zodResolver(schema),
values: pluginMetadata?.config as <PluginName>PluginDefinition,
});
const onSubmit = form.handleSubmit((data) =>
saveDefinitionWithFeedback(
(draftConfig) => {
// Update plugin config
},
{
successMessage: 'Plugin settings saved!',
onSuccess: () => onSave(),
},
),
);
return (
<form onSubmit={onSubmit} className="<plugin-name>:space-y-4 <plugin-name>:max-w-4xl">
{/* Add form fields here */}
<Button type="submit" disabled={form.formState.isSubmitting}>
Save
</Button>
</form>
);
}
IMPORTANT: All CSS classes must use the <plugin-name>: prefix (e.g., <plugin-name>:flex, <plugin-name>:space-y-4).
Skip this phase for standalone plugins.
mkdir -p plugins/plugin-<name>/src/<implementation-name>/core
mkdir -p plugins/plugin-<name>/src/<implementation-name>/core/schema
mkdir -p plugins/plugin-<name>/src/<implementation-name>/core/components
mkdir -p plugins/plugin-<name>/src/<implementation-name>/core/generators
mkdir -p plugins/plugin-<name>/src/<implementation-name>/static
Create plugins/plugin-<name>/src/<implementation-name>/plugin.json:
{
"name": "<implementation-name>",
"displayName": "<Implementation Display Name>",
"icon": "icon.svg",
"description": "Description of this specific implementation",
"version": "0.1.0",
"moduleDirectories": ["core"],
"managedBy": "@baseplate-dev/plugin-<name>:<plugin-name>"
}
The managedBy field links this implementation to the base plugin.
Follow the same pattern as the base plugin, but with implementation-specific logic.
Add exports for each implementation:
export * from './<plugin-name>/index.js';
export type * from './<implementation-name>/index.js';
pnpm install
cd plugins/plugin-<name>
pnpm build
pnpm typecheck
pnpm lint --fix
plugin.json exists in each plugin directorymoduleDirectories points to valid directories with module filespnpm-workspace.yaml scope (plugins/*)<plugin-name>: prefixstyles.css has the correct prefix configurationvite.config.ts has the correct federation namepnpm typecheck to identify issues.js extensions for ESM compatibilityexport type * for type-only exportsnpx skills add halfdomelabs/Add Plugin下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer