Claude Code Skills·Claude Skills·The open SKILL.md registry for Claude
HomeLearn › Claude Code SKILL.md Frontmatter Fields: The Complete Reference

Claude Code SKILL.md Frontmatter Fields: The Complete Reference

Published 31 May 2026 · 18 min read · By a long-time Claude Code practitioner

Every Claude Code skill begins with a YAML frontmatter block. The runtime treats it as the contract between your skill and the agent: which model loads it, which tools it may touch, when it activates, who wrote it. The block is small — usually under twenty lines — but the difference between a skill that fires reliably and one Claude silently ignores almost always lives in those lines.

This is the long version of the reference. For each field I list the type, whether Claude Code itself reads it (versus the catalog or your build pipeline), the parser quirks I've tripped over, and the conventions I've seen in real distributed skills. If you want the cheat-sheet, see the quick reference. If you want to understand why a field exists and what to put in it, read on.

In this guide

Required fields: name and description

Two fields are non-negotiable. If either is missing, Claude Code will either refuse to load the skill or — worse — load it without the trigger phrase that lets it activate.

name

Type: string. Read by: Claude Code runtime, the catalog, the install client. Canonical example: name: hugo-new-content.

This is the skill's identity. Claude Code uses it as the directory name under ~/.claude/skills/, as the slug for the /skill-name slash command when user-invokable: true, and as the stable key for telemetry and dedup. Conventions I've settled on after years of doing this:

The single most common mistake: forgetting that name drives the slash invocation. If you rename a skill from review to code-review and forget to update the user-facing trigger phrase, every user with muscle memory for /review hits a dead command.

description

Type: string (typically multi-sentence). Read by: Claude Code runtime — this is the field the model reads to decide whether to activate the skill. Common length: 200–800 characters.

This is the only field Claude Code actually shows the model in the skill-discovery pass. Get this wrong and even a perfectly-written skill body never fires. The pattern that works:

  1. Open with one sentence describing what the skill does.
  2. Spell out the trigger phrases and tasks that should activate it ("Use when: the user asks to…").
  3. List negative triggers if there's a near-neighbour skill that could swallow yours ("Not for…").

Real catalog example from prompt-architect, slightly trimmed: "Create and enhance prompts, system instructions, and principle files. Capabilities: transform verbose prompts, add patterns/heuristics, optimize token usage… Use when: creating new prompts, enhancing principle files, improving system instructions." The keyword density and the explicit "Use when" trailer are what make it discoverable.

Common mistakes: writing the description like a marketing tagline ("The best Python linter for Claude Code" — model has nothing to match on), or stopping after one sentence ("Lints Python code." — never activates because there's no trigger phrase). Aim for prose, not bullet lists, and write the trigger conditions plainly.

These four fields aren't enforced, but skipping them costs you portability, safety, and discoverability in measurable ways.

tags

Type: array of strings. Read by: catalog primarily, but the runtime also surfaces them as part of the skill summary the model sees on disambiguation.

tags:
  - lang:python
  - framework:fastapi
  - type:integration
  - ai:claude

Convention: use namespace:value pairs. The ClaudSkills catalog recognises lang:, framework:, ai:, tool:, cloud:, and type: as first-class namespaces; anything else becomes a free-form tag. Five to eight tags is the sweet spot. More than fifteen and you're keyword-stuffing.

model

Type: string. Read by: Claude Code runtime. Common values: claude-sonnet-4.7, claude-opus-4.7, claude-haiku-4.7, or the alias claude-3-5-sonnet-latest.

If your skill genuinely needs a specific model — say, you depend on Opus-level reasoning to plan a multi-step refactor — pin it. If the skill is model-agnostic, omit the field; Claude Code will use whatever the user has configured. The footgun: pinning claude-3-opus-20240229 by hash and then having the runtime refuse to load months later when that snapshot is deprecated. Prefer aliases.

allowed-tools

Type: array of strings (or comma-separated string — both parse). Read by: Claude Code runtime as an allowlist for tool calls while this skill is active.

allowed-tools: Read, Write, Edit, Bash, Grep

This is the safety boundary. If you list allowed-tools: Read, Grep, the model cannot write files or run shell commands while the skill is active — even if it tries. Conversely, if you don't list the field, the skill inherits the session's full tool set, which is usually fine for read-heavy skills but dangerous for anything that touches the filesystem. The torchserve-config-generator in the catalog is a clean example: Read, Write, Edit, Bash, Grep — exactly what a config-generator needs, nothing more.

MCP tool names follow the pattern mcp__<server>__<tool>. If your skill drives a GitHub MCP, list mcp__github__create_issue rather than the bare verb.

user-invokable

Type: boolean. Read by: Claude Code runtime. Default: true on most builds.

This gates the /skill-name slash command. Set user-invokable: false when the skill is meant to fire only on model decision (matching the description trigger), never on explicit user request. Use cases: meta-skills that orchestrate other skills, observability hooks, anything where direct user invocation would skip a required precondition.

Operational fields: argument-hint, metadata.*

These fields don't change what the skill does — they change how users and the runtime interact with it.

argument-hint

Type: string. Read by: Claude Code runtime, shown to the user when they type the slash command without arguments.

argument-hint: "<title> [--ext .md] [--section posts]"

Treat this like a man-page synopsis line: angle brackets for required arguments, square brackets for optional ones, ellipsis for repeatable. The runtime displays it verbatim, so think of it as the help text for the slash command. Skills that take complex arguments without this field force users to read the SKILL.md to figure out what to pass — friction you can spare them with one line.

The hugo-new-content skill in the catalog uses this well: it documents both the positional title argument and the two optional flags (--ext, --section) in a single string. Users who type /hugo-new-content alone see the hint and immediately know what's expected.

metadata.api_base

Type: string (URL). Read by: the skill body conventionally, not the runtime. The runtime exposes it as a substituted variable inside the skill's instructions if you reference it.

metadata:
  api_base: https://api.example.com/v1
  version: 1.2.0
  rate_limit: 100/min

This is the standard place to put external API endpoints that the skill body references. The convention emerged from skills that wrap third-party APIs (Stripe, GitHub, OpenAI-compatible providers) — putting the base URL in metadata keeps it editable without rewriting the body, and lets a fork override the endpoint without touching prose.

The metadata field accepts any nested structure. Common children I've seen in the wild: api_base, version, requires_auth, rate_limit, docs_url, provider. The runtime doesn't validate the schema — anything you put here is for the skill body or downstream tooling to consume.

metadata.version

Type: free-form string. Read by: nobody at runtime, but conventional and useful.

Convention: semantic version (1.2.0) for skills with explicit changelogs, ISO date (2026-05-22) for skills that ship daily, or simple integer (v3) for skills with informal versioning. The catalog reads this when present and shows it in the "Last updated" line, but the runtime ignores it. Useful for human comparison when a user has two copies on disk and wants to know which is newer.

Free-form custom fields

You can put anything else in the frontmatter and the runtime will ignore it cleanly. The catalog has skills with risk, date_added, source, category, subcategory, even icon fields. None of these have runtime semantics — they're metadata for downstream tooling. Add fields you need; just don't expect Claude Code to read them.

Attribution: author, license, homepage, repository

None of these are read by the Claude Code runtime — the model doesn't care who wrote the skill. They matter for distribution: every public catalog, install client, and third-party importer reads them, and skipping them is the single biggest reason a well-written skill never gets credit when it's redistributed.

author

Type: string or object. Both forms parse.

# Simple string form
author: Jeremy Longshore <[email protected]>

# Object form
author:
  name: Sélim Brihi
  email: [email protected]
  url: https://example.com

The string form with bracketed email is parseable by most clients (it follows the RFC 5322 mailbox convention) but the object form is unambiguous and lets you add a homepage. If you redistribute a skill, the catalog reads author verbatim and renders it as the attribution credit on the skill page. Skills with no author land in the unattributed bucket and lose the link back to your other work.

One quirk: if you use the object form under a top-level metadata: key (as the requete-cph-licenciement-faute-grave-selim-brihi skill does — metadata.author: Sélim Brihi), the catalog still picks it up because it falls through to the nested-metadata recovery path. The top-level placement is preferred, but both work.

license

Type: string. Convention: SPDX identifier (MIT, Apache-2.0, AGPL-3.0, BSD-3-Clause, CC-BY-4.0).

The catalog matches against the SPDX list and renders a clickable license badge for recognised values. Free-form strings ("MIT License", "Open Source") render but don't link. If your skill is internal and not for redistribution, write license: proprietary explicitly — silent omission gets read as "unknown" and downstream importers will treat it as a copyright risk.

The azure-monitor-opentelemetry-ts skill in the catalog uses source: community as a workaround when the upstream license isn't clear — that's a stopgap, not a recommendation. If you control the source, pin the SPDX identifier.

homepage and repository

Type: string (URL). Read by: catalog and install clients.

The convention from package.json applies here: homepage is the human-facing project page (often the README on GitHub, sometimes a separate docs site); repository is the git clone URL or the GitHub repo page. Both render as links in the catalog. If you only have one URL, use homepage — installers fall back to it when repository is missing.

Catalog-only fields the runtime ignores

A small set of fields appear on roughly half the skills in the public catalog but are never read by Claude Code. They're written by the ClaudSkills miner — or by other catalog scrapers — and exist purely for downstream tooling. If you're authoring a skill, you don't need to write any of these. If you're inheriting a SKILL.md and see them, you can leave them or strip them without consequence.

source-url / source-repo

Written by the miner to record where the SKILL.md was harvested from. Format is a git or HTTPS URL. The catalog uses these to compute attribution credits when no explicit author is present. If you fork a skill and want to credit upstream, prefer setting author and homepage explicitly rather than relying on these scraped fields — they're informational, not contractual.

daily-eligible

Type: boolean. Written by the catalog when a skill clears the quality threshold for the daily-pick pool. Roughly the top 12% of the catalog carries this. The runtime ignores it completely; the mobile companion and SOTD generator read it. Don't set this yourself — the catalog computes it from content signals you can't fake.

featured

Type: boolean. Editorial flag set by hand for the small set of skills that get featured-card placement on the catalog homepage. Like daily-eligible, the runtime ignores it. Don't set it in your source SKILL.md; it'll be overwritten on the next ingest pass anyway.

date_added / risk

Older catalog scrapers wrote these and you'll see them on skills imported from earlier indexers. date_added is ISO date; risk is one of unknown, low, medium, high based on permissions the skill requests. Neither is read by the runtime. The azure-monitor-opentelemetry-ts skill carries both — that's a hint it was imported, not authored from scratch.

The general rule

If a field looks like it's describing the skill (what it does, who wrote it, what it costs to run), it's authored. If it looks like it's describing the catalog's view of the skill (where it was found, when it was added, what bucket it fell into), it's catalog-written. The runtime cares about the first set and nothing else.

One subtle consequence: if you're vendoring a public skill into a private repo, you can safely strip every catalog-only field and end up with a cleaner SKILL.md. The runtime won't notice; only re-publishing the skill back to a public catalog requires you to rebuild them.

The frontmatter delimiter and newline rules

The frontmatter block is delimited by three hyphens on a line by themselves, both above and below. This is the YAML standard for inline document headers and it's strict.

---
name: my-skill
description: Does the thing.
---

# My Skill

Body starts here.

Things that break this and produce silent runtime failures:

The runtime's parser is permissive about the body — it'll handle Windows line endings, mixed tabs and spaces, embedded YAML in code blocks — but it's strict about the frontmatter envelope. When in doubt, run your file through yq or python -c 'import yaml,sys; print(yaml.safe_load(open(sys.argv[1]).read().split("---")[1]))' to confirm the YAML parses cleanly outside Claude Code first.

One operational tip: keep the frontmatter under twenty lines if you possibly can. The model sees this block during skill discovery, and longer blocks eat into the context budget for trigger-phrase matching. If you have lots of metadata, prefer nesting under metadata: over flat keys at the top level — it groups visually and parses identically.

YAML pitfalls: quoting, multiline, empty arrays

YAML has more ways to encode a string than any reasonable format should. Most of them work fine; a small set bites every skill author at least once.

Colons inside string values

YAML treats : followed by whitespace as a key-value separator. A description that contains a colon — and most natural-language descriptions do — must be quoted:

# Wrong — parses as { 'description': 'Use this skill when', 'the user': '...' }
description: Use this skill when: the user wants help.

# Right — quote the whole string
description: "Use this skill when: the user wants help."

# Also right — use the block-scalar form
description: |
  Use this skill when: the user wants help.

The single-quote form ('…') and double-quote form ("…") have different escaping rules: in single-quotes, the only escape is '' for a literal apostrophe; in double-quotes, the C-style \n, \t, \" all work. For natural language, prefer single-quotes — fewer surprises.

Folded vs literal multiline strings

For long descriptions, the block-scalar forms are cleaner:

# Folded (>) — newlines become spaces, blank lines preserve paragraph breaks
description: >
  Aggregates MCP Servers, Skills, Rules, and Prompts.
  Supports search, category browsing, and project-based recommendations.

# Literal (|) — preserves newlines exactly as written
description: |
  Aggregates MCP Servers, Skills, Rules, and Prompts.
  Supports search, category browsing, and project-based recommendations.

Folded is what you want for prose — it collapses line wraps so the model sees a single readable paragraph. Literal is for structured content where line breaks matter (a small ASCII table, a code snippet). Skills like eac in the catalog use folded; skills with embedded examples in the description use literal.

Arrays: inline vs block

# Inline (flow style)
allowed-tools: [Read, Write, Edit, Bash, Grep]

# Block style — preferred for diffability
allowed-tools:
  - Read
  - Write
  - Edit
  - Bash
  - Grep

# Comma-separated string — also works for allowed-tools specifically
allowed-tools: Read, Write, Edit, Bash, Grep

All three parse. Block style is what I default to because git diffs are cleaner: adding one tool is a one-line addition rather than a whole-line rewrite. For two-or-three-item arrays the inline form is fine.

Empty arrays vs missing fields

This catches people. tags: [] and tags: (no value) both parse to an empty list in most YAML libraries — but downstream tooling sometimes treats them differently. The catalog miner reads tags: [] as "author explicitly tagged this skill with nothing," and skips tag-inference; it reads a missing tags field as "unspecified, please infer." If you want auto-inference, omit the field. If you want to explicitly opt out, write tags: []. The same logic applies to allowed-tools: an empty list is a hard "no tools," a missing field falls back to session defaults.

Booleans and the Norway problem

YAML 1.1 famously interpreted NO, off, n as boolean false. Most modern parsers (and Claude Code's) use YAML 1.2, where only true and false are booleans. If you write name: NO meaning the country code, you're fine — but it's worth knowing why old guides obsessed about quoting. For user-invokable specifically, use lowercase true or false — capitalised or string forms aren't guaranteed to parse as booleans across every importer.

What the catalog actually carries

Across the public catalog of 76k+ skills, frontmatter coverage is uneven in instructive ways. The numbers below are rough but illustrate the patterns I see when reviewing third-party skills.

If you want a quick way to assess a third-party skill before installing: open the SKILL.md, look at the frontmatter, and check whether allowed-tools and license are present. Skills that take the time to write those fields tend to take the time to write the body well too — the correlation isn't perfect but it's strong.

The catalog stats page tracks this distribution in more detail and updates daily.

What to fill in if you want your skill to travel

If you're publishing a skill that other people will install — whether to a public catalog or just to your team's shared ~/.claude/skills/ — here's the floor I'd hold for myself before shipping. Required-required, recommended-required, and nice-to-have.

Floor

---
name: my-skill
description: "One sentence on what the skill does. Use when: trigger condition one, trigger condition two. Not for: near-neighbour case."
allowed-tools: Read, Write, Edit, Bash, Grep
author: Your Name <[email protected]>
license: MIT
---

Five fields. name and description for the runtime to find and activate the skill. allowed-tools so the model can't do something unexpected. author and license so other people can credit you and reason about redistribution.

Better

---
name: my-skill
description: "One sentence on what the skill does. Use when: trigger condition one, trigger condition two. Not for: near-neighbour case."
tags:
  - lang:python
  - framework:fastapi
  - type:integration
allowed-tools: Read, Write, Edit, Bash, Grep
user-invokable: true
argument-hint: "<input-file> [--dry-run]"
author:
  name: Your Name
  email: [email protected]
  url: https://example.com
license: MIT
homepage: https://github.com/you/my-skill
repository: https://github.com/you/my-skill
metadata:
  version: 1.0.0
---

The additions: tags so the catalog can group your skill correctly, user-invokable and argument-hint for users who want to invoke explicitly, structured author, homepage and repository for attribution, and a metadata.version field that lets users compare installed versions against upstream.

What I'd skip

I wouldn't pin model unless I'd actually tested the skill against the alternative tiers and found it wanting. I wouldn't write daily-eligible or featured — those are catalog flags, not author flags. I wouldn't add custom top-level fields that conflict with established conventions; if I need scratch metadata I put it under metadata: where it can't collide with future runtime fields.

Test before you publish

One last habit worth forming: every time you edit the frontmatter, re-run the skill once with a typical user prompt and confirm the model still activates it. Description tweaks especially can change discoverability in subtle ways. A regression where your skill stops firing for the trigger phrase you intended is invisible until you happen to try it — and when you're publishing for others to install, that's a worse failure mode than a parse error, because parse errors at least surface in the load log. Silent inactivity surfaces as a complaint email six months later.

For the longer-form companion piece on writing the body of a SKILL.md, see writing a SKILL.md file. For a one-page cheat sheet of the same fields covered here, see the quick reference.

Frequently asked questions

Does Claude Code require a name field in SKILL.md?
Yes. Without a top-level name field, Claude Code cannot resolve slash invocations and the skill becomes invisible to the runtime. The name must match the directory under ~/.claude/skills/ for filesystem-walking tools to work correctly.
What happens if I leave allowed-tools out of my SKILL.md?
The skill inherits the session's full tool set, which usually works but removes a safety boundary. Listing allowed-tools explicitly pins the tools the model can invoke while the skill is active, so a read-only skill cannot accidentally write files.
Can description contain a colon?
Yes, but you must quote the string or use a block-scalar form (| or >). Unquoted colons followed by whitespace are interpreted as YAML key-value separators and will break the parse silently.
Should I pin a specific model in frontmatter?
Only if you've tested the skill against alternative tiers and found it needs the specific model. Most skills are model-agnostic and pinning a deprecated snapshot causes the runtime to refuse to load the skill months later. Prefer aliases over hash-pinned versions.
What's the difference between user-invokable: false and just omitting it?
On most builds, user-invokable defaults to true. Omitting it leaves the slash command enabled. Setting it to false explicitly disables /skill-name invocation, which is the right move for orchestration skills meant to fire only on model decision.
Do I need to write daily-eligible or featured in my SKILL.md?
No. Both are catalog-written flags that the runtime ignores. Setting them in your source file has no effect and the catalog will overwrite them on the next ingest pass based on its own quality signals.
What's the safest way to escape special characters in description?
Use single quotes for natural-language prose — the only escape is two consecutive single quotes for a literal apostrophe. Use double quotes only when you need C-style escapes like \n or \t. Block-scalar forms (| and >) sidestep most escaping problems entirely.

Found a bug or want a topic covered? Email [email protected] or open an issue via GitHub.