How to Create Custom OpenClaw Skills: A Complete Developer Tutorial
OpenClaw ships with built-in capabilities, but its real power comes from custom skills. A skill is a self-contained unit of functionality defined in a Markdown file. No compiled code, no build system -- just a Markdown file that tells OpenClaw what a skill does, what tools it can use, and how to behave.
What Is a Skill?
A skill is a SKILL.md file that defines metadata (name, triggers, version), instructions for the LLM, tool declarations (shell, HTTP, filesystem), and examples of expected behavior.
When a message arrives, OpenClaw's gateway matches it against registered skills. If a match is found, the skill's instructions and tool declarations are injected into the LLM prompt, giving the model the knowledge and permissions to complete the task. You extend OpenClaw without writing traditional code -- the LLM is the execution engine; the skill file is the blueprint.
Directory Structure
~/.openclaw/
workspace/
skills/
weather-checker/
SKILL.md
url-shortener/
SKILL.md
config.json
daily-digest/
SKILL.md
templates/
digest.md
Each skill gets its own directory under ~/.openclaw/workspace/skills/. The only required file is SKILL.md.
The SKILL.md Format
A skill file has YAML frontmatter and a Markdown body. Here is the simplest possible skill:
---
name: hello-world
description: Responds with a greeting
version: 1.0.0
triggers:
- "say hello"
- "greet me"
---
# Hello World
When the user asks you to say hello, respond with a friendly greeting
that includes the current date and time.
Full Frontmatter Reference
---
name: weather-checker
description: Checks current weather for any city using wttr.in
version: 1.0.0
triggers:
- "weather in {city}"
- "what's the weather"
- "forecast for {city}"
author: your-username
license: MIT
tags: [weather, utility, api]
tools:
- shell
- http
dependencies: []
config:
units:
type: string
default: metric
description: Temperature units (metric or imperial)
---
Triggers support exact phrases and parameterized patterns. {city} captures a value from the user's message via fuzzy matching. Tools declare which system capabilities the skill needs -- if a skill does not declare a tool, the LLM cannot access it during execution. This is a security feature[1].
Writing Effective Instructions
The Markdown body is injected directly into the LLM prompt. Good instructions produce reliable skills; vague instructions produce unpredictable behavior.
Three principles matter most:
Be specific about output format -- tell the LLM exactly what the response should look like. Specify exact commands -- do not assume the LLM knows the right shell command or API endpoint. Handle errors explicitly -- tell the LLM what to do when an API call fails or input is invalid.
Practical Example: Weather Checker
mkdir -p ~/.openclaw/workspace/skills/weather-checker
---
name: weather-checker
description: Checks current weather conditions for any city worldwide
version: 1.2.0
triggers:
- "weather in {city}"
- "what's the weather in {city}"
- "forecast for {city}"
- "temperature in {city}"
tools:
- shell
- http
config:
units:
type: string
default: metric
tags: [weather, utility]
---
# Weather Checker
You check current weather using the wttr.in API.
## Fetching Data
Run this command:
\```bash
curl -s "wttr.in/{city}?format=j1"
\```
Replace {city} with the user's city. URL-encode spaces as +.
## Parsing the Response
Extract from `current_condition[0]`: `temp_C`, `FeelsLikeC`,
`weatherDesc[0].value`, `humidity`, `windspeedKmph`, `winddir16Point`.
## Response Format
**Weather in {City}:**
- Temperature: {temp} ({feels_like} feels like)
- Conditions: {conditions}
- Humidity: {humidity}%
- Wind: {speed} km/h {direction}
## Error Handling
- curl fails: "The weather service is currently unavailable."
- City not found: "I couldn't find weather data for '{city}'."
- No city given: "Which city would you like the weather for?"
Test it:
openclaw skills reload
openclaw skills list
openclaw chat "weather in Berlin"
Practical Example: URL Shortener
---
name: url-shortener
description: Shortens URLs using the is.gd API
version: 1.0.0
triggers:
- "shorten {url}"
- "short link for {url}"
tools:
- http
tags: [utility, links]
---
# URL Shortener
Make an HTTP GET request to:
\```
https://is.gd/create.php?format=json&url={url}
\```
Success: return the `shorturl` value.
Error code 1: "That URL appears to be invalid."
Error code 2: "That URL is blocked."
If the URL lacks a protocol, prepend https://.
Do not shorten already-shortened URLs (is.gd, bit.ly, t.co).
Practical Example: Daily Digest
---
name: daily-digest
description: Generates a daily summary of system status and tasks
version: 2.0.0
triggers:
- "daily digest"
- "morning report"
- "briefing"
tools:
- shell
- http
- filesystem
config:
city:
type: string
default: ""
tags: [productivity, automation]
---
# Daily Digest
Collect and present: system status (uptime, df -h, free -h),
weather if a city is configured (via wttr.in), pending tasks from
~/.openclaw/workspace/tasks.md, and recent agent activity from
journalctl -u openclaw --since "24 hours ago" | tail -20.
If any source fails, note it as "unavailable" -- always produce
a digest. Never fabricate data.
Testing Skills
# Test with a message
openclaw chat "weather in Paris"
# Debug mode shows trigger matching, LLM prompt, tool calls
OPENCLAW_LOG_LEVEL=debug openclaw chat "weather in Paris"
# Test trigger matching without execution
openclaw skills match "what's the weather like in London"
The development loop is fast: edit SKILL.md, run openclaw skills reload, test, repeat. No compilation or build step.
Publishing to ClawHub
openclaw hub login
openclaw hub validate ~/.openclaw/workspace/skills/weather-checker/
openclaw hub publish ~/.openclaw/workspace/skills/weather-checker/
Follow semantic versioning: patch for typo fixes, minor for new triggers or config options, major for breaking changes. After publishing, anyone can install with openclaw skill install weather-checker.
Best Practices
Be explicit about output format. The top cause of unreliable skills is vague instructions. Tell the LLM the exact format, not "respond with the result."
Specify exact commands. Provide the precise shell command or API endpoint:
# Good
\```bash
df -h --output=source,size,used,avail,pcent /
\```
# Bad
Check the disk usage using an appropriate command.
Handle errors in instructions. Every external call can fail:
If curl fails, respond: "Service unavailable. Try again later."
Do NOT retry. Do NOT guess or fabricate data.
Keep skills focused. One skill, one job. Split a kitchen-sink skill into focused ones[2].
Limit tool permissions. Only declare tools the skill actually needs. A weather checker needs http, not filesystem.
Include examples. Concrete input/output examples are one of the most effective ways to guide LLM behavior.
Common Pitfalls
Over-relying on LLM knowledge: Always provide specific API endpoints and command syntax. Built-in knowledge may be outdated.
Missing input validation: Without validation instructions, the LLM might pass unsanitized input to shell commands[3]. Specify character restrictions and length limits.
Ignoring timeouts: Use curl -s --max-time 10 to prevent hanging.
Not testing across models: A skill working with Claude might behave differently with GPT-4 or a local LLM.
Assuming tool availability: Include fallbacks like free -h 2>/dev/null || vm_stat.
Skill Chaining
Skills can declare dependencies on other skills for multi-step workflows:
dependencies:
- daily-digest
- weather-checker
OpenClaw loads instructions from dependent skills during execution, enabling composite behaviors.
ClawTank Integration
If you use ClawTank for managed hosting, custom SKILL.md files can be uploaded through the dashboard. The format is identical to self-hosted skills.
Summary
Custom skills are OpenClaw's primary extension mechanism. The process is simple: create a directory, write a SKILL.md with frontmatter and instructions, test with openclaw chat, and iterate. Skill quality depends almost entirely on instruction quality. Clear, specific, example-rich instructions produce reliable skills. Treat your SKILL.md as a precise specification, not a loose description.
