My OpenClaw agent was useful for conversations. Then I added my first skill and it went from “a chatbot” to “something that actually does things.”
This guide walks you through building a custom OpenClaw skill from scratch. By the end you’ll have a working skill that your agent can call on demand.
What you need
- OpenClaw installed (
openclaw --versionshould showv2026.3.8or later) - At least one agent already set up (follow the first agent tutorial if you haven’t)
- Node.js (any version ≥18)
How OpenClaw skills work
A skill in OpenClaw is a directory inside ~/.openclaw/skills/ that contains:
~/.openclaw/skills/
└── my-skill/
├── SKILL.md ← instructions + metadata (required)
└── scripts/ ← helper scripts (optional)
└── run.js
SKILL.md is the heart of the skill. It has a YAML frontmatter block that defines the skill’s name, description, and settings — then a Markdown body that gives the agent instructions on how to use it.
That’s it. No framework, no npm install, no build step. Just a folder and a file.
Skill #1 — A simple weather lookup
Let’s start small. A skill that fetches current weather for a city.
Create the skill directory:
mkdir -p ~/.openclaw/skills/get-weather/scripts
Create the SKILL.md:
touch ~/.openclaw/skills/get-weather/SKILL.md
Paste this into ~/.openclaw/skills/get-weather/SKILL.md:
---
name: get-weather
description: >
Get current weather conditions for any city.
Use this when the user asks about weather, temperature,
rain, or whether they need an umbrella or jacket.
user-invocable: false
---
## How to use this skill
Call the get-weather script with a city name. It returns current
temperature, conditions, and humidity.
## When to call it
- User mentions weather, temperature, or climate for a location
- User asks if they should pack for rain
- User asks what to wear
## Input
city: string — the city name (e.g. "Mumbai", "London")
## Output
JSON with: temp (°C), condition (string), humidity (%)
## Call
/skill get-weather --city "<city name>"
Now create the actual script that fetches weather. We’ll use the Open-Meteo API — it’s free, no key needed.
Create ~/.openclaw/skills/get-weather/scripts/run.js:
#!/usr/bin/env node
// Parse --city flag from args
const args = process.argv.slice(2);
const cityIndex = args.indexOf("--city");
const city = cityIndex !== -1 ? args[cityIndex + 1] : null;
if (!city) {
console.error(JSON.stringify({ error: "Missing --city argument" }));
process.exit(1);
}
async function getWeather(cityName) {
// Step 1: geocode city name to lat/lon
const geoRes = await fetch(
`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(cityName)}&count=1`
);
const geoData = await geoRes.json();
if (!geoData.results?.length) {
return { error: `City not found: ${cityName}` };
}
const { latitude, longitude, name, country } = geoData.results[0];
// Step 2: fetch current weather
const weatherRes = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t_weather=true&hourly=relativehumidity_2m`
);
const weatherData = await weatherRes.json();
const current = weatherData.current_weather;
const humidity = weatherData.hourly.relativehumidity_2m[0];
// WMO weather code → human description
const conditions = {
0: "Clear sky", 1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast",
45: "Foggy", 61: "Light rain", 63: "Moderate rain", 65: "Heavy rain",
80: "Rain showers", 95: "Thunderstorm"
};
return {
city: `${name}, ${country}`,
temp: `${current.temperature}°C`,
condition: conditions[current.weathercode] ?? `Code ${current.weathercode}`,
humidity: `${humidity}%`,
wind: `${current.windspeed} km/h`
};
}
getWeather(city)
.then(result => console.log(JSON.stringify(result, null, 2)))
.catch(err => console.error(JSON.stringify({ error: err.message })));
Make it executable:
chmod +x ~/.openclaw/skills/get-weather/scripts/run.js
Test it manually first
Before trusting your agent to use the skill, verify it works:
node ~/.openclaw/skills/get-weather/scripts/run.js --city "Mumbai"
Expected output:
{
"city": "Mumbai, India",
"temp": "31°C",
"condition": "Partly cloudy",
"humidity": "78%",
"wind": "14 km/h"
}
Tell the agent about the skill
Open your agent’s config.yaml and add the skill:
name: myagent
llm:
provider: claude
model: claude-sonnet-4-6
skills:
- get-weather
memory:
dir: ~/.openclaw/agents/myagent/memory/
Restart the agent:
openclaw restart myagent
Now talk to your agent:
You: Will it rain in Delhi tomorrow?
Agent: Let me check the current conditions in Delhi.
[calls get-weather --city "Delhi"]
Delhi is currently showing light rain at 27°C with 82% humidity.
Rain is likely tomorrow — I'd bring an umbrella.
The agent didn’t hallucinate a weather report. It fetched real data.
Skill #2 — A user-invocable command skill
The previous skill was user-invocable: false — the agent decides when to use it. Sometimes you want a skill you can trigger directly with a slash command.
Let’s build /standup — a skill that generates your daily standup notes from your memory files.
Create the skill:
mkdir ~/.openclaw/skills/standup
~/.openclaw/skills/standup/SKILL.md:
---
name: standup
description: >
Generate a formatted daily standup summary from the agent's
memory files. Lists what was done yesterday, what's planned
today, and any blockers.
user-invocable: true
---
## What this skill does
When the user runs /standup, read the agent memory directory
and generate a standup in this exact format:
**Yesterday:**
- [list tasks marked done in the last 24h from memory]
**Today:**
- [list tasks marked as in-progress or planned]
**Blockers:**
- [list anything flagged as blocked, or "None" if clean]
Keep it concise — standup should take 2 minutes to read, not 20.
## Memory location
Read from the agent's memory directory. Look for files containing
task lists, done items, or progress notes.
Now when you type /standup in the OpenClaw TUI or messaging interface, the agent reads your memory files and formats a standup in seconds.
Skill #3 — A skill with environment variable gating
Some skills only work if the right environment variables are set. Use the metadata block to gate skill availability.
This skill queries a local SQLite database — it should only activate if the database file path is configured:
---
name: query-db
description: >
Run a read-only SQL query against the local project database.
Use this when the user asks about data, records, counts, or
wants to look something up in the database.
user-invocable: false
metadata:
openclaw:
requires:
env: ["PROJECT_DB_PATH"]
---
## How to use this skill
Call the query-db script with a SQL query (SELECT only).
## Input
query: string — a SELECT SQL statement
## Safety
Only run SELECT queries. Refuse any INSERT, UPDATE, DELETE, DROP,
or CREATE statements. Return an error if a non-SELECT query is passed.
## Call
/skill query-db --query "<SQL statement>"
If PROJECT_DB_PATH is not set in the environment, OpenClaw automatically hides this skill from the agent — the agent won’t try to call it and fail.
Set it when you want the skill active:
export PROJECT_DB_PATH="/home/vishnu/projects/myapp/data.sqlite"
openclaw restart myagent
The SKILL.md fields explained
| Field | Required | What it does |
|---|---|---|
name | Yes | Unique identifier. Used in skills: config and slash commands |
description | Yes | What the AI reads to decide when to call the skill. Write this carefully. |
user-invocable | No (default: false) | true = available as /skillname slash command |
disable-model-invocation | No | true = the AI can never auto-call this, only manual invocation |
homepage | No | Link to documentation or repo |
metadata.openclaw.requires.bins | No | System binaries that must exist (e.g. ["ffmpeg"]) |
metadata.openclaw.requires.env | No | Env vars that must be set |
metadata.openclaw.requires.config | No | Config paths that must exist |
metadata.openclaw.os | No | Limit to specific OS: ["darwin", "linux", "win32"] |
Description writing tips — this is the most important part
The AI decides which skill to call based entirely on the description field. A vague description = wrong skill calls. A precise description = the agent makes the right choice every time.
Too vague:
description: Gets information
Too broad:
description: Use this for anything related to the internet
Just right:
description: >
Get current weather conditions and temperature for a city.
Use this when the user asks about weather, temperature, rain,
forecasts, or what to wear. Do NOT use this for historical
climate data.
Include: what the skill returns, when to use it, and when NOT to use it.
Troubleshooting
Agent doesn’t seem to know about the skill
- Check
skills:is inconfig.yamlwith the correct skill name - Restart the agent:
openclaw restart <agentname> - Confirm the skill directory name matches the name in
SKILL.md
Skill runs but returns wrong data
- Test the script directly in terminal before blaming the agent
- Add
console.error()logging to your script for debugging
Agent calls the wrong skill
- Improve the
descriptionfield — make it more specific - Add “Do NOT use this for X” to prevent overlap with other skills
Skill not available on this OS
- Check your
metadata.openclaw.osfield - If using
requires.bins, runwhich <binary>to verify it’s installed
What’s next
Understand skill concepts: What Are Agent Skills? AI Tools Explained Simply
Add memory to your agent: Agent Skills with Memory: Persisting State Between Chats
Build a real-world GitHub issues skill: Build a GitHub Issue Creator Skill for Your AI Agent — env gating pattern applied to a production skill
Skills in the Claude API: Agent Skills with the Claude API —
tool_usein Node.js from scratch
Skills in OpenAI: Agent Skills with the OpenAI API — function calling with
gpt-4o
Related Reading.
What Are Agent Skills? AI Tools Explained Simply
Agent skills are the actions an AI can take beyond just talking. Learn what skills are, how they differ from prompts, and why they make AI actually useful in real workflows.
Agent Skills with Google Gemini: Function Calling Guide
Complete guide to Gemini function calling — define tools, handle function_call responses, return results, and compare syntax with Claude and OpenAI. Node.js.
Vercel AI SDK Tools: One API for Claude and OpenAI Skills
Vercel AI SDK's unified tool interface works with Claude, OpenAI, and Gemini. Write your skill once and switch AI providers without rewriting the agent loop.