Skip to content
Content
Skill

/v5-to-v6-migration

Migration patterns for converting v5 Webiny code to v6 architecture. Use this skill when migrating existing v5 plugins to v6 features, converting context plugins to DI services, adapting v5 event subscriptions to v6 EventHandlers, or understanding how v5 patterns translate to

BOOST
From plugin
webiny-js
8k76 skills3 MCP
Install
$ npx -y skills add webiny/webiny-js --skill v5-to-v6-migration --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/v5-to-v6-migration

Context preview

The summary Claude sees to decide when to auto-load this skill.

Migration patterns for converting v5 Webiny code to v6 architecture. Use this skill when migrating existing v5 plugins to v6 features, converting context plugins to DI services, adapting v5 event subscriptions to v6 EventHandlers, or understanding how v5 patterns translate to

SKILL.md

v5-to-v6-migration.SKILL.md
name: webiny-v5-to-v6-migration
description: >
  Migration patterns for converting v5 Webiny code to v6 architecture. Use this skill when
  migrating existing v5 plugins to v6 features, converting context plugins to DI services,
  adapting v5 event subscriptions to v6 EventHandlers, or understanding how v5 patterns
  translate to v6. Targeted at AI agents performing migrations.

v5 → v6 Migration Patterns

Overview

v6 replaces v5's plugin-based architecture with **feature-based DI**. The key shifts:

| v5 Concept | v6 Equivalent | | -------------------------------- | ------------------------------------------------------ | | `ContextPlugin` | `createAbstraction` + `createImplementation` (Service) | | Plugin array | `createFeature` + `container.register()` | | `context.myService` | DI injection via constructor | | `onEntryAfterCreate.subscribe()` | `EventHandler` feature | | `new GraphQLSchemaPlugin()` | `GraphQLSchemaFactory.createImplementation()` |

---

Pattern 1: Context Plugin → DI Service

v5

new ContextPlugin(async context => {
  context.lingotekService = {
    translate: async (docId, locale) => {
      /* ... */
    },
    getStatus: async docId => {
      /* ... */
    },
    deleteProject: async projectId => {
      /* ... */
    }
  };
});

v6

// features/lingotekService/abstractions.ts
import { createAbstraction } from "webiny/api";

export interface ILingotekService {
  translate(docId: string, locale: string): Promise<Result<void, Error>>;
  getStatus(docId: string): Promise<Result<TranslationStatus, Error>>;
  deleteProject(projectId: string): Promise<Result<void, Error>>;
}

export const LingotekService = createAbstraction<ILingotekService>("MyExt/LingotekService");

export namespace LingotekService {
  export type Interface = ILingotekService;
}

// features/lingotekService/LingotekService.ts
class LingotekServiceImpl implements LingotekService.Interface {
  constructor(private buildParams: BuildParams.Interface) {}

  async translate(docId: string, locale: string) {
    /* ... */
  }
  async getStatus(docId: string) {
    /* ... */
  }
  async deleteProject(projectId: string) {
    /* ... */
  }
}

export default LingotekService.createImplementation({
  implementation: LingotekServiceImpl,
  dependencies: [BuildParams]
});

// features/lingotekService/feature.ts
export const LingotekServiceFeature = createFeature({
  name: "LingotekService",
  register(container) {
    container.register(LingotekServiceImpl).inSingletonScope();
  }
});

**Key difference:** v5 attaches to context object. v6 uses DI — consumers declare the service as a constructor dependency.

---

Pattern 2: Event Subscription → EventHandler Feature

v5

context.cms.onEntryAfterCreate.subscribe(async params => {
  if (params.model.modelId !== "myModel") return;
  await doSomething(params.entry);
});

v6

// features/syncOnCreate/EntryAfterCreateHandler.ts
import { EntryAfterCreateEventHandler } from "webiny/api/cms/entry";
import { LingotekService } from "../lingotekService/abstractions.js";
import { MY_MODEL_ID } from "~/shared/constants.js";

class SyncOnCreateHandler implements EntryAfterCreateEventHandler.Interface {
  constructor(private lingotekService: LingotekService.Interface) {}

  async handle(event: EntryAfterCreateEventHandler.Event) {
    const { entry, model } = event.payload;
    if (model.modelId !== MY_MODEL_ID) return;
    await this.lingotekService.translate(entry.entryId, "en");
  }
}

export default EntryAfterCreateEventHandler.createImplementation({
  implementation: SyncOnCreateHandler,
  dependencies: [LingotekService]
});

// features/syncOnCreate/feature.ts
export const SyncOnCreateFeature = createFeature({
  name: "SyncOnCreate",
  register(container) {
    container.register(SyncOnCreateHandler);
  }
});

**Key differences:**

  • Feature directory is named by **business capability** (`syncOnCreate`), not by event name
  • Handler is a thin orchestrator — business logic lives in the injected service
  • Must filter by `model.modelId` — handler fires for ALL models

---

Pattern 3: Plugin Array → Feature Registration

v5

export default () => [
    new GraphQLSchemaPlugin({ ... }),
    new ContextPlugin(async ctx => { ... }),
    myModelPlugin,
    eventSubscriptionPlugin
];

v6

// api/Extension.ts
import { createFeature } from "webiny/api";

export const Extension = createFeature({
  name: "MyExtension",
  register(container) {
    container.register(MyModel);
    container.register(MyGraphQLSchema);
    SyncOnCreateFeature.register(container);
    LingotekServiceFeature.register(container);
  }
});

---

Pattern 4: Async Service Bootstrap → ServiceProvider Pattern

When a v5 service was initialized with async data (loading settings, fetching config), v6 uses the **ServiceProvider** pattern — a provider abstraction with `async getService()` that lazily creates and caches the service.

See the **ServiceProvider Pattern** section in **webiny-api-architect** for the full pattern with abstractions, implementation, and consumer examples.

---

Pattern 6: Permissions objects

v5

[
  {
    name: "content.i18n",
    locales: ["en-US"]
  },
  {
    name: "cms.endpoint.read"
  },
  {
    name: "cms.endpoint.manage"
  },
  {
    name: "cms.endpoint.preview"
  },
  {
    name: "cms.contentModelGroup",
    groups: {
      "en-US": [LT_TRANSLATION_MODEL_GROUP_ID]
    },
    rwd: "rw",
    own: false,
    pw: ""
  },
  {
    name: "cms.contentModel",
    models: {
      "en-US": [
        LT_TRANSLATION_DOCUMENT_MODEL_ID,
        LT_CONFIG_MODEL_ID,
        LT_TRANSLATION_PROJECT_MODEL_ID
      ]
    },
    rwd: "rwd",
    own:
Read more
Ships withwebiny-js

Open-source content platform. Self-hosted on AWS serverless. Built as a TypeScript framework you extend with code, not a closed product you configure through a UI. Runs on Lambda, DynamoDB, S3, and CloudFront inside your own AWS account. Scales automatically.

Get the whole plugin
Stats
8,049
Stars
682
Forks
Active
Maintenance
TypeScript
Language
1d ago
Last commit
8y ago
Created
3d ago
Added

Repo: webiny/webiny-js

Other skills on webiny-js.