How to Write Your Own OpenClaw Skill from Scratch
Step-by-step guide to writing your own OpenClaw skill from scratch. Covers SKILL.md format, Python implementation, tool definitions, testing, and publishing to ClawHub.
Writing a custom OpenClaw skill allows you to give your agent capabilities tailored to your specific needs — connecting to internal APIs, automating proprietary workflows, or integrating tools that don't yet have ClawHub skills. This guide covers the complete development process from scratch.
Skill Structure
Create a directory for your skill:
my-skill/
├── SKILL.md # Manifest and documentation
├── main.py # Implementation
├── requirements.txt # Python dependencies
└── tests/
└── test_main.py # Optional tests
Writing SKILL.md
---
name: my-weather-skill
version: 1.0.0
author: yourname
description: Get current weather for any location
permissions:
- network_access
- read_env:WEATHER_API_KEY
tools:
- name: get_weather
description: Get the current weather and forecast for a location
parameters:
location:
type: string
description: City name or ZIP code
required: true
units:
type: string
description: Temperature units - celsius or fahrenheit
default: celsius
enum: [celsius, fahrenheit]
minimum_openclaw_version: "2.3.0"
---
# My Weather Skill
Provides current weather information using the OpenWeatherMap API.
Requires a free API key from openweathermap.org.
Writing main.py
import os
import httpx
from openclaw.skill import SkillBase, tool
class MyWeatherSkill(SkillBase):
def __init__(self):
self.api_key = os.environ.get("WEATHER_API_KEY")
self.base_url = "https://api.openweathermap.org/data/2.5"
@tool(name="get_weather")
async def get_weather(self, location: str, units: str = "celsius") -> dict:
"""Get current weather for a location."""
unit_param = "metric" if units == "celsius" else "imperial"
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/weather",
params={
"q": location,
"appid": self.api_key,
"units": unit_param
}
)
response.raise_for_status()
data = response.json()
return {
"location": data["name"],
"temperature": data["main"]["temp"],
"units": units,
"description": data["weather"][0]["description"],
"humidity": data["main"]["humidity"]
}
def create_skill():
return MyWeatherSkill()
requirements.txt
httpx>=0.26.0
Testing Your Skill
# Install your skill locally
python -m openclaw skill install /path/to/my-skill
# Test it works
python -m openclaw skill test my-weather-skill
# In a conversation, ask:
# "What's the weather in Tokyo?"
Publishing to ClawHub
- Create a GitHub repository for your skill
- Register as a ClawHub publisher at clawhub.io/publish
- Submit your skill via
python -m openclaw skill publish - ClawHub runs automated security scans and human review (1–3 days)
- Once approved, your skill is live on ClawHub
Best Practices
- Handle errors gracefully: Return informative error messages rather than raising exceptions
- Validate inputs: Check parameters before making API calls
- Use async: All skill functions should be
async deffor non-blocking operation - Document tool descriptions well: The LLM uses these to decide when to call your tool
- Keep permissions minimal: Only request what you actually need
Frequently Asked Questions
Can skills maintain state between calls?
Yes. The SkillBase class provides access to persistent storage via self.storage.set(key, value) and self.storage.get(key). State persists across restarts.
Can my skill access other installed skills?
Yes. Use self.skill_manager.call_skill("other-skill", "tool_name", **kwargs) to call another skill's tools from within your skill.
How are skills monetized on ClawHub?
ClawHub supports one-time purchase, subscription, and usage-based pricing. ClawHub takes a percentage (currently 20%) of skill sales. Free skills are always available.
nacre.sh
Run OpenClaw without the server headaches
Dedicated instance, automatic TLS, nightly backups, and 290+ LLM integrations. Live in under 90 seconds from $12/month.
Deploy your agent →