← All tutorials

Build OpenClaw skills — reusable tools your assistant can call

How to write, test and ship OpenClaw skills: typed tool calls, shell commands, HTTP APIs and the agent-skills registry.

A skill in OpenClaw is a small, typed tool the assistant can call. Skills are the difference between a chat toy and an actual agent that can do things — fetch a calendar event, open a PR, send a message, run a script. This tutorial shows you how to write, test and ship your own.

What a skill looks like

A skill is a folder with two files:

my-skill/
├── skill.yaml        # name, description, parameters
└── handler.ts        # the TypeScript that runs when called

The skill.yaml is the contract — the LLM reads it to decide when to call your skill. The handler.ts is the implementation.

A minimal skill

Create a folder:

mkdir -p ~/.openclaw/skills/greet
cd ~/.openclaw/skills/greet

Write skill.yaml:

name: greet
description: |
  Say hello to a person by name in a chosen language.
  Use when the user wants to send a quick greeting
  (e.g. "say hi to Anna in German").
parameters:
  type: object
  required: [name, language]
  properties:
    name:
      type: string
      description: Person's first name
    language:
      type: string
      enum: [en, de, zh, ja]

Write handler.ts:

import type { SkillContext } from "openclaw/sdk";

const greetings: Record = {
  en: "Hello, {name}!",
  de: "Hallo, {name}!",
  zh: "{name},你好!",
  ja: "こんにちは、{name}さん!",
};

export default async function greet(
  input: { name: string; language: string },
  ctx: SkillContext
) {
  const template = greetings[input.language] ?? greetings.en;
  const message = template.replace("{name}", input.name);
  ctx.log.info(`greet → ${message}`);
  return { message };
}

Reload the Gateway to pick it up:

openclaw skills reload greet

Now message the assistant:

say hi to Anna in German

It should call your greet skill and reply with Hallo, Anna!.

Calling HTTP APIs

Skills are a great fit for wrapping third-party APIs. Here is a weather skill:

name: weather
description: |
  Look up the current weather for a city. Use when the user
  asks "what's the weather in ".
parameters:
  type: object
  required: [city]
  properties:
    city: { type: string }
export default async function weather(
  { city }: { city: string },
  ctx: SkillContext
) {
  const url = `https://wttr.in/${encodeURIComponent(city)}?format=j1`;
  const res = await fetch(url);
  if (!res.ok) {
    throw new Error(`weather: ${res.status} ${res.statusText}`);
  }
  const data = await res.json();
  const cur = data.current_condition[0];
  return {
    city,
    temperature_c: Number(cur.temp_C),
    feels_like_c: Number(cur.FeelsLikeC),
    description: cur.weatherDesc[0].value,
  };
}

The Gateway automatically retries transient errors, surfaces typed exceptions to the LLM and includes the return value in the assistant's reply context.

Calling shell commands

For skills that need to touch the local filesystem or run a binary, use the sandboxed shell:

import { sh } from "openclaw/shell";

export default async function listDownloads(_: unknown, ctx: SkillContext) {
  const out = await sh("ls -lh ~/Downloads", { cwd: process.env.HOME });
  return { files: out.stdout };
}

The shell runs inside a filesystem sandbox. By default it can read ~/Documents, ~/Downloads, ~/Desktop and ~/.openclaw. To grant access to a custom path, opt in explicitly:

const out = await sh("git status", { cwd: "~/Code/myapp", allow: ["~/Code/myapp"] });

If the assistant tries to read outside the allowed roots, the call fails with a clear error rather than silently leaking data.

Sharing skills with the team

OpenClaw ships an agent-skills repository of canonical skills under github.com/openclaw/agent-skills. If you have a skill that other people will find useful, the workflow is:

openclaw skills publish greet

This:

  1. Bundles the folder into a tarball.
  2. Computes a content hash.
  3. Pushes it to ClawHub (the community skill registry at clawhub.ai).
  4. Returns a permanent URL you can paste in chat:
   https://clawhub.ai/skills/greet@1.0.2

To install someone else's skill:

openclaw skills install https://clawhub.ai/skills/weather@1.4.0
Skills run with your user permissions. Only install skills whose source you have reviewed. ClawHub tags every skill with a ClawScan security score from clawscanner.app; check it before installing.

Composing skills with Lobster

Lobster is the OpenClaw-native workflow shell. It composes skills and CLI tools into typed, local-first pipelines. A simple Lobster recipe that combines skills:

# ~/.openclaw/lobster/morning-briefing.yaml
name: morning-briefing
steps:
  - skill: weather
    args: { city: "Vienna" }
  - skill: calendar.today
  - skill: inbox.unread
    args: { limit: 5 }

Run it on a schedule:

openclaw cron add \\
  --name "morning briefing" \\
  --at "0 7 * * *" \\
  --lobster ~/.openclaw/lobster/morning-briefing.yaml

Every morning at 7 a.m. the assistant will fetch the weather, your calendar and your unread emails, and send you a single Telegram message summarising them.

Testing skills

The SDK ships a tiny test harness:

import greet from "./handler";
import { test } from "openclaw/test";

test("greet returns German template", async ({ run }) => {
  const out = await run(greet, { name: "Anna", language: "de" });
  expect(out.message).toBe("Hallo, Anna!");
});
openclaw skills test greet

Next steps

OpenClaw Guide Editorial
Independently maintained walkthroughs and ecosystem notes for OpenClaw.

Found an issue or have a correction? Get in touch.