Skip to main content

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

1

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
2

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
};
3

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,
];
4

Test Your Service

Run the test suite to validate your service definition:
pnpm test

Service Categories

Choose the appropriate category for your service:
CategoryDescription
automationWorkflow automation tools
vector-dbVector databases for embeddings
mediaMedia processing and streaming
storageFile and object storage
databaseTraditional databases
proxyReverse proxies and API gateways
monitoringMonitoring and observability
browserHeadless browsers
searchSearch engines
aiAI and ML services
communicationChat and messaging
coding-agentAI coding assistants
social-mediaSocial media tools
analyticsAnalytics platforms
ai-platformAI platforms and frameworks
dev-toolsDevelopment tools
knowledgeKnowledge management
desktopDesktop environments
streamingVideo streaming
securitySecurity and authentication

Adding OpenClaw Integration

To make your service accessible to the OpenClaw AI agent:
1

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
  }
]
2

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 }]

Native (Bare-Metal) Support

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:
  1. Run tests: pnpm test
  2. Check types: pnpm typecheck
  3. Lint code: pnpm lint
  4. Create a changeset: pnpm changeset
  5. Submit a pull request with:
    • Service definition
    • Skill pack (if applicable)
    • Test coverage
    • Documentation
See the CONTRIBUTING.md for detailed PR guidelines.

Build docs developers (and LLMs) love