@webiny/mcp 0.0.0-unstable.6844005670

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/Extension.d.ts +2 -0
  2. package/Extension.js +11 -0
  3. package/Extension.js.map +1 -0
  4. package/LICENSE +21 -0
  5. package/README.md +11 -0
  6. package/agents/claude.d.ts +15 -0
  7. package/agents/claude.js +33 -0
  8. package/agents/claude.js.map +1 -0
  9. package/agents/cline.d.ts +17 -0
  10. package/agents/cline.js +29 -0
  11. package/agents/cline.js.map +1 -0
  12. package/agents/copilot.d.ts +17 -0
  13. package/agents/copilot.js +64 -0
  14. package/agents/copilot.js.map +1 -0
  15. package/agents/cursor.d.ts +15 -0
  16. package/agents/cursor.js +33 -0
  17. package/agents/cursor.js.map +1 -0
  18. package/agents/instructions.d.ts +7 -0
  19. package/agents/instructions.js +13 -0
  20. package/agents/instructions.js.map +1 -0
  21. package/agents/shared.d.ts +41 -0
  22. package/agents/shared.js +124 -0
  23. package/agents/shared.js.map +1 -0
  24. package/agents/windsurf.d.ts +15 -0
  25. package/agents/windsurf.js +33 -0
  26. package/agents/windsurf.js.map +1 -0
  27. package/cli/ConfigureMcp.d.ts +15 -0
  28. package/cli/ConfigureMcp.js +57 -0
  29. package/cli/ConfigureMcp.js.map +1 -0
  30. package/cli/McpServer.d.ts +12 -0
  31. package/cli/McpServer.js +239 -0
  32. package/cli/McpServer.js.map +1 -0
  33. package/index.d.ts +1 -0
  34. package/index.js +3 -0
  35. package/index.js.map +1 -0
  36. package/package.json +49 -0
  37. package/skills/admin-ui-extensions/SKILL.md +267 -0
  38. package/skills/cli-extensions/SKILL.md +133 -0
  39. package/skills/content-models/SKILL.md +306 -0
  40. package/skills/custom-graphql-api/SKILL.md +199 -0
  41. package/skills/dependency-injection/SKILL.md +252 -0
  42. package/skills/infrastructure-extensions/SKILL.md +192 -0
  43. package/skills/lifecycle-events/SKILL.md +203 -0
  44. package/skills/local-development/SKILL.md +245 -0
  45. package/skills/project-structure/SKILL.md +156 -0
  46. package/skills/webiny-sdk/SKILL.md +271 -0
  47. package/skills/website-builder/SKILL.md +384 -0
@@ -0,0 +1,252 @@
1
+ ---
2
+ name: webiny-dependency-injection
3
+ context: webiny-extensions
4
+ description: >
5
+ The universal createImplementation DI pattern and all injectable services.
6
+ Use this skill when the developer is writing any Webiny extension and needs to understand
7
+ dependency injection, constructor injection, how to access Logger/BuildParams/IdentityContext,
8
+ how to inject CMS use-cases (list/get/create/update/delete entries), or how the dependencies
9
+ array works. This is the connective tissue across all extension types -- API, Admin, CLI,
10
+ and Infrastructure.
11
+ ---
12
+
13
+ # Dependency Injection Patterns
14
+
15
+ ## TL;DR
16
+
17
+ Every Webiny extension type uses the same DI pattern: define a class implementing `*.Interface`, declare dependencies in the constructor, and export via `*.createImplementation({ implementation, dependencies })`. The DI container automatically provides the required services, ensures type safety, and validates at compile time. This pattern is the connective tissue across all extension types -- API, Admin, CLI, and Infrastructure.
18
+
19
+ ## The Universal Pattern
20
+
21
+ ```typescript
22
+ import { SomeFactory } from "webiny/some/path";
23
+ import { Logger } from "webiny/api/logger";
24
+ import { BuildParams } from "webiny/api/build-params";
25
+
26
+ class MyImplementation implements SomeFactory.Interface {
27
+ constructor(
28
+ private logger: Logger.Interface,
29
+ private buildParams: BuildParams.Interface
30
+ ) {}
31
+
32
+ execute(/* factory-specific params */) {
33
+ this.logger.info("Doing something...");
34
+ const value = this.buildParams.get<string>("MY_PARAM");
35
+ }
36
+ }
37
+
38
+ export default SomeFactory.createImplementation({
39
+ implementation: MyImplementation,
40
+ dependencies: [Logger, BuildParams]
41
+ });
42
+ ```
43
+
44
+ Key rules:
45
+ 1. **One class per file** -- each extension file exports a single implementation.
46
+ 2. **Constructor injection** -- dependencies are received as constructor parameters, in the same order as the `dependencies` array.
47
+ 3. **Dependencies array** -- must exactly match the constructor parameter order and types.
48
+ 4. **Interface types** -- always type constructor params as `Feature.Interface`.
49
+
50
+ ## Where This Pattern Appears
51
+
52
+ | Extension Type | Factory | Import Path |
53
+ |---|---|---|
54
+ | Content Models | `ModelFactory` | `"webiny/api/cms/model"` |
55
+ | GraphQL Schemas | `GraphQLSchemaFactory` | `"webiny/api/graphql"` |
56
+ | CMS Entry Hooks | `EntryBeforeCreateEventHandler`, etc. | `"webiny/api/cms/entry"` |
57
+ | API Key Hooks | `ApiKeyAfterUpdateHandler` | `"webiny/api/security/apiKey"` |
58
+ | API Keys | `ApiKeyFactory` | `"webiny/api/security"` |
59
+ | CLI Commands | `CliCommandFactory` | `"webiny/cli/command"` |
60
+ | Pulumi Handlers | `CorePulumi` | `"webiny/infra/core"` |
61
+
62
+ ## Injectable Services
63
+
64
+ ### Utility Services
65
+
66
+ | Service | Import | Interface | Available In | Purpose |
67
+ |---|-----------------------------|---|---|---|
68
+ | `Logger` | `"webiny/api/logger"` | `Logger.Interface` | API | Logging (persists to CloudWatch) |
69
+ | `BuildParams` | `"webiny/api/build-params"` | `BuildParams.Interface` | API | Access build-time parameters |
70
+ | `Ui` (CLI) | `"webiny/cli"` | `Ui.Interface` | CLI | Terminal output formatting |
71
+ | `Ui` (Infra) | `"webiny/infra"` | `Ui.Interface` | Infra | Terminal output during deploy |
72
+
73
+ ### Logger Methods
74
+
75
+ ```typescript
76
+ this.logger.info("Informational message");
77
+ this.logger.warn("Warning message");
78
+ this.logger.error("Error message");
79
+ this.logger.debug("Debug message");
80
+ ```
81
+
82
+ ### BuildParams Methods
83
+
84
+ ```typescript
85
+ // Get a string parameter
86
+ const value = this.buildParams.get<string>("MY_PARAM");
87
+
88
+ // Get a complex parameter
89
+ const config = this.buildParams.get<{ myKey: number; nested: { foo: string } }>("MY_CONFIG");
90
+ ```
91
+
92
+ Parameters are set in `webiny.config.tsx`:
93
+
94
+ ```tsx
95
+ <Api.BuildParam paramName="MY_PARAM" value="customValue" />
96
+ <Api.BuildParam paramName="MY_CONFIG" value={{ myKey: 2, nested: { foo: "bar" } }} />
97
+ ```
98
+
99
+ ### Core Context Features
100
+
101
+ | Feature | Import Path | Purpose |
102
+ |---|---|---|
103
+ | `IdentityContext` | `"webiny/api/security"` or `"@webiny/api-core/features/IdentityContext"` | Current user identity and permissions |
104
+ | `TenantContext` | `"@webiny/api-core/features/TenantContext"` | Current tenant information |
105
+ | `EventPublisher` | `"@webiny/api-core/features/EventPublisher"` | Publish domain events |
106
+ | `WcpContext` | `"@webiny/api-core/features/WcpContext"` | Webiny Control Panel integration |
107
+ | `GetSettings` | `"@webiny/api-core/features/settings/GetSettings"` | Retrieve settings records |
108
+ | `UpdateSettings` | `"@webiny/api-core/features/settings/UpdateSettings"` | Create/update settings records |
109
+
110
+ ### Headless CMS Use-Cases
111
+
112
+ | Feature | Import Path | Purpose |
113
+ |---|---|---|
114
+ | `GetEntryByIdUseCase` | `"@webiny/api-headless-cms/features/contentEntry/GetEntryById"` | Fetch entry by revision ID |
115
+ | `GetEntryUseCase` | `"@webiny/api-headless-cms/features/contentEntry/GetEntry"` | Get entry by query |
116
+ | `ListLatestEntriesUseCase` | `"@webiny/api-headless-cms/features/contentEntry/ListEntries"` | List latest entries |
117
+ | `ListPublishedEntriesUseCase` | `"@webiny/api-headless-cms/features/contentEntry/ListEntries"` | List published entries |
118
+ | `ListDeletedEntriesUseCase` | `"@webiny/api-headless-cms/features/contentEntry/ListEntries"` | List deleted entries |
119
+ | `CreateEntryUseCase` | `"@webiny/api-headless-cms/features/contentEntry/CreateEntry"` | Create entry |
120
+ | `UpdateEntryUseCase` | `"@webiny/api-headless-cms/features/contentEntry/UpdateEntry"` | Update entry |
121
+ | `DeleteEntryUseCase` | `"@webiny/api-headless-cms/features/contentEntry/DeleteEntry"` | Delete entry |
122
+ | `GetModelUseCase` | `"@webiny/api-headless-cms/features/contentModel/GetModel"` | Get model by ID |
123
+ | `ListModelsUseCase` | `"@webiny/api-headless-cms/features/contentModel/ListModels"` | List all models |
124
+ | `GetModelRepository` | `"@webiny/api-headless-cms/features/contentModel/GetModel"` | Fetch model from cache |
125
+ | `ListModelsRepository` | `"@webiny/api-headless-cms/features/contentModel/ListModels"` | Fetch all models from cache |
126
+ | `ModelsFetcher` | `"@webiny/api-headless-cms/features/contentModel/shared"` | Centralized model fetching |
127
+ | `ListEntriesRepository` | `"@webiny/api-headless-cms/features/contentEntry/ListEntries"` | Storage-level entry fetching |
128
+
129
+ ### Tenancy Use-Cases
130
+
131
+ | Feature | Import Path | Purpose |
132
+ |---|---|---|
133
+ | `GetTenantByIdUseCase` | `"@webiny/api-core/features/tenancy/GetTenantById"` | Fetch tenant by ID |
134
+ | `CreateTenantUseCase` | `"@webiny/api-core/features/tenancy/CreateTenant"` | Create a tenant |
135
+ | `UpdateTenantUseCase` | `"@webiny/api-core/features/tenancy/UpdateTenant"` | Update a tenant |
136
+ | `DeleteTenantUseCase` | `"@webiny/api-core/features/tenancy/DeleteTenant"` | Delete a tenant |
137
+ | `InstallTenantUseCase` | `"@webiny/api-core/features/tenancy/InstallTenant"` | Install a tenant |
138
+
139
+ ## Examples Across Extension Types
140
+
141
+ ### API Extension (GraphQL Schema with DI)
142
+
143
+ ```typescript
144
+ import { GraphQLSchemaFactory } from "webiny/api/graphql";
145
+ import { IdentityContext } from "webiny/api/security";
146
+
147
+ class SchemaImpl implements GraphQLSchemaFactory.Interface {
148
+ constructor(private identityContext: IdentityContext.Interface) {}
149
+
150
+ execute(): GraphQLSchemaFactory.Return {
151
+ return [{
152
+ typeDefs: /* GraphQL */ `
153
+ type Query { whoAmI: String }
154
+ `,
155
+ resolvers: {
156
+ Query: {
157
+ whoAmI: () => {
158
+ const identity = this.identityContext.getIdentity();
159
+ return `Hello, ${identity.displayName}!`;
160
+ }
161
+ }
162
+ }
163
+ }];
164
+ }
165
+ }
166
+
167
+ export default GraphQLSchemaFactory.createImplementation({
168
+ implementation: SchemaImpl,
169
+ dependencies: [IdentityContext]
170
+ });
171
+ ```
172
+
173
+ ### CMS Lifecycle Hook with DI
174
+
175
+ ```typescript
176
+ import { EntryBeforeCreateEventHandler as Handler } from "webiny/api/cms/entry";
177
+ import { Logger } from "webiny/api/logger";
178
+
179
+ class MyHookImpl implements Handler.Interface {
180
+ constructor(private logger: Logger.Interface) {}
181
+
182
+ async handle(event: Handler.Event): Promise<void> {
183
+ this.logger.info(`Entry created for model: ${event.modelId}`);
184
+ }
185
+ }
186
+
187
+ export default Handler.createImplementation({
188
+ implementation: MyHookImpl,
189
+ dependencies: [Logger]
190
+ });
191
+ ```
192
+
193
+ ### CLI Command with DI
194
+
195
+ ```typescript
196
+ import { Ui } from "webiny/cli";
197
+ import { CliCommandFactory } from "webiny/cli/command";
198
+
199
+ class MyCommandImpl implements CliCommandFactory.Interface<{ name: string }> {
200
+ constructor(private ui: Ui.Interface) {}
201
+
202
+ execute(): CliCommandFactory.CommandDefinition<{ name: string }> {
203
+ return {
204
+ name: "greet",
205
+ description: "Greet someone",
206
+ params: [{ name: "name", description: "Name", type: "string" }],
207
+ handler: async params => {
208
+ this.ui.success(`Hello, ${params.name}!`);
209
+ }
210
+ };
211
+ }
212
+ }
213
+
214
+ export default CliCommandFactory.createImplementation({
215
+ implementation: MyCommandImpl,
216
+ dependencies: [Ui]
217
+ });
218
+ ```
219
+
220
+ ### Pulumi Handler with DI
221
+
222
+ ```typescript
223
+ import { Ui } from "webiny/infra";
224
+ import { CorePulumi } from "webiny/infra/core";
225
+
226
+ class MyPulumiImpl implements CorePulumi.Interface {
227
+ constructor(private ui: Ui.Interface) {}
228
+
229
+ execute(app: any) {
230
+ this.ui.info("Deploying with environment:", app.env);
231
+ }
232
+ }
233
+
234
+ export default CorePulumi.createImplementation({
235
+ implementation: MyPulumiImpl,
236
+ dependencies: [Ui]
237
+ });
238
+ ```
239
+
240
+ ## Key Rules
241
+
242
+ 1. Always import from the **feature path**, not the package root.
243
+ 2. Use `Feature.Interface` for constructor parameter types.
244
+ 3. The `dependencies` array order must match the constructor parameter order.
245
+ 4. Read the `abstractions.ts` file in the feature folder to see available methods.
246
+ 5. Extensions with no dependencies use `dependencies: []`.
247
+
248
+ ## Related Skills
249
+
250
+ - `custom-graphql-api` -- DI in GraphQL schema extensions
251
+ - `lifecycle-events` -- DI in lifecycle event handlers
252
+ - `cli-extensions` -- DI in CLI command extensions
@@ -0,0 +1,192 @@
1
+ ---
2
+ name: webiny-infrastructure-extensions
3
+ context: webiny-extensions
4
+ description: >
5
+ Modifying AWS infrastructure using Pulumi handlers and declarative Infra components.
6
+ Use this skill when the developer wants to customize AWS infrastructure, add Pulumi handlers,
7
+ configure OpenSearch, VPC, resource tags, regions, custom domains, blue-green deployments,
8
+ environment-conditional config, or manage production vs development infrastructure modes.
9
+ Covers CorePulumi.Interface, all <Infra.*> declarative components, and <Infra.Env.Is>.
10
+ ---
11
+
12
+ # Infrastructure Extensions
13
+
14
+ ## TL;DR
15
+
16
+ Infrastructure extensions modify your AWS infrastructure using Pulumi handlers and declarative `<Infra.*>` components in `webiny.config.tsx`. Pulumi handlers implement `CorePulumi.Interface` and are registered via `<Infra.Core.Pulumi>`. Declarative components configure OpenSearch, VPC, tags, regions, custom domains, blue-green deployments, and environment-specific settings.
17
+
18
+ ## Pulumi Handler Pattern
19
+
20
+ Write custom Pulumi code that runs during infrastructure deployment:
21
+
22
+ ```typescript
23
+ // extensions/MyCorePulumiHandler.ts
24
+ import { Ui } from "webiny/infra";
25
+ import { CorePulumi } from "webiny/infra/core";
26
+
27
+ class MyCorePulumiHandlerImpl implements CorePulumi.Interface {
28
+ constructor(private ui: Ui.Interface) {}
29
+
30
+ execute(app: any) {
31
+ this.ui.info("Executing MyCorePulumiHandler with environment:", app.env);
32
+
33
+ // Access and modify Pulumi resources here
34
+ // app.resources gives you access to all provisioned resources
35
+ }
36
+ }
37
+
38
+ export default CorePulumi.createImplementation({
39
+ implementation: MyCorePulumiHandlerImpl,
40
+ dependencies: [Ui]
41
+ });
42
+ ```
43
+
44
+ Register:
45
+
46
+ ```tsx
47
+ <Infra.Core.Pulumi src={"/extensions/MyCorePulumiHandler.ts"} />
48
+ ```
49
+
50
+ ### Use Cases for Pulumi Handlers
51
+
52
+ - Add custom AWS resources (CloudWatch alarms, extra S3 buckets, Lambda functions)
53
+ - Modify existing resource properties (Lambda memory, timeouts, environment variables)
54
+ - Add conditional infrastructure based on environment
55
+ - Integrate with third-party infrastructure providers
56
+
57
+ ## Declarative Infrastructure Components
58
+
59
+ These components go directly in `webiny.config.tsx` -- no separate extension file needed:
60
+
61
+ ### AWS Configuration
62
+
63
+ ```tsx
64
+ {/* Set the AWS region */}
65
+ <Infra.Aws.DefaultRegion name={"us-east-1"} />
66
+
67
+ {/* Apply tags to all AWS resources -- multiple calls are merged */}
68
+ <Infra.Aws.Tags tags={{ OWNER: "me", PROJECT: "my-project" }} />
69
+ <Infra.Aws.Tags tags={{ COST_CENTER: "engineering" }} />
70
+ ```
71
+
72
+ ### Search & Networking
73
+
74
+ ```tsx
75
+ {/* Enable/disable OpenSearch */}
76
+ <Infra.OpenSearch enabled={true} />
77
+
78
+ {/* Enable/disable VPC deployment */}
79
+ <Infra.Vpc enabled={false} />
80
+ ```
81
+
82
+ ### Resource Naming
83
+
84
+ ```tsx
85
+ {/* Prefix all Pulumi resource names */}
86
+ <Infra.PulumiResourceNamePrefix prefix={"myproj-"} />
87
+
88
+ {/* Define which environments use production-grade infrastructure */}
89
+ <Infra.ProductionEnvironments environments={["prod", "staging"]} />
90
+ ```
91
+
92
+ ### Custom Domains
93
+
94
+ ```tsx
95
+ <Infra.Admin.CustomDomains
96
+ domains={["admin.example.com"]}
97
+ sslMethod="sni-only"
98
+ certificateArn="arn:aws:acm:us-east-1:123456789:certificate/abc-123"
99
+ />
100
+ ```
101
+
102
+ ### Blue-Green Deployments
103
+
104
+ ```tsx
105
+ <Infra.BlueGreenDeployments
106
+ enabled={true}
107
+ domains={{
108
+ acmCertificateArn: "arn:aws:acm:us-east-1:123456789:certificate/abc-123",
109
+ sslSupportMethod: "sni-only",
110
+ domains: {
111
+ api: ["api.example.com"],
112
+ admin: ["admin.example.com"],
113
+ website: ["website.example.com"],
114
+ preview: ["preview.example.com"]
115
+ }
116
+ }}
117
+ deployments={[
118
+ { name: "green", env: "dev", variant: "green" },
119
+ { name: "blue", env: "dev", variant: "blue" }
120
+ ]}
121
+ />
122
+ ```
123
+
124
+ ## Environment-Conditional Configuration
125
+
126
+ Use `<Infra.Env.Is>` to apply settings only in specific environments:
127
+
128
+ ```tsx
129
+ {/* Production only */}
130
+ <Infra.Env.Is env="prod">
131
+ <Infra.Aws.Tags tags={{ ENV: "production" }} />
132
+ <Infra.OpenSearch enabled={true} />
133
+ </Infra.Env.Is>
134
+
135
+ {/* Non-production (accepts array) */}
136
+ <Infra.Env.Is env={["dev", "staging"]}>
137
+ <Infra.Aws.Tags tags={{ ENV: "non-production" }} />
138
+ <Infra.OpenSearch enabled={false} />
139
+ </Infra.Env.Is>
140
+ ```
141
+
142
+ ## Project-Level Settings
143
+
144
+ ```tsx
145
+ {/* Disable telemetry */}
146
+ <Project.Telemetry enabled={false} />
147
+
148
+ {/* Auto-install for CI/CD (skip the installation wizard) */}
149
+ {process.env.WEBINY_CLI_AUTO_INSTALL && (
150
+ <Project.AutoInstall
151
+ adminUser={{
152
+ firstName: "Ad",
153
+ lastName: "Min",
154
+ email: "admin@webiny.com",
155
+ password: "12345678"
156
+ }}
157
+ />
158
+ )}
159
+ ```
160
+
161
+ ## All Infrastructure Components Reference
162
+
163
+ | Component | Purpose |
164
+ |---|---|
165
+ | `<Infra.Aws.DefaultRegion name="..." />` | Set the AWS region |
166
+ | `<Infra.Aws.Tags tags={{ ... }} />` | Tag all AWS resources |
167
+ | `<Infra.OpenSearch enabled={bool} />` | Enable/disable OpenSearch cluster |
168
+ | `<Infra.Vpc enabled={bool} />` | Enable/disable VPC deployment |
169
+ | `<Infra.PulumiResourceNamePrefix prefix="..." />` | Prefix Pulumi resource names |
170
+ | `<Infra.ProductionEnvironments environments={[...]} />` | Define production-grade environments |
171
+ | `<Infra.Admin.CustomDomains ... />` | Custom domains for Admin app |
172
+ | `<Infra.BlueGreenDeployments ... />` | Blue-green deployment configuration |
173
+ | `<Infra.Env.Is env="..." />` | Conditional config per environment |
174
+ | `<Infra.Core.Pulumi src="..." />` | Register a custom Pulumi handler |
175
+ | `<Project.Telemetry enabled={bool} />` | Enable/disable telemetry |
176
+ | `<Project.AutoInstall adminUser={{ ... }} />` | Auto-install for CI/CD |
177
+
178
+ ## Quick Reference
179
+
180
+ ```
181
+ Pulumi import: import { CorePulumi } from "webiny/infra/core";
182
+ Ui import: import { Ui } from "webiny/infra";
183
+ Interface: CorePulumi.Interface
184
+ Export: CorePulumi.createImplementation({ implementation, dependencies })
185
+ Register: <Infra.Core.Pulumi src={"/extensions/MyHandler.ts"} />
186
+ Deploy: yarn webiny deploy core (infrastructure changes)
187
+ ```
188
+
189
+ ## Related Skills
190
+
191
+ - `project-structure` -- Full `webiny.config.tsx` anatomy
192
+ - `local-development` -- Deployment commands and environment management
@@ -0,0 +1,203 @@
1
+ ---
2
+ name: webiny-lifecycle-events
3
+ context: webiny-extensions
4
+ description: >
5
+ CMS entry lifecycle hooks and security event handlers.
6
+ Use this skill when the developer wants to intercept content entry operations
7
+ (create, update, delete, publish, unpublish), validate data before save, auto-calculate
8
+ fields, send notifications after operations, enforce security policies, sync with external
9
+ systems, or hook into API key updates. Covers before/after hooks, event filtering by modelId,
10
+ payload mutation, and the Logger/BuildParams DI services.
11
+ ---
12
+
13
+ # Lifecycle Events
14
+
15
+ ## TL;DR
16
+
17
+ Webiny fires lifecycle events before and after CMS entry operations (create, update, delete, publish, unpublish) and security operations (API key updates). You hook into these by implementing the corresponding `EventHandler.Interface`, filtering by `modelId`, and optionally mutating `payload.values` before data is saved. Register as `<Api.Extension>`.
18
+
19
+ ## CMS Entry Lifecycle Events
20
+
21
+ ### Available Hooks
22
+
23
+ | Hook | Import From | Fires When |
24
+ |---|---|---|
25
+ | `EntryBeforeCreateEventHandler` | `"webiny/api/cms/entry"` | Before a new entry is saved |
26
+ | `EntryAfterCreateEventHandler` | `"webiny/api/cms/entry"` | After a new entry is saved |
27
+ | `EntryBeforeUpdateEventHandler` | `"webiny/api/cms/entry"` | Before an existing entry is updated |
28
+ | `EntryAfterUpdateEventHandler` | `"webiny/api/cms/entry"` | After an existing entry is updated |
29
+ | `EntryBeforeDeleteEventHandler` | `"webiny/api/cms/entry"` | Before an entry is deleted |
30
+ | `EntryAfterDeleteEventHandler` | `"webiny/api/cms/entry"` | After an entry is deleted |
31
+ | `EntryBeforePublishEventHandler` | `"webiny/api/cms/entry"` | Before an entry is published |
32
+ | `EntryAfterPublishEventHandler` | `"webiny/api/cms/entry"` | After an entry is published |
33
+
34
+ ### The Event Object
35
+
36
+ Every handler receives an `event` with:
37
+
38
+ - `event.modelId` -- The model ID string (e.g., `"contactSubmission"`)
39
+ - `event.payload` -- The entry data object
40
+ - `event.payload.values` -- The field values (can be mutated in `before` hooks)
41
+
42
+ ### Pattern
43
+
44
+ ```typescript
45
+ import { EntryBeforeCreateEventHandler as Handler } from "webiny/api/cms/entry";
46
+
47
+ class MyHookImpl implements Handler.Interface {
48
+ async handle(event: Handler.Event): Promise<void> {
49
+ const { payload, modelId } = event;
50
+
51
+ // 1. Filter by model -- handlers fire for ALL models
52
+ if (modelId !== "myTargetModel") {
53
+ return;
54
+ }
55
+
56
+ // 2. Read values
57
+ const someField = payload.values?.someField as string;
58
+
59
+ // 3. Mutate values (before hooks only)
60
+ if (!payload.values) {
61
+ payload.values = {};
62
+ }
63
+ payload.values.computedField = "computed value";
64
+ }
65
+ }
66
+
67
+ export default Handler.createImplementation({
68
+ implementation: MyHookImpl,
69
+ dependencies: []
70
+ });
71
+ ```
72
+
73
+ Register in `webiny.config.tsx`:
74
+
75
+ ```tsx
76
+ <Api.Extension src={"/extensions/MyHook.ts"} />
77
+ ```
78
+
79
+ ### When to Use Before vs After
80
+
81
+ | Use Case | Hook Type | Why |
82
+ |---|---|---|
83
+ | Validate data | `Before` | Reject or modify before persistence |
84
+ | Auto-calculate fields | `Before` | Set computed values before save |
85
+ | Send email notification | `After` | Ensure data is persisted first |
86
+ | Sync with external CRM | `After` | Side effect after successful save |
87
+ | Enforce security policies | `Before` | Block operations before they happen |
88
+
89
+ ## Full Example: Email Classification Hook
90
+
91
+ This hook intercepts contact form submissions, checks the email domain, and automatically classifies it as "work" or "personal":
92
+
93
+ ```typescript
94
+ // extensions/contactSubmission/ContactSubmissionHook.ts
95
+ import { EntryBeforeCreateEventHandler as Handler } from "webiny/api/cms/entry";
96
+ import { Logger } from "webiny/api/logger";
97
+
98
+ const PERSONAL_EMAIL_DOMAINS = [
99
+ "gmail.com",
100
+ "yahoo.com",
101
+ "hotmail.com",
102
+ "outlook.com",
103
+ "aol.com",
104
+ "icloud.com",
105
+ "protonmail.com"
106
+ ];
107
+
108
+ class ContactSubmissionHookImpl implements Handler.Interface {
109
+ public constructor(private logger: Logger.Interface) {}
110
+
111
+ public async handle(event: Handler.Event): Promise<void> {
112
+ const { payload, modelId } = event;
113
+
114
+ // Only run for Contact Submission entries
115
+ if (modelId !== "contactSubmission") {
116
+ return;
117
+ }
118
+
119
+ this.logger.info(`Processing contact submission for model: ${modelId}`);
120
+
121
+ const email = payload.values?.email as string;
122
+ if (!email) {
123
+ this.logger.warn("No email found in contact submission");
124
+ return;
125
+ }
126
+
127
+ // Classify the email
128
+ const domain = email.split("@")[1]?.toLowerCase();
129
+ let type = "work";
130
+ if (domain && PERSONAL_EMAIL_DOMAINS.includes(domain)) {
131
+ type = "personal";
132
+ }
133
+
134
+ this.logger.info(`Classified email ${email} as ${type}`);
135
+
136
+ // Set the emailType field before the entry is saved
137
+ if (!payload.values) {
138
+ payload.values = {};
139
+ }
140
+ payload.values.emailType = type;
141
+ }
142
+ }
143
+
144
+ export default Handler.createImplementation({
145
+ implementation: ContactSubmissionHookImpl,
146
+ dependencies: [Logger]
147
+ });
148
+ ```
149
+
150
+ ## Security Lifecycle Events
151
+
152
+ ### API Key After Update
153
+
154
+ ```typescript
155
+ // extensions/MyApiKeyAfterUpdate.ts
156
+ import { ApiKeyAfterUpdateHandler } from "webiny/api/security/api-key";
157
+ import { Logger } from "webiny/api/logger";
158
+ import { BuildParams } from "webiny/api/build-params";
159
+
160
+ class MyApiKeyAfterUpdateImpl implements ApiKeyAfterUpdateHandler.Interface {
161
+ constructor(
162
+ private logger: Logger.Interface,
163
+ private buildParams: BuildParams.Interface
164
+ ) {}
165
+
166
+ async handle() {
167
+ this.logger.warn("An API key was updated!");
168
+
169
+ const param1 = this.buildParams.get<string>("MY_CUSTOM_BUILD_PARAM");
170
+ console.log(`Build param 1: ${param1}`);
171
+ }
172
+ }
173
+
174
+ const MyApiKeyAfterUpdate = ApiKeyAfterUpdateHandler.createImplementation({
175
+ implementation: MyApiKeyAfterUpdateImpl,
176
+ dependencies: [Logger, BuildParams]
177
+ });
178
+
179
+ export default MyApiKeyAfterUpdate;
180
+ ```
181
+
182
+ Register with a dedicated JSX element:
183
+
184
+ ```tsx
185
+ <Security.ApiKey.AfterUpdate src={"/extensions/MyApiKeyAfterUpdate.ts"} />
186
+ ```
187
+
188
+ ## Quick Reference
189
+
190
+ ```
191
+ CMS hooks import: import { EntryBeforeCreateEventHandler } from "webiny/api/cms/entry";
192
+ Security import: import { ApiKeyAfterUpdateHandler } from "webiny/api/security/apiKey";
193
+ Event shape: event.modelId (string), event.payload (object), event.payload.values (object)
194
+ Export: Handler.createImplementation({ implementation, dependencies })
195
+ Register CMS: <Api.Extension src={"/extensions/MyHook.ts"} />
196
+ Register Security: <Security.ApiKey.AfterUpdate src={"/extensions/MyHook.ts"} />
197
+ Deploy: yarn webiny deploy api
198
+ ```
199
+
200
+ ## Related Skills
201
+
202
+ - `content-models` -- Define the models your hooks target
203
+ - `dependency-injection` -- Inject Logger, BuildParams, and other services