How to Build Custom OpenClaw Skills: A Developer's Guide
Build your own OpenClaw skills from scratch. Covers the SKILL.md spec, adding tools, testing locally, and publishing to ClawHub. With real examples.
OpenClaw skills are the fastest way to extend your AI assistant without modifying core code. A skill adds new capabilities — weather lookup, CRM queries, image generation, anything with an API — in minutes. You write a SKILL.md file, define what it does, and OpenClaw loads it automatically.
This guide covers the full development cycle: from scaffolding to testing to publishing on ClawHub.
What a Skill Is
A skill is a directory with a SKILL.md file. The SKILL.md tells OpenClaw:
- What the skill does (description that triggers activation)
- How to use it (step-by-step instructions)
- What scripts or reference files it includes
Skills can also include:
- Shell scripts (
scripts/subdirectory) - Reference data (
references/subdirectory) - Config files the agent reads during execution
The simplest possible skill is a single SKILL.md file. No code required.
Skill Discovery: How OpenClaw Loads Skills
OpenClaw scans the skills directory on startup. Every folder with a valid SKILL.md gets registered. When you send a message, OpenClaw scans skill descriptions and activates the most relevant one.
The matching is semantic, not keyword-based. A skill described as "fetch current weather and forecasts" will activate when you ask "what's the weather in Miami" — even though "Miami" isn't in the description.
This means your description matters more than your code. Write descriptions that match how users will actually ask for the feature.
Scaffolding a New Skill
Directory structure for a new skill called crm-lookup:
skills/
crm-lookup/
SKILL.md # Required — the skill definition
scripts/
lookup.sh # Optional — shell script
references/
field-map.md # Optional — reference data
Create the structure:
mkdir -p /usr/lib/node_modules/openclaw/skills/crm-lookup/scripts
mkdir -p /usr/lib/node_modules/openclaw/skills/crm-lookup/references
touch /usr/lib/node_modules/openclaw/skills/crm-lookup/SKILL.md
Writing the SKILL.md
The SKILL.md format follows a strict spec. Every field matters.
# CRM Lookup Skill
## Description
Look up customer records in HubSpot CRM by email, company, or deal name. Returns contact details, deal stage, last activity date, and assigned rep. Use when someone asks about a customer, prospect, or company in the CRM.
## Prerequisites
- HubSpot API key set as HUBSPOT_API_KEY environment variable
- jq installed (brew install jq / apt install jq)
## Usage
### Look up by email
```bash
bash scripts/lookup.sh --email "customer@company.com"
```
### Look up by company name
```bash
bash scripts/lookup.sh --company "Acme Corp"
```
## Output Format
Returns JSON with fields: id, email, company, deal_stage, last_activity, rep_name, deal_value
## Notes
- API rate limit: 100 requests per 10 seconds
- If contact not found, returns {"error": "not_found"}
- Deal values are in USD
The description paragraph (under ## Description) is what OpenClaw uses to decide if this skill applies. Make it specific and include the trigger phrases users will actually say.
Writing the Skill Script
For the crm-lookup skill, the shell script does the actual API call:
#!/bin/bash
# scripts/lookup.sh
set -euo pipefail
HUBSPOT_URL="https://api.hubapi.com"
HEADERS=(-H "Authorization: Bearer $HUBSPOT_API_KEY" -H "Content-Type: application/json")
lookup_by_email() {
local email="$1"
curl -s "${HEADERS[@]}" "${HUBSPOT_URL}/crm/v3/objects/contacts/search" -d "{"filterGroups":[{"filters":[{"propertyName":"email","operator":"EQ","value":"${email}"}]}]}" | jq '{id: .results[0].id, email: .results[0].properties.email, company: .results[0].properties.company}'
}
lookup_by_company() {
local company="$1"
curl -s "${HEADERS[@]}" "${HUBSPOT_URL}/crm/v3/objects/companies/search" -d "{"filterGroups":[{"filters":[{"propertyName":"name","operator":"CONTAINS_TOKEN","value":"${company}"}]}]}" | jq '{id: .results[0].id, name: .results[0].properties.name, domain: .results[0].properties.domain}'
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--email) EMAIL="$2"; shift 2 ;;
--company) COMPANY="$2"; shift 2 ;;
*) echo "Unknown arg: $1"; exit 1 ;;
esac
done
if [ -n "${EMAIL:-}" ]; then
lookup_by_email "$EMAIL"
elif [ -n "${COMPANY:-}" ]; then
lookup_by_company "$COMPANY"
else
echo '{"error": "provide --email or --company"}'
exit 1
fi
Testing Your Skill Locally
Three tests before shipping any skill:
Test 1: Script works in isolation
export HUBSPOT_API_KEY=your-key
bash skills/crm-lookup/scripts/lookup.sh --email "test@example.com"
Expected: JSON output. If you get an error — fix the script before moving on.
Test 2: Script works in cron context (no env)
env -i bash -c 'source /root/.env && bash skills/crm-lookup/scripts/lookup.sh --email test@example.com'
This simulates how the script runs when called by OpenClaw. Missing env variables show up here, not in direct execution.
Test 3: Skill activates correctly
Restart OpenClaw and ask it: "Look up customer john@acme.com in the CRM."
Check the OpenClaw log to confirm the crm-lookup skill was selected (not another skill). If a different skill activated — rewrite the description to be more specific.
The 4 Most Common Skill Bugs
1. Description too vague
A description like "helps with customer data" matches too many scenarios. Be specific: "Look up customer records in HubSpot CRM by email, company, or deal name."
2. Missing error handling
Scripts that crash silently are hard to debug. Always handle the "not found" case and return a parseable error: {"error": "not_found", "query": "john@acme.com"}
3. Hardcoded paths
Skills that reference /home/username/ break on different servers. Use environment variables for all paths.
4. No rate limit handling
If your API returns 429, a script without retry logic will fail silently. Add exponential backoff:
retry_with_backoff() {
local n=1
local max=3
until "$@"; do
if [ $n -ge $max ]; then
echo "Failed after $max retries"
return 1
fi
sleep $((2 ** n))
n=$((n + 1))
done
}
Publishing to ClawHub
ClawHub is the OpenClaw skill marketplace. Publishing makes your skill installable with one command by any OpenClaw user.
Prerequisites:
- clawhub CLI installed:
npm install -g clawhub - Account at clawhub.com
- Skill tested and working locally
Publish:
cd skills/crm-lookup
clawhub publish
ClawHub validates your SKILL.md format, packages the directory, and makes it available. Other users install it with:
clawhub install crm-lookup
For versioned updates:
clawhub publish --version 1.1.0
Skill Security Checklist
Before publishing any skill:
- No API keys hardcoded in scripts — environment variables only
- No sensitive data in SKILL.md or reference files
- Input validation on all user-provided parameters (prevent injection)
- Output sanitization if the result goes to a public channel
- Rate limiting documented in SKILL.md → Notes section