Building Custom OpenClaw Skills: A Developer Guide
Skills are what transform an OpenClaw agent from a conversational chatbot into a powerful automation tool. While the language model handles natural language understanding and generation, skills give the agent the ability to take actions: query databases, call APIs, process data, trigger workflows, and interact with the outside world.
The EZClaws Skills Marketplace offers a growing catalog of pre-built skills. But sometimes you need something specific to your business, your tools, or your workflow. That is where custom skill development comes in.
This guide walks you through the entire skill development process: understanding the architecture, setting up your development environment, building your first skill, testing it, and publishing it for others to use.
Skill Architecture Overview
Before writing any code, let us understand how skills fit into the OpenClaw architecture.
The Request Flow
When a message arrives at your OpenClaw agent, here is what happens:
- Message received - The agent receives the user's message through its gateway URL or a platform integration.
- Pre-processing skills run - Skills registered as pre-processors execute first. They can modify the input, add context, or short-circuit the response.
- Skill trigger evaluation - The agent evaluates which skills should be activated based on the message content, conversation state, or explicit triggers.
- Triggered skills execute - Matched skills run and produce results (data, actions, or responses).
- Context assembly - The agent assembles the model context: system prompt, conversation history, and skill outputs.
- Model response generation - The AI model generates a response using all available context.
- Post-processing skills run - Skills registered as post-processors can modify, validate, or enhance the model's response.
- Response delivered - The final response is sent back to the user.
Skill Types
Skills fall into several categories based on when and how they execute:
Data Provider Skills inject information into the model's context. Example: a skill that looks up a customer's order status and adds it to the context so the model can reference it in its response.
Action Skills perform external actions based on the conversation. Example: a skill that creates a support ticket in your help desk when the agent detects an escalation is needed.
Pre-Processing Skills modify or enrich the incoming message before the model sees it. Example: a skill that detects the user's language and translates non-English messages.
Post-Processing Skills modify the model's output before it is sent to the user. Example: a skill that scans responses for prohibited content and redacts it.
Trigger Skills are activated only when specific conditions are met. Example: a skill that activates when the user mentions an order number, then fetches the order details.
Setting Up Your Development Environment
Prerequisites
- Node.js 18 or later
- npm or yarn
- TypeScript familiarity (skills are written in TypeScript)
- A code editor (VS Code recommended)
- An EZClaws account with a running agent for testing (see the deployment tutorial)
Creating a Skill Project
Start by creating a new directory for your skill:
mkdir my-custom-skill
cd my-custom-skill
npm init -y
npm install typescript @types/node --save-dev
npx tsc --init
Your project structure should look like this:
my-custom-skill/
src/
index.ts # Main skill entry point
handlers/ # Skill handler functions
utils/ # Helper utilities
tests/
index.test.ts # Skill tests
skill.json # Skill manifest
package.json
tsconfig.json
The Skill Manifest (skill.json)
Every skill needs a manifest file that describes it:
{
"name": "my-custom-skill",
"version": "1.0.0",
"description": "A custom skill that does something useful",
"author": "Your Name",
"type": "trigger",
"triggers": {
"keywords": ["order", "tracking", "status"],
"patterns": ["order\\s*#?\\s*\\d+"]
},
"requiredEnv": [
{
"key": "MY_API_KEY",
"description": "API key for the external service"
}
],
"tags": ["customer-support", "order-tracking"]
}
Fields explained:
- name: Unique identifier for the skill. Use lowercase with hyphens.
- version: Semantic versioning (major.minor.patch).
- description: Human-readable description shown in the marketplace.
- author: Your name or organization.
- type: One of
trigger,preprocessor,postprocessor,provider, oraction. - triggers: Conditions that activate the skill. Keywords are matched against the user's message. Patterns support regular expressions.
- requiredEnv: Environment variables the skill needs. Users will be prompted to provide these during installation.
- tags: Categories for marketplace discovery.
Building Your First Skill
Let us build a practical skill: an order status lookup that connects to a hypothetical e-commerce API.
Step 1: Define the Handler
Create src/index.ts:
import { SkillContext, SkillResult } from './types';
import { lookupOrder } from './handlers/orderLookup';
export default async function handler(context: SkillContext): Promise<SkillResult> {
const { message, env, conversationHistory } = context;
// Extract order number from message
const orderMatch = message.match(/order\s*#?\s*(\d{4,})/i);
if (!orderMatch) {
return {
status: 'skipped',
reason: 'No order number found in message'
};
}
const orderNumber = orderMatch[1];
try {
const orderData = await lookupOrder(orderNumber, env.MY_API_KEY);
return {
status: 'success',
data: {
contextInjection: `Order #${orderNumber} status: ${orderData.status}.
Shipped via ${orderData.carrier}.
Tracking: ${orderData.trackingNumber}.
Estimated delivery: ${orderData.estimatedDelivery}.`,
metadata: {
orderNumber,
lookedUpAt: new Date().toISOString()
}
}
};
} catch (error) {
return {
status: 'error',
error: `Failed to look up order #${orderNumber}: ${error.message}`
};
}
}
Step 2: Implement the Data Fetching
Create src/handlers/orderLookup.ts:
interface OrderData {
orderNumber: string;
status: string;
carrier: string;
trackingNumber: string;
estimatedDelivery: string;
items: Array<{
name: string;
quantity: number;
}>;
}
export async function lookupOrder(
orderNumber: string,
apiKey: string
): Promise<OrderData> {
const response = await fetch(
`https://api.yourstore.com/orders/${orderNumber}`,
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
}
);
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Order #${orderNumber} not found`);
}
throw new Error(`API returned status ${response.status}`);
}
return response.json();
}
Step 3: Define Types
Create src/types.ts:
export interface SkillContext {
message: string;
userId: string;
conversationHistory: Array<{
role: 'user' | 'assistant';
content: string;
}>;
env: Record<string, string>;
metadata: Record<string, unknown>;
}
export interface SkillResult {
status: 'success' | 'skipped' | 'error';
data?: {
contextInjection?: string;
response?: string;
metadata?: Record<string, unknown>;
};
reason?: string;
error?: string;
}
Understanding the Result Object
The SkillResult tells OpenClaw what to do with the skill's output:
- contextInjection: Text that gets added to the model's context. The model sees this information and can reference it in its response. Use this for data retrieval skills.
- response: A direct response to send to the user, bypassing the model entirely. Use this for simple, deterministic responses that do not need model interpretation.
- metadata: Structured data that other skills or logging systems can access. Does not affect the model's response directly.
- status: Tells OpenClaw whether the skill ran successfully, was skipped (trigger did not match), or encountered an error.
Testing Your Skill
Unit Tests
Create tests/index.test.ts:
import handler from '../src/index';
import { SkillContext } from '../src/types';
describe('Order Lookup Skill', () => {
const baseContext: SkillContext = {
message: '',
userId: 'test-user',
conversationHistory: [],
env: { MY_API_KEY: 'test-key' },
metadata: {}
};
it('should skip when no order number is present', async () => {
const context = { ...baseContext, message: 'Hello, I need help' };
const result = await handler(context);
expect(result.status).toBe('skipped');
});
it('should extract order number from message', async () => {
const context = {
...baseContext,
message: 'What is the status of order #12345?'
};
const result = await handler(context);
expect(result.status).toBe('success');
expect(result.data?.contextInjection).toContain('12345');
});
it('should handle various order number formats', async () => {
const formats = [
'order 12345',
'order #12345',
'Order#12345',
'order number 12345'
];
for (const format of formats) {
const context = { ...baseContext, message: format };
const result = await handler(context);
expect(result.status).toBe('success');
}
});
});
Run tests:
npx jest
Integration Testing on EZClaws
After unit testing, test the skill with a real agent:
- Build the skill:
npm run build - Install it on your test agent through the EZClaws dashboard
- Send messages to your agent that should trigger the skill
- Check the agent event log for skill execution details
- Verify the agent's responses include the skill's data
Advanced Skill Patterns
Chaining Skills
Skills can be designed to work together in a chain. For example:
- Language Detection Skill (pre-processor) detects the user's language and adds it to metadata
- Order Lookup Skill (trigger) fetches order data
- Translation Skill (post-processor) translates the response to the user's detected language
To support chaining, skills should:
- Read from
context.metadatafor input from previous skills - Write to the result's
metadatafor downstream skills - Not assume they are the only skill running
Error Handling Best Practices
Robust error handling is critical for production skills:
export default async function handler(context: SkillContext): Promise<SkillResult> {
try {
// Main skill logic
const result = await doSomething(context);
return { status: 'success', data: result };
} catch (error) {
// Log the error for debugging
console.error('Skill error:', error);
// Return a graceful error that the model can work with
return {
status: 'error',
error: 'I was unable to retrieve that information right now. Please try again in a moment.',
data: {
metadata: {
errorType: error.name,
errorMessage: error.message
}
}
};
}
}
Key principles:
- Never let exceptions crash the agent. Always catch and return a proper
SkillResult. - Provide user-friendly error messages the model can relay to the user.
- Log technical details for debugging but do not expose them to users.
- Use timeout mechanisms for external API calls to prevent hanging.
Rate Limiting and Caching
If your skill calls external APIs, implement rate limiting and caching:
const cache = new Map<string, { data: unknown; expires: number }>();
async function cachedLookup(key: string, fetcher: () => Promise<unknown>): Promise<unknown> {
const cached = cache.get(key);
if (cached && cached.expires > Date.now()) {
return cached.data;
}
const data = await fetcher();
cache.set(key, {
data,
expires: Date.now() + 5 * 60 * 1000 // 5 minute cache
});
return data;
}
This prevents your skill from hammering external APIs with repeated identical requests within a short time window.
Secure Environment Variable Handling
Skills should never log, expose, or transmit environment variables:
// Good: Use env vars only for authentication
const response = await fetch(url, {
headers: { 'Authorization': `Bearer ${context.env.API_KEY}` }
});
// Bad: Never log env vars
console.log('API Key:', context.env.API_KEY); // NEVER DO THIS
For more on API key security, see our API keys guide.
Publishing Your Skill
Publishing to ClawHub
ClawHub is the community skill repository synced with the EZClaws marketplace.
-
Prepare your skill:
- Ensure all tests pass
- Write clear documentation in the skill description
- Include usage examples in the README
- Set the correct version number
-
Submit for review:
- Push your skill to the ClawHub repository
- Submit a review request
- The review team checks for quality, security, and documentation
-
After approval:
- Your skill appears in the EZClaws marketplace under Community Skills
- Users can install it with one click
- You can track install count and user feedback
Publishing Official EZClaws Skills
If you build a skill that you think should be an official EZClaws skill (higher visibility, quality guarantee), contact the EZClaws team. Official skills are maintained by the platform and receive priority support.
Skill Development Best Practices
Keep Skills Focused
Each skill should do one thing well. A skill that looks up orders, processes returns, AND sends emails is trying to do too much. Split it into three skills.
Make Skills Configurable
Use environment variables for anything that might vary between users:
- API endpoints
- Authentication credentials
- Feature flags
- Threshold values
Handle Missing Configuration Gracefully
If a required environment variable is not set, return a helpful error instead of crashing:
if (!context.env.MY_API_KEY) {
return {
status: 'error',
error: 'This skill requires the MY_API_KEY environment variable to be configured.'
};
}
Document Everything
Good documentation makes your skill usable by others. Include:
- What the skill does
- What triggers it
- What environment variables it needs
- Example conversations showing the skill in action
- Known limitations
Version Responsibly
Follow semantic versioning:
- Patch (1.0.1): Bug fixes that do not change behavior
- Minor (1.1.0): New features that are backward compatible
- Major (2.0.0): Breaking changes that require user action
Common Skill Ideas
Looking for inspiration? Here are skill categories that are in high demand:
- E-commerce: Order tracking, inventory checks, return processing, product recommendations
- Customer support: Ticket creation, FAQ matching, escalation detection, satisfaction surveys
- Productivity: Calendar integration, task management, email drafting, document search
- Communication: Language translation, sentiment analysis, spam detection, message routing
- Data: Analytics queries, report generation, data validation, dashboard creation
For more ideas on what to automate with AI agents, see our automation ideas guide.
Conclusion
Custom skills are what make OpenClaw agents truly powerful. They bridge the gap between conversational AI and real-world action. Whether you are building a simple FAQ lookup or a complex multi-step workflow, the skill architecture provides a clean, testable, and reusable pattern.
Start with a simple skill that solves a real problem. Get it working, get it tested, get it deployed. Then iterate. The best skills evolve from real usage patterns and real user needs.
Ready to build? Set up your development environment, pick a use case, and start coding. When you are ready to deploy, EZClaws makes testing and running your skill as easy as clicking a button. Browse the Skills Marketplace for inspiration, and check the configuration guide for understanding how skills fit into the broader agent configuration.
Frequently Asked Questions
OpenClaw skills are written in TypeScript or JavaScript. The skill system uses a standard module pattern with exported handler functions that receive conversation context and return results. If you are familiar with writing serverless functions or Express.js route handlers, the pattern will feel familiar.
No. Using pre-built skills from the EZClaws Skills Marketplace requires zero coding. You install them through the dashboard with one click. Building custom skills does require development experience, which is what this guide covers.
Yes. You can publish skills to ClawHub, the community skill repository, where other OpenClaw users can discover and install them. Published skills go through a review process for quality and security before being listed.
Skills run before or after the model generates a response. A 'pre-processing' skill can modify the input or add context before the model sees it. A 'post-processing' skill can modify or validate the model's output. Some skills provide data that is injected into the model's context, while others take action based on the model's response.
There is no hard limit on the number of installed skills. However, each active skill adds some latency and complexity to request processing. In practice, most agents work best with 3-8 focused skills. Installing too many skills with overlapping triggers can cause conflicts and unpredictable behavior.
Your OpenClaw Agent is Waiting for you
Our provisioning engine is standing by to spin up your private OpenClaw instance — dedicated VM, HTTPS endpoint, and full autonomy in under a minute.
