Build a tiny Claude plugin using the official directory structure
Welcome to another official UVF IT build guide! In the next 45 minutes you’ll create a minimal yet functional Claude plugin that follows the official plugin directory layout. This clone mimics the structure of Anthropic’s official plugins without any external services.
We’ll use plain Node.js, SQLite for optional state, and only free, local tooling – perfect for a beginner who wants to see a real plugin load in Claude Code.
Create the directory layout and basic metadata files required by Claude’s plugin system. This structure lets Claude discover your plugin and load its commands without any extra configuration.
We’ll also add a minimal package.json so Node can resolve modules, and a .claude-plugin manifest that tells Claude what commands exist. All files are plain text, so you can edit them with any editor.
mkdir -p my-plugin/commands
cat > my-plugin/.claude-plugin <<'EOF'
{
"name": "my-plugin",
"description": "A minimal Claude plugin clone",
"version": "0.1.0",
"commands": [
{"name": "hello", "description": "Say hello", "path": "commands/hello.js"}
]
}
EOF
cat > my-plugin/package.json <<'EOF'
{
"type": "module",
"name": "my-plugin",
"version": "0.1.0",
"main": "index.js"
}
EOFImplement a static slash command that returns a greeting. Claude will invoke this file when the user types /hello. The command simply exports a handler function that receives the request payload and returns a JSON response.
Because we are using ES modules, we export the handler with export async function handle(...). No external dependencies are needed, keeping the clone lightweight.
my-plugin/commands/hello.js
/**
* Minimal slash command for the Claude plugin.
* Returns a friendly greeting.
*/
export async function handle(request) {
// The request object may contain user info; we ignore it for this demo.
const response = {
content: "Hello from your minimal Claude plugin!",
// Optional: indicate the response is finished.
done: true
};
return response;
}We need to tell Claude where our new slash command lives. Claude reads a manifest.json file inside the commands folder of each plugin and uses it to populate the /plugin UI. By adding an entry for our /hello command we make it discoverable and executable from any Claude chat.
The manifest includes a unique identifier, a human‑readable name, a short description, and the path to the executable script we wrote earlier. Once this file is in place, Claude will load the command automatically when the plugin directory is supplied.
my-plugin/commands/manifest.json
{
"commands": [
{
"id": "hello",
"name": "/hello",
"description": "Send a friendly greeting from the plugin",
"entrypoint": "../src/hello.py",
"type": "script",
"runtime": "python3"
}
]
}With the manifest in place we can start Claude Code (or the Claude CLI) in plugin‑development mode. The tool watches the directory you point it at, loads any manifests it finds, and makes the commands available in the chat UI.
Running the CLI with the --plugin-dir flag points Claude at our my-plugin folder. The process will stay alive, printing logs that help you spot loading errors before you try the slash command.
# Install the Claude development tools if you haven't already
pip install claude-code
# Launch Claude Code in plugin‑dev mode, pointing at the plugin root
claude-code --plugin-dir ./my-plugin
# The console will show something like:
# Loaded plugin: My Minimal Plugin (1 command)
# Listening for slash commands…Now open Claude Code’s UI (usually at http://localhost:8080) and start a new chat. In the message box type /hello and hit Enter – Claude should call our script and display the greeting we coded.
If you want the plugin to remember data across runs, you can add a tiny SQLite store. Installing the Python driver is all that’s needed; the database file will be created on first use. This step is optional but shows how to grow the plugin beyond a single‑run script.
# Optional: add SQLite support for future state
pip install aiosqlite
# Example snippet you could paste into src/hello.py to use the DB:
# import aiosqlite, asyncio
# async def init_db():
# async with aiosqlite.connect('plugin_state.db') as db:
# await db.execute('CREATE TABLE IF NOT EXISTS greetings(id INTEGER PRIMARY KEY, text TEXT)')
# await db.commit()
# asyncio.run(init_db())| Check | Command / Why it matters |
|---|---|
| Plugin appears in Claude’s /plugin > Discover list | Open Claude Code UI at http://localhost:8080 and look for “My Minimal Plugin” under the Discover tab |
Running /hello returns the greeting message | Type /hello in a Claude chat window and verify the response contains the 👋 greeting |
| No runtime errors in the console | Watch the terminal where claude-code --plugin-dir ./my-plugin was started; there should be no stack traces |
You now have a working Claude plugin skeleton. Explore adding arguments, persisting data with SQLite, or publishing to the official directory by following Anthropic’s contribution guide.