Back to Blog
Build a Plugin Skill from Scratch

Build a Plugin Skill from Scratch

A skill is a self-contained capability you add to Claude Code through a plugin. It has a trigger (usually a slash command), a definition (what it does and how), and optional hooks (code that runs before or after tool calls). This tutorial walks through building one from scratch.

We will build a skill called changelog that generates a changelog entry from recent git commits.

Step 1: Project Setup

Skills live inside a plugin directory. If you already have a plugin, add the skill there. Otherwise, scaffold a new plugin:

my-plugin/
  PLUGIN.md
  skills/
    changelog/
      SKILL.md
  hooks/
    changelog-hook.js

The PLUGIN.md file declares the plugin metadata. The skills/ directory holds one folder per skill, each containing a SKILL.md. Hooks are optional JavaScript files that run at specific lifecycle points.

Step 2: Define the Skill

The SKILL.md file is the skill’s contract. It uses frontmatter for metadata and markdown for the instruction body.

---
name: changelog
description: Generate a changelog entry from recent git commits
triggers:
  - /changelog
  - /cl
args:
  - name: since
    description: Start date or tag (default: last tag)
    required: false
  - name: format
    description: Output format (markdown | json)
    default: markdown
---

## Instructions

1. Run `git log` to fetch commits since the specified
   start point (default: most recent tag).
2. Group commits by type using conventional commit
   prefixes: feat, fix, refactor, docs, chore.
3. For each group, list commits with their short hash
   and message.
4. If format is "json", output a JSON object with
   groups as keys and arrays of entries as values.
5. Write the result to `CHANGELOG-entry.md` in the
   project root (or stdout for JSON format).

## Rules

- Skip merge commits
- Skip commits with "chore: release" messages
- Truncate commit messages longer than 80 characters
- Include the date range in the header

The triggers array defines how users invoke the skill. The args array describes accepted parameters with defaults. The body contains instructions Claude follows when the skill is activated.

Step 3: Add Hook Integration

Hooks let you run JavaScript before or after specific tool calls. For the changelog skill, we want to validate that the project has git history before attempting to generate a changelog.

Create hooks/changelog-hook.js:

export default {
  name: "changelog-hook",
  description: "Validates git state before changelog generation",

  hooks: {
    PreToolUse: {
      match: {
        tool: "Bash",
        skill: "changelog"
      },
      handler: async (input, context) => {
        const { command } = input;

        if (command.includes("git log")) {
          const tagCheck = await context.bash("git tag --list");
          if (!tagCheck.stdout.trim()) {
            return {
              decision: "modify",
              message: "No tags found. Using first commit as start point.",
              updatedInput: {
                ...input,
                command: command.replace(
                  /--since=[^\s]+/,
                  ""
                )
              }
            };
          }
        }

        return { decision: "approve" };
      }
    },

    PostToolUse: {
      match: {
        tool: "Write",
        skill: "changelog"
      },
      handler: async (output, context) => {
        if (output.filePath?.endsWith("CHANGELOG-entry.md")) {
          const lineCount = output.content.split("\n").length;
          context.log(
            `Changelog generated: ${lineCount} lines`
          );
        }
        return output;
      }
    }
  }
};

The PreToolUse hook intercepts bash commands within the changelog skill and checks for git tags. If none exist, it modifies the command to use the full commit history instead of failing. The PostToolUse hook logs the result after the changelog file is written.

Step 4: Register the Plugin

Add your plugin to your Claude Code configuration so skills and hooks are discovered:

claude plugin add ./my-plugin

Verify the skill is registered:

claude skills list

You should see changelog in the output with its triggers and description.

Step 5: Test the Skill

Test with claude -p (prompt mode) to run the skill non-interactively:

claude -p "/changelog --since=v1.2.0"

For interactive testing, start a session and invoke the trigger:

claude
> /changelog --format json

To test the hook behavior specifically, run in a repository without tags:

cd /tmp && mkdir test-repo && cd test-repo
git init && git commit --allow-empty -m "feat: initial"
claude -p "/changelog"

The hook should log “No tags found” and still generate a changelog from the full history.

Step 6: Debug and Iterate

If the skill does not behave as expected, check these common issues:

  • Trigger not recognized — verify the triggers array in SKILL.md starts with /
  • Hook not firing — confirm the match.skill value matches the skill name exactly
  • Arguments not passed — ensure args entries have correct name fields matching what the trigger parser expects

Enable debug logging to see skill resolution and hook execution:

claude --debug

This prints which skill was matched, which hooks fired, and what modifications were applied.

What Next

Once your skill works locally, you can publish it to the Claude Code marketplace or share the plugin directory with your team. Skills compose well — you can build higher-level skills that invoke other skills, creating layered automation.

For more examples, browse the Brewcode plugin source. It contains 22 skills ranging from simple utilities to complex multi-step workflows. Each follows the same pattern: SKILL.md definition, optional hooks, clear instructions.