Adding Custom Services
This guide walks you through creating custom service definitions for better-openclaw. Service definitions describe Docker containers (or bare-metal services) that can be included in your OpenClaw stack.
Understanding Service Definitions
A service definition is a TypeScript object that follows the ServiceDefinition schema. It includes:
- Identity: ID, name, description, category, and icon
- Docker Configuration: Image, ports, volumes, environment variables
- OpenClaw Integration: Skills, environment variables for the gateway
- Metadata: Documentation URLs, tags, maturity level
- Dependencies: Required services, recommendations, and conflicts
Prerequisites
Before creating a custom service:
- Node.js 20+
- pnpm 9+ (
corepack enable && corepack prepare pnpm@latest --activate)
- Cloned better-openclaw repository
Creating a Service Definition
Create the Definition File
Create a new file in packages/core/src/services/definitions/ with your service name:touch packages/core/src/services/definitions/my-service.ts
Define the Service Schema
Add the service definition using the template below:import type { ServiceDefinition } from "../../types.js";
export const myServiceDefinition: ServiceDefinition = {
// Identity
id: "my-service", // lowercase, hyphenated slug
name: "My Service", // display name
description: "What it does", // brief description
category: "database", // see ServiceCategorySchema
icon: "🔧", // emoji icon
// Docker Configuration
image: "docker/image",
imageTag: "latest",
ports: [
{
host: 8080,
container: 8080,
description: "Web UI",
exposed: true
}
],
volumes: [
{
name: "my-service-data",
containerPath: "/data",
description: "Persistent data"
}
],
environment: [
{
key: "SERVICE_PASSWORD",
defaultValue: "",
secret: true,
description: "Service password",
required: true
}
],
healthcheck: {
test: "curl -f http://localhost:8080/health || exit 1",
interval: "30s",
timeout: "10s",
retries: 3
},
// Dependencies
dependsOn: [],
restartPolicy: "unless-stopped",
networks: ["openclaw-network"],
// OpenClaw Integration
skills: [],
openclawEnvVars: [],
// Metadata
docsUrl: "https://example.com/docs",
tags: ["tag1", "tag2"],
maturity: "stable",
// Constraints
requires: [],
recommends: [],
conflictsWith: [],
minMemoryMB: 256,
gpuRequired: false
};
Register the Service
Add your service to packages/core/src/services/definitions/index.ts:// Add import
export { myServiceDefinition } from "./my-service.js";
// Add to allServiceDefinitions array
import { myServiceDefinition } from "./my-service.js";
export const allServiceDefinitions: ServiceDefinition[] = [
// ... existing services
myServiceDefinition,
];
Test Your Service
Run the test suite to validate your service definition:
Service Categories
Choose the appropriate category for your service:
| Category | Description |
|---|
automation | Workflow automation tools |
vector-db | Vector databases for embeddings |
media | Media processing and streaming |
storage | File and object storage |
database | Traditional databases |
proxy | Reverse proxies and API gateways |
monitoring | Monitoring and observability |
browser | Headless browsers |
search | Search engines |
ai | AI and ML services |
communication | Chat and messaging |
coding-agent | AI coding assistants |
social-media | Social media tools |
analytics | Analytics platforms |
ai-platform | AI platforms and frameworks |
dev-tools | Development tools |
knowledge | Knowledge management |
desktop | Desktop environments |
streaming | Video streaming |
security | Security and authentication |
Adding OpenClaw Integration
To make your service accessible to the OpenClaw AI agent:
Create Environment Variables
Add openclawEnvVars to expose connection details:openclawEnvVars: [
{
key: "MY_SERVICE_HOST",
defaultValue: "my-service",
secret: false,
description: "Service hostname for OpenClaw",
required: true
},
{
key: "MY_SERVICE_PORT",
defaultValue: "8080",
secret: false,
description: "Service port for OpenClaw",
required: true
}
]
Create a Skill Pack (Optional)
If your service needs specific instructions for AI agents, create a skill file in skills/my-service-skill/SKILL.md:---
name: my-service-skill
description: "Interact with My Service at {{MY_SERVICE_HOST}}:{{MY_SERVICE_PORT}}"
metadata:
openclaw:
emoji: "🔧"
---
# My Service Skill
My Service is available at `{{MY_SERVICE_HOST}}:{{MY_SERVICE_PORT}}`.
## Basic Usage
```bash
curl http://{{MY_SERVICE_HOST}}:{{MY_SERVICE_PORT}}/api/endpoint
</Step>
<Step title="Link the Skill">
Reference the skill in your service definition:
```typescript
skills: [{ skillId: "my-service-skill", autoInstall: true }]
For services that can run natively on the host (without Docker), add native recipes:
nativeSupported: true,
nativeRecipes: [
{
platform: "linux",
installSteps: [
"sudo apt-get update -qq",
"sudo apt-get install -y -qq my-service"
],
startCommand: "sudo systemctl start my-service",
stopCommand: "sudo systemctl stop my-service",
configPath: "/etc/my-service/config.conf",
configTemplate: "# Generated config\nport=8080\n",
systemdUnit: "my-service"
}
]
Native recipes are used when users generate a bare-metal deployment. The service will be installed and run on the host instead of in a container.
Real-World Example: Redis
Here’s how Redis is defined in better-openclaw:
export const redisDefinition: ServiceDefinition = {
id: "redis",
name: "Redis",
description: "In-memory data store used for caching, session management, and pub/sub messaging.",
category: "database",
icon: "🔴",
image: "redis",
imageTag: "8-alpine",
ports: [
{
host: 6379,
container: 6379,
description: "Redis server port",
exposed: true
}
],
volumes: [
{
name: "redis-data",
containerPath: "/data",
description: "Persistent Redis data"
}
],
environment: [
{
key: "REDIS_PASSWORD",
defaultValue: "changeme",
secret: true,
description: "Password for Redis authentication",
required: true
}
],
healthcheck: {
test: "redis-cli ping",
interval: "10s",
timeout: "5s",
retries: 3
},
skills: [{ skillId: "redis-cache", autoInstall: true }],
openclawEnvVars: [
{
key: "REDIS_HOST",
defaultValue: "redis",
secret: false,
description: "Redis hostname for OpenClaw",
required: true
}
],
docsUrl: "https://redis.io/docs",
tags: ["cache", "pubsub", "message-bus"],
maturity: "stable",
conflictsWith: ["valkey"],
minMemoryMB: 128,
nativeSupported: true,
nativeRecipes: [
{
platform: "linux",
installSteps: [
"command -v redis-server || sudo apt-get install -y redis-server"
],
startCommand: "sudo systemctl start redis-server",
configPath: "/etc/redis/redis.conf"
}
]
};
Best Practices
- Always use semantic versioning for
imageTag (e.g., 17-alpine, not latest)
- Set appropriate
minMemoryMB to prevent out-of-memory issues
- Use
conflictsWith to prevent incompatible services from running together
- Mark sensitive environment variables with
secret: true
- Use the
requires field to automatically pull in dependencies
- Add
recommends for services that work well together
- Always include a
healthcheck for reliable container orchestration
- Use descriptive
tags for better searchability
Submitting Your Service
Once your service is complete:
- Run tests:
pnpm test
- Check types:
pnpm typecheck
- Lint code:
pnpm lint
- Create a changeset:
pnpm changeset
- Submit a pull request with:
- Service definition
- Skill pack (if applicable)
- Test coverage
- Documentation
See the CONTRIBUTING.md for detailed PR guidelines.