Skip to main content
BlogSoftware Development

Claude Code Plugin vs Skill: When to Bundle a Workflow

A skill is a document, a plugin is a dependency. When one SKILL.md file is enough, when drift between copies means bundle it, and what a plugin costs.

Matthews Wong

Software Engineer

September 202611 min read
Claude Code Plugin vs Skill: When to Bundle a Workflow

Photo by Minnesota Historical Society via Wikimedia Commons (CC BY-SA 2.0)

Key Takeaway

A Claude Code skill is a document; a plugin is a dependency. Keep a workflow as one SKILL.md file while you are the only reader, and bundle it as a versioned plugin only when two or more people must run the identical configuration and you cannot tell whether their copies have drifted apart.

Three skills sit in this repository under .claude/skills — one for writing blog content, one for publishing it, one for drawing the figures. None of them is a plugin. There is no manifest, no marketplace entry and no version to pin, and after months of daily use that has not cost me anything I can name.

This post is the decision rule behind that, argued rather than listed: a skill is a document and a plugin is a dependency. The mechanics come from the Claude Code documentation, which draws the same line in its own table — standalone configuration for personal workflows, project customisations and quick experiments, plugins for sharing with teammates, versioned releases and reuse across projects.

A skill is a document, a plugin is a dependency

Both ship the same markdown. A skill is a SKILL.md file with YAML frontmatter that Claude loads when the task matches its description, and a plugin's skills directory holds exactly those files, unchanged. The difference is not capability. It is the unit of distribution: a document you read and edit, or a dependency you install, pin and upgrade.

The documentation makes the split visible in the command name. A standalone skill at .claude/skills/hello answers to slash hello. The same folder inside a plugin answers to slash plugin-name colon hello, because plugin skills are always namespaced so two plugins cannot collide on one name. That prefix is an honest signal: the procedure now belongs to a package rather than to your project.

What a single skill file costs you

Nothing but the file. A personal skill lives at ~/.claude/skills/name/SKILL.md and is available in every project you open; a project skill lives at .claude/skills/name/SKILL.md and loads from the directory you started in plus every parent up to the repository root. There is no install step, because the file being on disk is the installation.

# A skill is a folder with one required file in it. There is no install
# step, because being on disk IS the installation.
.claude/skills/publishing-blog-posts/SKILL.md      # project, this repo
~/.claude/skills/summarise-changes/SKILL.md        # personal, everywhere

# The whole interface is the frontmatter. description is what Claude
# matches the task against, so it is the only field that decides whether
# the skill is ever loaded at all.
---
name: publishing-blog-posts
description: The staging and merge pipeline for a bilingual blog post.
  Use when adding, merging or debugging a post or the merge scripts.
disable-model-invocation: false   # default; true means only /name invokes it
allowed-tools: Read Grep          # pre-approved for the invoking turn only
---

# Project skills load from the starting directory AND every parent up to
# the repository root, so starting Claude in a subdirectory still finds
# the skills defined at the root.
#
# The trap: across levels, personal beats project. A stale copy in
# ~/.claude/skills wins over the one you committed, and nothing says so.

Two properties make that hard to beat while you are the only reader. Claude Code watches the skill directories, so adding, editing or removing a SKILL.md is picked up inside the running session with no restart — the edit-and-test loop is one save long. And a skill's body loads only when the skill is used, so a long reference document costs almost nothing until something needs it. That is why the three skills in this repository can be exhaustive without being expensive.

What the plugin wrapper adds to the same markdown

A manifest and a directory contract. A plugin declares itself in .claude-plugin/plugin.json with a name, a description, an optional version and an optional author, and everything else sits at the plugin root: skills, agents, hooks/hooks.json, .mcp.json for MCP servers, .lsp.json for language servers, monitors, a bin directory and a settings.json. Only the manifest belongs inside .claude-plugin, and the documentation labels putting the component directories in there the common mistake.

# The same markdown, wrapped. Only plugin.json goes inside
# .claude-plugin/ — every component directory sits at the plugin root.
my-tool/
  .claude-plugin/plugin.json   # name, description, version, author
  skills/publishing-blog-posts/SKILL.md
  agents/blog-post-author.md
  hooks/hooks.json             # the same object you had in settings.json
  .mcp.json                    # MCP servers the bundle brings with it
  bin/                         # joins the Bash tool PATH while enabled
  settings.json                # its agent key can swap the main thread

# The manifest, in full. version is optional, and it is the field that
# decides whether your users ever cross a change you did not intend:
#   name         "my-tool"
#   description  "Blog publishing pipeline"
#   version      "1.0.0"

# Test it without installing anything and without a marketplace:
claude --plugin-dir ./my-tool      # also accepts ./my-tool.zip
claude plugin validate ./my-tool   # --strict fails on warnings too

# Or skip the marketplace permanently. A manifest dropped into a folder
# that is already in your skills directory loads as my-tool@skills-dir:
claude plugin init my-tool         # scaffolds it under ~/.claude/skills

There is a cheaper rung than a marketplace, and it is the one most teams should try first. Drop a .claude-plugin/plugin.json into a folder that is already in your skills directory and it loads on the next session as name at skills-dir, with no marketplace and no install step — which is exactly what claude plugin init scaffolds. It can carry agents, hooks and MCP servers while still being a folder you edit in place.

That rung is also where you feel the unit change. Edits to SKILL.md still take effect immediately, but changes to hooks, .mcp.json, agents or output styles do not until you run slash reload-plugins or restart. The moment a procedure grows a part that has to be reloaded, it has stopped being a document.

The threshold is drift, not headcount

Copying a skill folder to a second machine is not the problem. The problem is that the two copies then diverge and nothing tells you. Claude Code resolves same-named skills across levels with a fixed precedence — enterprise overrides personal, personal overrides project — so a teammate who once copied the procedure into their own ~/.claude/skills runs that stale copy, not the one you committed, and no diff, review or startup message mentions it.

The second copy usually arrives before the second person does. A skill cannot carry a hook: the hooks block lives in .claude/settings.json, so the first time a procedure needs a deterministic step it exists in two files that have to be copied together and can be copied apart. Migrating to a plugin moves that same object into hooks/hooks.json in the same format, which is the clearest statement of what a bundle is actually for.

So the trigger is not team size. It is the first time you cannot answer the question which version of this procedure is that person running. Installing at project scope answers it, because the plugin is written into .claude/settings.json and the repository names the version everyone gets. One caveat is worth knowing before you rely on that: a project-enabled plugin from an external source does not install itself, and Claude Code reports it as not installed until the collaborator runs claude plugin install.

A five-band stack running from one person editing one skill file, through a personal copy silently outranking the project copy and a hook splitting the procedure across two files, to three teammates each running an unnamed version, ending on drift that no review sees.
The band to look at is the third rather than the fourth: a procedure splits into two files as soon as it needs a hook, and that usually happens long before a second person does.

The upgrade you did not schedule

A plugin is a dependency, which means it can change because somebody else pushed. Claude Code checks marketplaces and installed plugins for updates after your session starts, with a random delay of up to ten minutes, so the running session keeps the versions it loaded at launch and you either get a prompt to run slash reload-plugins or the new versions arrive at your next launch. Auto-update is on by default for claude-plugins-official and most other official Anthropic marketplaces, and off by default for third-party and local development marketplaces.

The community marketplace goes a step further, and it is worth reading twice: every approved plugin is pinned to a specific commit SHA in the catalogue, and CI bumps that pin automatically as the author pushes new commits, with the public catalogue syncing nightly. Pinning exists, but it belongs to the plugin author. A version in plugin.json means users only receive updates when that field is bumped, and it wins over the version in the marketplace entry.

# What changes under you, and how to stop it changing.

# Auto-update runs AFTER your session starts, with a random delay of up
# to ten minutes, so the running session keeps the versions it launched
# with and you get a line instead of a surprise:
#
#   Run /reload-plugins to activate.
#
# ON by default: claude-plugins-official and most official Anthropic
# marketplaces. OFF by default: third-party and local development ones.

# Stop Claude Code and its marketplace plugins updating themselves:
export DISABLE_AUTOUPDATER=1

# The combination most teams actually want — pin the CLI for a sprint,
# keep the plugins current:
export DISABLE_AUTOUPDATER=1
export FORCE_AUTOUPDATE_PLUGINS=1

# Pull an update on purpose rather than on a timer, and read what the
# new version installs before you reload it:
claude plugin marketplace update my-team-tools
claude plugin details my-tool

# If you are the author, ship a version in plugin.json. Without one your
# users track whatever you last pushed: in the community catalogue every
# plugin is pinned to a commit SHA, and CI bumps that pin as you push.

A skill file changes when somebody commits to your repository. A plugin can change because its author pushed, and the change lands in the middle of whatever you were doing. Reloading is not free either: it costs tokens on the next request, and a plugin that provides MCP servers invalidates the prompt cache, so that request re-reads the entire conversation unless tool search has deferred its tools.

Eight axes on which the two genuinely differ

Most comparisons of the two collapse into one axis, sharing, and then restate it in four columns. These are the axes that change behaviour rather than vocabulary, and only the last three have ever changed a decision for me.

AxisSingle skill fileVersioned plugin bundle
DistributionCommit the folder, or copy it by handAdd a marketplace, then install by name at a scope
VersioningWhatever your git history happens to sayA version field in plugin.json that gates every update
Install frictionNone — the file on disk is the installMarketplace, install, scope, then reload or restart
Feedback loopEdited in place and picked up mid-sessionSKILL.md stays live; hooks and MCP need a reload
Review surfaceOne markdown file, visible in the diffManifest, hooks, MCP, LSP, monitors, bin, settings
Blast radiusText the model may choose to readArbitrary code running with your user privileges
Who maintains itYou, in the repository it servesThe author, on a release cadence you do not set
When it is wrongDelete the folder; the session drops itDisable or uninstall, then reload to unload it

Read the bottom half first. Distribution and install friction are cosmetic — a competent team solves either in an afternoon. Review surface, blast radius and maintenance are structural, because they decide what you have to read before you trust the thing and who is allowed to change it after you have.

What you have to read before you adopt one

For a skill, the review is the file. Open the markdown, read the frontmatter and the prose, and you have seen everything it can do, because everything it can do is ask the model to do something. For a plugin, the review is the whole bundle: the manifest, every skill and agent, hooks/hooks.json, .mcp.json, .lsp.json, the monitors, the bin directory whose executables join the Bash tool's PATH while the plugin is enabled, and a plugin-root settings.json whose agent key can activate one of the plugin's own agents as the main thread and change how Claude Code behaves by default.

The documentation is blunt about why that matters: plugins and marketplaces are highly trusted components that can execute arbitrary code on your machine with your user privileges, and Anthropic does not control what MCP servers, files or other software a plugin contains. Three things make the review tractable. The plugin detail pane lists what will install — commands, agents, skills, hooks, MCP and LSP servers — beside a context cost estimate and a last-updated date, before you install anything. The claude plugin validate command checks your own bundle, and its strict flag turns warnings into errors. And the installed list flags plugins you have not used in at least two weeks across at least ten sessions, which is the cheapest way to find bundles still charging you context for a workflow you abandoned.

Read the will-install list and the context cost estimate on every install, not only the ones that look risky. A plugin's context cost is paid on every turn of every session it is enabled in, and the estimate is on the same screen as the install button.

Three side-by-side panels comparing what a review must cover for a skill file, a plugin bundle and a plugin that ships MCP servers, escalating from one markdown file to hooks, executables on PATH and external tools reaching your data.
Each panel is a superset of the one before it, and that is the real price of bundling: the questions you must answer before adopting a workflow grow faster than the workflow does.

The rule I follow now

Three steps, in order, and most workflows stop at the first one.

  1. Write it as one SKILL.md under .claude/skills and leave it there while you are the only reader. Editing in place with no install step is worth more than versioning you are not yet using.
  2. Promote it when a second person needs the identical configuration and you cannot verify that they have it. Promote it to a skills-dir plugin first, which buys hooks, agents and MCP servers without a marketplace.
  3. Publish a marketplace only when installing by hand is itself the drift, and set a version in plugin.json before you tell anyone to install it, because the alternative is that your users track your latest commit.

The reverse case is real and I do not want to argue it away. Three people hand-copying a skill folder and pasting a hooks block into three separate settings files is precisely the drift a plugin exists to end, and at that point the version, the marketplace and the larger review surface are simply the price of a workflow that is the same for everybody. What I object to is paying that price at one reader.

So the question is not whether a workflow is important enough to be a plugin. It is whether more than one copy of it exists, and whether you could tell if those copies had drifted apart. If the answer is no, what you have is a document, and a document belongs in a file you can edit while it is running.

FAQ

Frequently Asked Questions

Short answers to what readers ask most about this topic.

Should I make my Claude Code workflow a skill or a plugin?

Start as a skill and stay there while you are the only reader. The Claude Code documentation splits it the same way: standalone configuration under .claude/ for personal workflows, project customisations and quick experiments, and plugins for sharing with teammates, versioned releases and reuse across projects. Promote it to a plugin when more than one person must run the identical configuration and you cannot verify that their copies match.

Can a Claude Code skill include hooks or an MCP server?

Not on its own. A plain skill is a SKILL.md file, while a hooks block lives in .claude/settings.json and MCP servers are configured separately, so a procedure that needs either already spans two files. Adding a .claude-plugin/plugin.json to the skill folder turns it into a plugin that can carry hooks, agents and MCP servers, and migrating moves the same hooks object into hooks/hooks.json in the same format.

What is a skills-dir plugin in Claude Code?

It is a plugin that needs no marketplace. Any folder in your skills directory that contains a .claude-plugin/plugin.json manifest loads on the next session as name at skills-dir, with no install step, and claude plugin init scaffolds exactly that layout. A project-scope one loads only after you accept the workspace trust dialog, and SKILL.md edits still apply immediately while hooks, agents and MCP changes need slash reload-plugins.

Will a Claude Code plugin update itself in the middle of my session?

The version you launched with stays loaded, but the update is fetched. Claude Code checks marketplaces and installed plugins after your session starts, with a random delay of up to ten minutes, then either prompts you to run slash reload-plugins or leaves the new version for your next launch. Auto-update is on by default for claude-plugins-official and most other official Anthropic marketplaces, and you can turn it off with the DISABLE_AUTOUPDATER environment variable.

What should I check before installing a Claude Code plugin?

Read the will-install list and the context cost estimate in the plugin detail pane before you confirm, since both appear on the same screen as the install button. Then look at what the bundle actually contains: hooks/hooks.json runs shell commands, a bin directory joins the Bash tool's PATH while the plugin is enabled, and a plugin-root settings.json can activate one of the plugin's own agents as the main thread. Anthropic's own documentation warns that plugins and marketplaces can execute arbitrary code with your user privileges.