How do you build a custom OpenClaw skill with SKILL.md?
Build an OpenClaw skill from a folder and a SKILL.md: the documented frontmatter fields, where the file loads from, local testing and ClawHub publishing.
A custom skill is a folder with a SKILL.md file in it, and two required frontmatter fields: name and description. Put the folder in your workspace skills/ directory, write the instructions in Markdown below the frontmatter, and run openclaw skills list to confirm it loaded. The version behind every command and field below is v2026.9.5, the latest release listed in the OpenClaw docs as of September 19, 2026.
If you learned the format from an earlier tutorial that showed author, tools, triggers, permissions or priority keys, none of those appear in OpenClaw's current documentation. Neither does the layout those tutorials use, which files personal skills under a custom subfolder of the managed skills root. version is the one partial exception: ClawHub's skill format page shows it in a frontmatter example, but the publishing CLI picks the number for you. The documented schema is smaller than the one those tutorials describe, and the section below lists it field by field.
#What is an OpenClaw skill?
A directory with a Markdown file in it. OpenClaw's creating-skills page states it directly: "Skills teach the agent how and when to use tools. Each skill is a directory containing a SKILL.md file with YAML frontmatter and markdown instructions." The skills reference opens the same way, calling skills "markdown instruction files that teach the agent how and when to use tools".
There is no build step and no compiled artifact. The frontmatter tells OpenClaw the skill's name, what it is for, and what it needs in order to run; the Markdown body tells the model what to do once it decides to use it. OpenClaw's own words for the split are that "The frontmatter defines metadata; the body gives the agent instructions."
The docs also draw a line between skills and plugins. The skills reference notes that plugins can ship their own skills "by listing skills directories in openclaw.plugin.json", and the skills CLI page sends plugin packages to the separate openclaw plugins command. A skill on its own is just the folder.
OpenClaw's skills reference says it "follows the AgentSkills spec", and that frontmatter "is parsed as YAML first; if that fails, it falls back to a single-line-only parser." Nested metadata blocks are "flattened to a JSON string and re-parsed as JSON5", which is why both the inline and the multi-line forms of metadata.openclaw shown in the docs work.
#Where do skills live?
In your workspace skills/ folder. The creating-skills page says "Skills live in your workspace skills/ folder:" and gives the first command as mkdir -p ~/.openclaw/workspace/skills/hello-world. Subfolders are allowed for organization: the same page notes that "the skill is still named by the SKILL.md frontmatter, not the folder path", and the skills reference adds that OpenClaw "discovers a skill whenever SKILL.md appears anywhere under a configured root (up to 6 levels deep)".
That workspace folder is one of several roots. The skills reference publishes the full order and says that "When the same skill name appears in multiple places, the highest source wins." The table below carries the docs' rows unchanged, with the docs' em dash markers for highest and lowest rewritten in parentheses.
| Priority | Source | Path |
|---|---|---|
| 1 (highest) | Workspace skills | <workspace>/skills |
| 2 | Project agent skills | <workspace>/.agents/skills |
| 3 | Personal agent skills | ~/.agents/skills (default state only) |
| 4 | Managed / local skills | <state-dir>/skills |
| 5 | Workshop skills | <state-dir>/agents/<agentId>/agent/workshop-skills |
| 6 | Bundled skills | shipped with the install |
| 6 | Custodian skills | shipped; configured Custodian agent only |
| 7 (lowest) | Extra directories | skills.load.extraDirs + plugin skills |
Two of those roots are the ones you will actually type. The workspace root is where you author. The managed root is where shared installs land: the skills reference says openclaw skills install "installs into the active workspace skills/ directory by default", and that --global installs "into the shared ~/.openclaw/skills directory, visible to all local agents unless agent allowlists narrow it." The skills CLI page states the same boundary for all three commands that touch disk: "By default, install, update, and verify target the active workspace skills/ directory; with --global, they target the shared managed skills directory."
Installing someone else's skill from the registry is one command:
openclaw skills install @owner/<slug>openclaw skills install @owner/<slug> --globalThe skills CLI page also documents openclaw skills install git:owner/repo@ref for a Git source and openclaw skills install ./path/to/skill --as my-tool for a local directory, and notes that "Git and local directory installs expect SKILL.md at the source root."
Folder name and name should agree. The creating-skills page's naming rules say to "Keep the directory name and frontmatter name aligned", and the skills reference explains what happens when they do not: "The skill's name and slash command come from the name frontmatter field (or the directory name when name is missing)."
#What goes in SKILL.md?
Two required fields, six optional top-level keys, and an optional metadata.openclaw block. The table lists every field documented across OpenClaw's creating-skills page, its skills reference, and ClawHub's skill format page, minus ClawHub's version, which the publishing CLI sets for you.
| Field | Required | What it does |
|---|---|---|
name | Yes | "Unique slug using lowercase letters, digits, and hyphens" |
description | Yes | "One-line description shown to the agent and in discovery output". Keep it "one line and under 160 characters" |
user-invocable | No, default true | "When true, the skill is exposed as a user-invocable slash command." |
disable-model-invocation | No, default false | "When true, OpenClaw keeps the skill's instructions out of the agent's normal prompt." It stays reachable as a slash command while user-invocable is also true |
command-dispatch | No | "When set to tool, the slash command bypasses the model and dispatches directly to a registered tool." |
command-tool | No | "Tool name to invoke when command-dispatch: tool is set." |
command-arg-mode | No, default raw | "For tool dispatch, forwards the raw args string to the tool with no core parsing." |
homepage | No | URL shown as the Website link in the macOS Skills UI. Also supported as metadata.openclaw.homepage |
metadata.openclaw.requires.bins | No | "Each binary must exist on PATH." |
metadata.openclaw.requires.anyBins | No | "At least one binary must exist on PATH." |
metadata.openclaw.requires.env | No | "Each env var must exist in the process or be provided via config." |
metadata.openclaw.requires.config | No | "Each openclaw.json path must be truthy." |
metadata.openclaw.os | No | "Hard platform filter" over darwin, linux and win32 |
metadata.openclaw.always | No | "When true, include the skill whenever its os requirement is compatible, bypassing requires.bins, requires.anyBins, requires.env, and requires.config." |
metadata.openclaw.primaryEnv | No | "Env var name associated with skills.entries.<name>.apiKey." |
metadata.openclaw.envVars | No | "Environment variable declarations with name, optional required, and optional description." |
metadata.openclaw.emoji | No | "Optional emoji shown in the macOS Skills UI." |
metadata.openclaw.install | No | "Optional installer specs used by the macOS Skills UI (brew / node / go / uv / download)." |
metadata.openclaw.skillKey | No | "Override the skill's invocation key." |
metadata.openclaw.nix | No | "Nix plugin spec (see README)." |
metadata.openclaw.config | No | "Clawdbot config spec (see README)." |
The two required fields and the six optional top-level keys come from OpenClaw's own SKILL.md reference. The gating keys under requires, plus os, always, primaryEnv, emoji and install, are documented on the skills reference under Gating. envVars, skillKey, nix and config are documented on ClawHub's skill format page, which also records the block's aliases: "Declare your skill's runtime requirements under metadata.openclaw (aliases: metadata.clawdbot, metadata.clawdis)."
Leaving the block out is fine. The skills reference is explicit that "A skill with no metadata.openclaw block is always eligible unless explicitly disabled."
The gating check runs on the host. The skills reference notes that "requires.bins is checked on the host at skill load time. If an agent runs in a sandbox, the binary must also exist inside the container." The same page warns that skills.entries.*.env and skills.entries.*.apiKey "inject secrets into the host process for that agent turn only", not into the sandbox.
#How does the agent decide to use a skill?
By reading descriptions. OpenClaw's skills reference describes the per-run sequence: it resolves the eligible skill list, injects any configured env and API keys, and then builds the prompt, where "Eligible skills are compiled into a compact XML block and injected into the system prompt." The cost is published at "~97 characters + your name, description, and location field lengths" per skill, which is why its token-impact note closes with "Keep descriptions short and descriptive to minimize prompt overhead."
So the description is the matching surface. The model sees the name, the description and the location of every eligible skill, and reads the full SKILL.md only once it picks one. A description that names the trigger condition in plain language does more work than a long body.
A person can also name the skill outright. The skills reference documents a $ reference in the Control UI composer that "matches the current agent's eligible, user-invocable skills and tells the model to read each referenced SKILL.md before acting", with a ceiling: "A single message can reference up to eight distinct skills". On the command side, the slash commands page says "/skill <name> [input] always works as the generic entrypoint", and that skills "may register as direct commands using their declared skill name."
Three frontmatter keys change that behavior:
user-invocableistrueby default, which is what exposes the slash command at all.disable-model-invocation: truetakes the skill off the model's menu without removing it from yours. The skills reference: skills with it set "stay out of the$picker and the model's normal prompt, so the model cannot select them on its own. An authorized explicit$skill-namereference still invokes them".command-dispatch: toolremoves the model from the path entirely. The slash commands page notes that by default "skill commands route to the model as a normal request", and that a skill can declarecommand-dispatch: tool"to route directly to a tool (deterministic, no model involvement)."
Gating happens before any of this. The skills reference says OpenClaw "loads bundled skills plus any local overrides, and filters them at load time based on environment, config, and binary presence", so a skill whose requires.env variable is unset never reaches the prompt in the first place.
#Example: a morning brief skill
Create the folder first. OpenClaw's creating-skills page opens with mkdir -p ~/.openclaw/workspace/skills/hello-world; the same command with this skill's folder name is:
mkdir -p ~/.openclaw/workspace/skills/morning-briefThen write SKILL.md inside it. This file uses only fields from the table above, and declares every environment variable its body mentions. WEATHER_API_KEY is the credential, so it appears under requires.env, as primaryEnv, and in envVars. MORNING_BRIEF_CITY is optional, so it appears only in envVars with required: false, which is what ClawHub's skill format page asks for: "Do not add optional entries to requires.env, because requires.env means the skill cannot run without them." The homepage is optional; the creating-skills publish step asks you to "Add a homepage URL if you have a project page."
---name: morning-briefdescription: Assemble a short morning brief with the day's weather and the top open items when the user asks for one.homepage: https://github.com/<your-handle>/morning-briefmetadata: openclaw: requires: bins: - curl env: - WEATHER_API_KEY primaryEnv: WEATHER_API_KEY envVars: - name: WEATHER_API_KEY required: true description: Weather provider API token used for the forecast lookup. - name: MORNING_BRIEF_CITY required: false description: Default city for the forecast when the user does not name one. emoji: "☀️"---# Morning briefWhen the user asks for a morning brief, produce one short summary in thisorder, and stop when you have covered all three parts.1. Weather. Use the `exec` tool to fetch the forecast, then report the current conditions, the high and the low in one sentence.```bashcurl -s "https://api.example-weather.invalid/v1/forecast?city=$MORNING_BRIEF_CITY" \ -H "Authorization: Bearer $WEATHER_API_KEY"```2. Open items. List anything the user flagged yesterday that is still open, one line each, newest first.3. One suggestion. Name the single thing worth doing first today, and say why in one clause.Keep the whole brief under 150 words. If the forecast request fails, say soin one line and continue with the remaining parts rather than stopping.The description is 104 characters, inside the documented limit of one line under 160. The body carries the length, which is what the token-impact note in the skills reference asks for: the description is paid for on every turn, the body only when the skill is used.
Wire the credential in openclaw.json. The block below is the one on OpenClaw's creating-skills page, with the skill name and variable changed:
{ skills: { entries: { "morning-brief": { enabled: true, apiKey: { source: "env", provider: "default", id: "WEATHER_API_KEY" }, }, }, },}The creating-skills page describes the effect in one sentence: "The key is injected into the host process for that agent turn only." The skills config reference adds that skills.entries.<key>.apiKey is a "Convenience field for skills that declare metadata.openclaw.primaryEnv" and accepts either a plaintext string or a SecretRef. Use the SecretRef form. API key setup covers where the underlying value should live.
The creating-skills page documents {baseDir} for files that ship next to SKILL.md: the agent "resolves {baseDir} against the skill's own directory", so a helper script can be referenced as `{baseDir}/scripts/run.sh` without hardcoding a path that breaks when the skill is installed somewhere else.
#How do you test a skill locally?
By loading it, not by validating it. OpenClaw's skills CLI page documents search, install, update, verify, list, info, check, curator, library and workshop subcommands. No validate subcommand appears there, so there is no documented way to lint a SKILL.md before OpenClaw reads it. Older tutorials that show openclaw skills validate <path> are describing a command the current documentation does not list.
What the docs do give you is a load, an inspection and a real turn:
openclaw skills listopenclaw skills info morning-briefopenclaw skills checkopenclaw agent --message "give me a morning brief"openclaw skills list is the verification step the creating-skills page uses after writing the file, and openclaw agent --message "give me a greeting" is its test step, shown there with the hello-world prompt. openclaw skills check is the prerequisite report: the skills CLI page says it "reports missing prerequisites independently of agent exclusion", so a skill missing curl or WEATHER_API_KEY shows up under Missing requirements rather than silently vanishing. openclaw skills info <name> prints one skill's details, and the same page notes that it "resolves an exact skill name before a metadata key."
A broken file will not take the others down. The skills reference states that "Invalid skill files are reported and skipped; valid siblings can still load", so a skill that disappears from openclaw skills list after an edit is the signal that its frontmatter did not parse.
Edits are usually picked up for you. The creating-skills page says "OpenClaw watches SKILL.md files under skills roots by default", and tells you what to do when it is not enough: "If the watcher is disabled or you are continuing an existing session, start a new one so the agent receives the refreshed list", with /new from chat or openclaw gateway restart as the two routes. The skills reference adds that the refreshed list "is picked up on the next agent turn in the same session".
To invoke the skill by name rather than hoping the model picks it, the creating-skills page gives the explicit form: "Use /skill hello-world to invoke it explicitly by name."
For a broader sweep, openclaw doctor covers skills among other subsystems. Its CLI page describes it as "Health checks and quick fixes for the gateway, channels, plugins, skills, model routing, local state, and config migrations."
#How do you publish to ClawHub?
With the standalone clawhub CLI. OpenClaw's own creating-skills page routes publishing there rather than to an openclaw subcommand, and the ClawHub CLI reference names the package: "CLI package: clawhub, bin: clawhub."
npm i -g clawhubclawhub loginclawhub skill publish ./morning-brief --dry-runclawhub skill publish ./morning-brief --owner <handle> --changelog "First release"The first three lines are the creating-skills page's publish sequence plus the dry run; the ClawHub CLI reference lists clawhub skill publish ./my-skill --dry-run among its examples and says --dry-run "resolves the publish without uploading". clawhub login prints "a one-time code and verification URL" rather than opening a browser.
Five details decide whether a publish does what you expect.
The slug comes from the folder. ClawHub's skill format page says slugs are "Derived from folder name by default." Settle the folder name before the first publish; if you need to change it afterwards, the CLI reference documents clawhub skill rename <skill> <new-name>, which keeps "the previous slug as a redirect alias".
The owner is a handle that already exists. The creating-skills page defines it: "An owner is a ClawHub publisher handle, such as @alice or @your-org. Your account has a personal owner." The publishing page adds "Use --owner <handle> when publishing to an org owner. Omit it to publish as the authenticated user", and the CLI reference restricts it to publishers where "the actor has publisher access." ClawHub also requires the two to match: "Package scopes must match the ClawHub publisher handle exactly."
Versions are the CLI's job. The ClawHub CLI reference: "New skills default to 1.0.0; changed skills default to the next patch version." The publishing page says the same in prose, and adds "Pass --version only when you need an explicit version." ClawHub's skill format page shows a version key in its basic frontmatter example, but you do not have to bump it by hand for the CLI to pick the right number. Unchanged content is a no-op: publish "Compares the local bundle fingerprint with ClawHub and exits successfully when the content is already published."
Undeclared environment variables get flagged. ClawHub's skill format page explains the check in its own words: "If your code references TODOIST_API_KEY but your frontmatter doesn't declare it under requires.env, primaryEnv, or envVars, the analysis will flag a metadata mismatch." That is the reason the example above declares WEATHER_API_KEY in all three places. Any one of the three clears the check; the example uses all three because requires.env is what gates the skill locally and primaryEnv is what skills.entries.<name>.apiKey binds to.
A changelog is optional and free-form. The ClawHub CLI reference documents the workflow input that maps to it and describes the flag's behavior: changelog "reaches the CLI verbatim, the way skill publish --changelog stores it", and "the CLI already reads an omitted --changelog as empty."
Two rules are worth knowing before you upload. ClawHub's skill format page caps "Total bundle size: 50MB." And the license is not negotiable: "All skills published on ClawHub are licensed under MIT-0", with the CLI reference repeating that "Publishing a skill means it is released under MIT-0 on ClawHub." The format page adds "Do not add conflicting license terms in SKILL.md; ClawHub does not support per-skill license overrides."
If validation fails, nothing lands. The publishing page: "If validation fails, nothing is published. New releases may also stay out of normal install and download surfaces until review finishes."
#Common mistakes
Using an environment variable the frontmatter never declares. ClawHub's security analysis compares the two, and the format page says a body that references a variable not listed "under requires.env, primaryEnv, or envVars" produces a metadata mismatch. The same declaration is what gates the skill locally, since requires.env means "Each env var must exist in the process or be provided via config."
Writing a description longer than one line. The creating-skills page's rule is to "keep it one line and under 160 characters", and the skills reference explains the cost of ignoring it: when the rendered block exceeds skills.limits.maxSkillsPromptChars, OpenClaw "first preserves as many skill identities (name and location) as the description-free compact format can fit", and "If no description budget remains, descriptions are omitted." A description the model never sees cannot get the skill selected.
Putting the folder somewhere that is not a skill root. The eight sources in the loading-order table above are the whole list. A custom subfolder of the managed skills root is not among them, and neither is Codex CLI's own skills directory: the skills reference says it is not an OpenClaw skill root and points at openclaw migrate plan codex and openclaw migrate codex for moving those files across.
Carrying frontmatter fields over from an older tutorial. tools, triggers and permissions are not part of the documented schema. OpenClaw's SKILL.md reference lists name and description as the required fields and user-invocable, disable-model-invocation, command-dispatch, command-tool, command-arg-mode and homepage as the optional ones; the gating keys live under metadata.openclaw. Nothing in the documented format grants a skill access to a tool: command-tool names one dispatch target for a slash command, and nothing takes a list of trigger phrases. Tool access is an agent-level concern: the skills reference notes that a skill allowlist "is not a host shell authorization boundary" and that an eligible skill "does not grant tool access."
Letting the folder name and name drift apart. The creating-skills naming rules say to keep them aligned, the skills reference falls back to the directory name only "when name is missing", and ClawHub's format page expects name to match the parent directory for portable Agent Skills.
Trusting a community skill because it installed cleanly. The skills reference says to treat third-party skills as untrusted code and to "Read them before enabling." openclaw skills verify @owner/<slug> asks ClawHub for the skill's trust envelope, and --card prints the generated Skill Card instead of JSON.
#What to read next
- API key setup covers where the value behind
primaryEnvshould actually live, including the SecretRef sources OpenClaw accepts inopenclaw.json. - Security hardening is the companion read for the untrusted-code warning above, once you start installing skills you did not write.
- The best OpenClaw skills in 2026 is the browse-before-you-build step: the creating-skills page's own advice is to browse community skills at clawhub.ai before building from scratch.
Every field, path and command on this page was checked against the OpenClaw and ClawHub documentation on September 19, 2026. The metadata block has already been renamed more than once, which is why metadata.clawdbot and metadata.clawdis are still accepted as aliases, so check the sources below before you copy a frontmatter block out of any tutorial, including this one.
Frequently asked questions
- What is an OpenClaw skill?
- A folder with a SKILL.md file in it. OpenClaw's skills reference describes skills as markdown instruction files that teach the agent how and when to use tools, and says each skill lives in a directory containing a SKILL.md file with YAML frontmatter and a markdown body. There is no compiled plugin and no build step.
- Where does a custom SKILL.md file go?
- In your workspace skills folder. OpenClaw's creating-skills page says skills live in your workspace skills/ folder and gives mkdir -p ~/.openclaw/workspace/skills/hello-world as the first step. The docs also list a shared managed root at ~/.openclaw/skills, which is where openclaw skills install --global puts a skill so that all local agents can see it.
- Which SKILL.md frontmatter fields are required?
- Two: name and description. OpenClaw's creating-skills reference lists name as a unique slug using lowercase letters, digits, and hyphens, and description as a one-line description shown to the agent and in discovery output, which the same page says to keep to one line and under 160 characters. Everything else, including the metadata.openclaw block, is optional.
- How does the agent decide to use a skill?
- From the description. OpenClaw compiles eligible skills into a compact XML block and injects it into the system prompt, spending roughly 97 characters per skill plus the length of its name, description and location, so the description is the text the model matches against. A user can also name a skill explicitly with a $skill-name reference or the /skill <name> command.
- How do you give a skill an API key?
- Declare the variable in metadata.openclaw, then wire the value in openclaw.json. Name the variable under requires.env or envVars and point primaryEnv at it, then set skills.entries.<name>.apiKey to a SecretRef such as { source: env, provider: default, id: WEATHER_API_KEY }. OpenClaw's creating-skills page says the key is injected into the host process for that agent turn only and does not reach the sandbox.
- How do you publish a skill to ClawHub?
- With the standalone clawhub CLI, not the openclaw command. Install it with npm i -g clawhub, run clawhub login, then clawhub skill publish ./my-skill. ClawHub derives the slug from the folder name by default, and the CLI assigns versions: new skills default to 1.0.0 and changed skills default to the next patch version.