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.
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.
nameType: 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:
code-review-agent, not CodeReviewAgent or code_review_agent. The slash-command surface is case-sensitive and underscores look ugly in a URL.~/.claude/skills/foo/SKILL.md with name: bar, the runtime will resolve calls to bar but anything that walks the filesystem (an install client, a sync script) will mis-key the skill. Don't fight your own tooling.azure-monitor-opentelemetry-ts; past that, prefer namespacing.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.
descriptionType: 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:
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.
tagsType: 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:claudeConvention: 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.
modelType: 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-toolsType: 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, GrepThis 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-invokableType: 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.
These fields don't change what the skill does — they change how users and the runtime interact with it.
argument-hintType: 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_baseType: 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/minThis 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.versionType: 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.
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.
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.
authorType: 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.comThe 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.
licenseType: 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 repositoryType: 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.
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-repoWritten 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-eligibleType: 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.
featuredType: 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 / riskOlder 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.
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 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:
---. If your file starts with name: directly, Claude Code treats the entire file as markdown body. The skill loads but has no name, no description, no trigger — it's invisible.---. The parser keeps reading YAML until it hits the next document marker. If you have a YAML-shaped code block later in the body (a Kubernetes manifest, a GitHub Actions snippet), the parser may consume it and produce confusing errors. Always close the block. --- (with a space) does not parse. The three hyphens must be at column zero.--- and fails to recognise the document start. If your skill works on Linux but fails on Windows, check for a BOM first.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 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.
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.
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.
# 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, GrepAll 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.
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.
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.
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.
name and description: 100%. Anything missing these is rejected at admission.author: roughly 50%. The other half were either authored anonymously or harvested from sources without explicit attribution. The 50% with author are mostly skills that originated from a personal repo where the author wanted credit.license: roughly 30%. Most missing-license skills inherit from the upstream repo's LICENSE file, which the catalog cross-references — but absent that file, the skill is treated as license-unknown and downstream importers may avoid it.tags: roughly 65%. Skills authored after the tag-namespace convention took hold consistently tag with lang:, framework:, etc. Older skills get tags inferred by the catalog at ingest time, which is workable but lower-fidelity.allowed-tools: small minority — under 10%. This is the field I'd most like to see more authors fill out. Without it, a skill inherits the session's full tool set, which means a "reads-the-repo" skill can theoretically rm-rf if the model misfires. Even allowed-tools: Read, Grep is a meaningful safety pin.model: small minority — under 15%. Pinning a model only matters when the skill genuinely needs a specific tier. Most don't, and the omission is correct.argument-hint: low — maybe 5%. Most skills that take arguments still don't document them in frontmatter. Adding this one line is the cheapest UX win you can ship.metadata.api_base: only present on skills that wrap external APIs — maybe 8% of the catalog. The skills that have it tend to be high-quality wrappers from authors who've shipped multiple API integrations.homepage: roughly 17%. repository: often inferred from source-url by the catalog rather than authored. Setting these explicitly produces cleaner attribution.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.
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.
---
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.
---
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.
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.
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.
Found a bug or want a topic covered? Email [email protected] or open an issue via GitHub.
SKILL.md files, not affiliated with, endorsed by, or sponsored by Anthropic.