Skip to main content

Migration Guide

This guide explains how to migrate between different versions of better-openclaw and how the config migration system works.

Understanding Config Versions

better-openclaw uses a versioned configuration schema. When the schema changes, a migration path ensures your existing configs continue to work. Current Config Version: 2 Config files include a configVersion field:
{
  "configVersion": 2,
  "projectName": "my-project",
  "services": ["redis", "postgresql"],
  "deploymentType": "docker"
}

Automatic Migrations

Migrations run automatically when you load an older config:
import { migrateConfig, needsMigration } from "@better-openclaw/core";

const oldConfig = loadConfig("config.json");

if (needsMigration(oldConfig)) {
  const migrated = migrateConfig(oldConfig);
  // Config is now up to date
}
The CLI and API automatically apply migrations, so you typically don’t need to do this manually.

Migration History

v1 → v2: Add deploymentType field

What Changed: Added deploymentType field to distinguish between Docker and bare-metal deployments. Before (v1):
{
  "configVersion": 1,
  "projectName": "my-project",
  "services": ["redis", "postgresql"]
}
After (v2):
{
  "configVersion": 2,
  "projectName": "my-project",
  "services": ["redis", "postgresql"],
  "deploymentType": "docker"
}
Migration Logic:
// v1 → v2 migration
1: (input) => ({
  ...input,
  configVersion: 2,
  deploymentType: (input.deploymentType as string) ?? "docker"
})
The migration defaults deploymentType to "docker" to maintain backward compatibility. Existing Docker-based configs will continue to work without changes.

Manual Migration Steps

If you need to manually update a config file:
1

Identify Current Version

Check the configVersion field in your config file:
cat config.json | grep configVersion
2

Review Breaking Changes

Consult the Migration History for changes between your version and the latest.
3

Update Config

Apply necessary changes to your config file:
{
  "configVersion": 2,  // Update version number
  "projectName": "my-project",
  "services": ["redis", "postgresql"],
  "deploymentType": "docker"  // Add new required fields
}
4

Validate Config

Use the CLI to validate your updated config:
pnpm --filter cli start
# Or use the API
curl -X POST http://localhost:3000/api/validate \
  -H "Content-Type: application/json" \
  -d @config.json

Breaking Changes by Version

Version 2 (Current)

Breaking Changes:
  • Added required deploymentType field (defaults to "docker")
  • Native/bare-metal deployments now require explicit deploymentType: "bare-metal"
Impact: Low — migration is automatic and defaults preserve existing behavior. Action Required: None if using CLI or API. If manually constructing configs, add:
"deploymentType": "docker" // or "bare-metal" or "local"

Version 1 (Legacy)

Changes:
  • Initial config schema
  • No deploymentType field
  • All deployments assumed Docker
Action Required: Upgrade to v2 by loading config through the migration system.

Handling Future Migrations

When new versions are released:
  1. Check the Changelog: Review CHANGELOG.md for migration notes
  2. Update Dependencies: Run pnpm install to get the latest version
  3. Run Migrations: The system will automatically migrate on load
  4. Test Thoroughly: Regenerate your stack and verify all services work

Migration API

The migration system provides several utilities:

Check if Migration is Needed

import { needsMigration } from "@better-openclaw/core";

const config = { configVersion: 1, ... };

if (needsMigration(config)) {
  console.log("Config needs migration");
}

Apply Migrations

import { migrateConfig, CURRENT_CONFIG_VERSION } from "@better-openclaw/core";

try {
  const migrated = migrateConfig(oldConfig);
  console.log(`Migrated to v${CURRENT_CONFIG_VERSION}`);
} catch (error) {
  console.error("Migration failed:", error.message);
}

Get Current Version

import { CURRENT_CONFIG_VERSION } from "@better-openclaw/core";

console.log(`Current version: ${CURRENT_CONFIG_VERSION}`);

Migration Failure Handling

If a migration fails:

No Migration Path Error

Error: No migration path from config version X Cause: Config is from a future version or invalid version number Solution:
  1. Check you’re using the latest version of better-openclaw:
    git pull origin main
    pnpm install
    pnpm build
    
  2. Verify config version is valid:
    // Valid: version ≤ CURRENT_CONFIG_VERSION
    configVersion: 2
    
    // Invalid: future version
    configVersion: 999
    

Validation Error After Migration

Error: Config fails validation after migration Cause: Manual edits introduced invalid fields Solution: Use the schema validator:
import { GenerationInputSchema } from "@better-openclaw/core";

try {
  GenerationInputSchema.parse(config);
} catch (error) {
  console.error("Validation errors:", error.issues);
}

Migrating Custom Services

If you’ve created custom service definitions:
When the ServiceDefinitionSchema changes, you must update your custom services to match the new schema.
1

Check Schema Changes

Review changes to packages/core/src/schema.ts in the release notes.
2

Update Service Definitions

Apply required changes to your custom services:
export const myServiceDefinition: ServiceDefinition = {
  // ... existing fields
  // Add new required fields from latest schema
  newField: "value"
};
3

Run Tests

Validate your changes:
pnpm test
4

Update Skills

If skill schema changed, update corresponding SKILL.md files.

Rollback Strategy

If you encounter issues after migration:

Option 1: Revert to Previous Version

git checkout v1.0.0  # Replace with previous stable version
pnpm install
pnpm build

Option 2: Use Backup Config

Always keep backups of working configs:
cp config.json config.backup.json
Restore if needed:
cp config.backup.json config.json

Option 3: Regenerate from Scratch

If config is corrupted, regenerate:
pnpm --filter cli start
# Select services and options interactively

Schema Reference

The latest schema is defined in packages/core/src/schema.ts. Key schemas:
  • GenerationInputSchema: Input config for generation
  • ServiceDefinitionSchema: Service definition format
  • SkillPackSchema: Skill pack format
  • ComposeOptionsSchema: Docker Compose generation options
All schemas use Zod for validation. You can introspect schemas programmatically:
import { GenerationInputSchema } from "@better-openclaw/core";

// Get schema shape
console.log(GenerationInputSchema.shape);

// Validate and get errors
const result = GenerationInputSchema.safeParse(config);
if (!result.success) {
  console.error(result.error.issues);
}

Testing Migrations

If you’re contributing migration code:
import { describe, it, expect } from "vitest";
import { migrateConfig } from "./migrations.js";

describe("Config Migrations", () => {
  it("migrates v1 to v2", () => {
    const v1Config = {
      configVersion: 1,
      projectName: "test",
      services: ["redis"]
    };

    const migrated = migrateConfig(v1Config);

    expect(migrated.configVersion).toBe(2);
    expect(migrated.deploymentType).toBe("docker");
  });
});
Run migration tests:
pnpm test packages/core/src/migrations.test.ts

Best Practices

  • Always backup configs before upgrading versions
  • Test migrations in a non-production environment first
  • Review changelogs for breaking changes
  • Keep dependencies up to date to avoid large migration jumps
  • Validate after migration to catch issues early
  • Never skip versions — migrations are sequential
  • Don’t manually edit configVersion without applying corresponding changes
  • Don’t assume forward compatibility — newer versions may not read older schemas

Getting Help

If you encounter migration issues:
  1. Check GitHub Issues for similar problems
  2. Review CHANGELOG.md for migration notes
  3. Ask in GitHub Discussions
  4. Open an issue with:
    • Current config version
    • Target config version
    • Full error message
    • Config file (redact secrets)

Build docs developers (and LLMs) love