From 2d5febe57d63a4b7808336c3a6ecde165c727e02 Mon Sep 17 00:00:00 2001 From: webiny-bot Date: Tue, 21 Apr 2026 15:06:53 +0000 Subject: [PATCH 01/49] chore: generate changelog for 6.3.0 --- docs/release-notes/6.3.0/changelog.mdx | 88 ++++++++++++++++++++++ docs/release-notes/6.3.0/upgrade-guide.mdx | 43 +++++++++++ 2 files changed, 131 insertions(+) create mode 100644 docs/release-notes/6.3.0/changelog.mdx create mode 100644 docs/release-notes/6.3.0/upgrade-guide.mdx diff --git a/docs/release-notes/6.3.0/changelog.mdx b/docs/release-notes/6.3.0/changelog.mdx new file mode 100644 index 000000000..1b6228693 --- /dev/null +++ b/docs/release-notes/6.3.0/changelog.mdx @@ -0,0 +1,88 @@ +--- +id: uv5qkskt +title: Webiny 6.3.0 Changelog +description: See what's new in Webiny version 6.3.0 +--- + +import { GithubRelease } from "@/components/GithubRelease"; +import { Alert } from "@/components/Alert"; + + + +## Development + +### Typescript Upgraded to 6.0.2 ([#5043](https://github.com/webiny/webiny-js/pull/5043)) + +Webiny now uses Typescript 6.0.2 with module resolution set to `bundler`. This brings improved type inference and better alignment with modern bundler toolchains. + +### Install Version Flag for Upgrade Command ([#5115](https://github.com/webiny/webiny-js/pull/5115)) + +The `webiny upgrade` command now accepts an `--install-version` flag, letting you specify an exact package version to install during the upgrade process. This is useful when you want to test an unstable release before the stable version ships. + +``` +webiny upgrade --install-version=6.3.0-unstable.abc +``` + +### Old Pulumi Plugin Versions Now Cleaned Up ([#5101](https://github.com/webiny/webiny-js/pull/5101)) + +Previously, downloading new Pulumi plugins would leave old versions behind, causing the `.webiny/pulumi-cli` folder to grow over time. Old plugin versions are now automatically removed when newer versions are installed. + +## Webiny SDK + +### Introducing the `Ai` Service ([#5096](https://github.com/webiny/webiny-js/pull/5096)) + +A new `Ai` service provides a unified abstraction for working with language models across multiple providers. The service wraps the [Vercel AI SDK](https://sdk.vercel.ai/) and integrates with Webiny's dependency injection system. + +The service includes: + +- `generateText` and `streamText` methods for interacting with language models +- Built-in provider factories for Anthropic and OpenAI +- An `AiGateway` that routes requests based on a `provider/model` string format + +Configure providers via environment variables: + +- `WEBINY_API_ANTHROPIC_API_KEY` +- `WEBINY_API_OPENAI_API_KEY` + +Inject the service into any Route, UseCase, or handler: + +```typescript +import { Ai, Logger, Route } from "webiny/api"; + +class MyApiRouteImpl implements Route.Interface { + constructor( + private logger: Logger.Interface, + private aiService: Ai.Interface + ) {} + + async execute(request: Route.Request, reply: Route.Reply) { + const { text } = await this.aiService.generateText({ + model: "anthropic/claude-sonnet-4-5", + prompt: "Is this working?!" + }); + + return reply.send({ message: text }); + } +} + +export default Route.createImplementation({ + implementation: MyApiRouteImpl, + dependencies: [Logger, Ai] +}); +``` + +Switching providers is a one-line change to the model string — no other code changes required. + +### Encryption Passphrase Now Optional ([#5112](https://github.com/webiny/webiny-js/pull/5112)) + +The encryption service now works without a passphrase configured. When `` is omitted, `encrypt` and `decrypt` pass values through unchanged instead of throwing an error. This allows teams to adopt encryption gradually without requiring every environment to have a passphrase configured upfront. + +### Feature API Types Corrected ([#5108](https://github.com/webiny/webiny-js/pull/5108)) + +The second parameter in the Feature API's `register` method now correctly populates when defined via generics. + +## Admin + +### API Playground Renamed to GraphQL Playground ([#5103](https://github.com/webiny/webiny-js/pull/5103)) + +The "API Playground" label in the admin interface has been renamed to "GraphQL Playground" for clarity. diff --git a/docs/release-notes/6.3.0/upgrade-guide.mdx b/docs/release-notes/6.3.0/upgrade-guide.mdx new file mode 100644 index 000000000..b20459745 --- /dev/null +++ b/docs/release-notes/6.3.0/upgrade-guide.mdx @@ -0,0 +1,43 @@ +--- +id: ihp684dq +title: Upgrade from 6.2.x to 6.3.0 +description: Learn how to upgrade Webiny from 6.2.x to 6.3.0. +--- + +import { Alert } from "@/components/Alert"; +import { AdditionalNotes } from "@/components/upgrade/AdditionalNotes"; + + + +- how to upgrade Webiny from 6.2.x to 6.3.0 + + + + + +Make sure to check out the [6.3.0 changelog](./changelog) to get familiar with the changes introduced in this release. + + + +## Step-by-Step Guide + +### 1. Upgrade Webiny Packages + +Upgrade all Webiny NPM packages by running the following command: + +```bash +yarn up "@webiny/*@6.3.0" +``` + +Once the upgrade has finished, running the `yarn webiny --version` command in your terminal should return **6.3.0**. + +### 2. Deploy Your Project + +Proceed by redeploying your Webiny project: + +```bash +# Execute in your project root. +yarn webiny deploy --env {environment} +``` + + From d952264b4c3b0fd17e669dcdb922382e5f3354ff Mon Sep 17 00:00:00 2001 From: adrians5j Date: Wed, 22 Apr 2026 13:55:02 +0200 Subject: [PATCH 02/49] wip --- docs/release-notes/6.3.0/changelog.mdx | 50 -------------------------- 1 file changed, 50 deletions(-) diff --git a/docs/release-notes/6.3.0/changelog.mdx b/docs/release-notes/6.3.0/changelog.mdx index 1b6228693..4d866b823 100644 --- a/docs/release-notes/6.3.0/changelog.mdx +++ b/docs/release-notes/6.3.0/changelog.mdx @@ -27,56 +27,6 @@ webiny upgrade --install-version=6.3.0-unstable.abc Previously, downloading new Pulumi plugins would leave old versions behind, causing the `.webiny/pulumi-cli` folder to grow over time. Old plugin versions are now automatically removed when newer versions are installed. -## Webiny SDK - -### Introducing the `Ai` Service ([#5096](https://github.com/webiny/webiny-js/pull/5096)) - -A new `Ai` service provides a unified abstraction for working with language models across multiple providers. The service wraps the [Vercel AI SDK](https://sdk.vercel.ai/) and integrates with Webiny's dependency injection system. - -The service includes: - -- `generateText` and `streamText` methods for interacting with language models -- Built-in provider factories for Anthropic and OpenAI -- An `AiGateway` that routes requests based on a `provider/model` string format - -Configure providers via environment variables: - -- `WEBINY_API_ANTHROPIC_API_KEY` -- `WEBINY_API_OPENAI_API_KEY` - -Inject the service into any Route, UseCase, or handler: - -```typescript -import { Ai, Logger, Route } from "webiny/api"; - -class MyApiRouteImpl implements Route.Interface { - constructor( - private logger: Logger.Interface, - private aiService: Ai.Interface - ) {} - - async execute(request: Route.Request, reply: Route.Reply) { - const { text } = await this.aiService.generateText({ - model: "anthropic/claude-sonnet-4-5", - prompt: "Is this working?!" - }); - - return reply.send({ message: text }); - } -} - -export default Route.createImplementation({ - implementation: MyApiRouteImpl, - dependencies: [Logger, Ai] -}); -``` - -Switching providers is a one-line change to the model string — no other code changes required. - -### Encryption Passphrase Now Optional ([#5112](https://github.com/webiny/webiny-js/pull/5112)) - -The encryption service now works without a passphrase configured. When `` is omitted, `encrypt` and `decrypt` pass values through unchanged instead of throwing an error. This allows teams to adopt encryption gradually without requiring every environment to have a passphrase configured upfront. - ### Feature API Types Corrected ([#5108](https://github.com/webiny/webiny-js/pull/5108)) The second parameter in the Feature API's `register` method now correctly populates when defined via generics. From 107536cff592594c0adb2aaa17d39460f355f3c2 Mon Sep 17 00:00:00 2001 From: adrians5j Date: Wed, 22 Apr 2026 13:56:17 +0200 Subject: [PATCH 03/49] wip --- .../6.3.0/assets/graphql-playground.png | Bin 0 -> 48733 bytes docs/release-notes/6.3.0/changelog.mdx | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 docs/release-notes/6.3.0/assets/graphql-playground.png diff --git a/docs/release-notes/6.3.0/assets/graphql-playground.png b/docs/release-notes/6.3.0/assets/graphql-playground.png new file mode 100644 index 0000000000000000000000000000000000000000..cf76dd29e3c585fa9ff40cddf69bea0a083eef95 GIT binary patch literal 48733 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 Date: Wed, 22 Apr 2026 14:02:19 +0200 Subject: [PATCH 04/49] wip --- docs/release-notes/6.3.0/changelog.mdx | 107 +++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/docs/release-notes/6.3.0/changelog.mdx b/docs/release-notes/6.3.0/changelog.mdx index 81349ec5b..c6d45dd7f 100644 --- a/docs/release-notes/6.3.0/changelog.mdx +++ b/docs/release-notes/6.3.0/changelog.mdx @@ -38,3 +38,110 @@ The second parameter in the Feature API's `register` method now correctly popula The "API Playground" label in the admin interface has been renamed to "GraphQL Playground" for clarity. ![GraphQL Playground in the admin interface](./assets/graphql-playground.png) + +## AI PowerUps + +### AI-Powered Page Content Generation ([#5125](https://github.com/webiny/webiny-js/pull/5125), [#5117](https://github.com/webiny/webiny-js/pull/5117), [#5113](https://github.com/webiny/webiny-js/pull/5113), [#5111](https://github.com/webiny/webiny-js/pull/5111)) + +Webiny now includes AI-powered content generation capabilities through the new AI PowerUps feature. You can configure AI providers (OpenAI, Anthropic) and define personas in the Admin settings, then use AI to generate page content directly within the Page Builder. + +The feature includes: + +- **Provider configuration** — set up connections to OpenAI or Anthropic with your API keys +- **Personas** — define reusable AI personas with custom instructions for different content styles +- **Content generation** — generate page sections and content using natural language prompts +- **Tool pipeline** — AI-generated content is automatically processed through tools that convert text to Lexical editor format and resolve images + +API keys are stored securely using a new encryption service. To enable encryption, configure the passphrase in your infrastructure: + +```typescript +import { Infra } from "webiny/api"; + + +``` + +### AI Image Enrichment for File Manager ([#5104](https://github.com/webiny/webiny-js/pull/5104), [#5123](https://github.com/webiny/webiny-js/pull/5123)) + +Images uploaded to the File Manager are now automatically enriched with AI-generated metadata: + +- **Tags** — AI analyzes the image and assigns relevant tags for improved searchability +- **Description** — a human-readable description is generated and stored with the file + +Both fields can be viewed and edited manually in the file details panel. This runs as a background task after upload, so it doesn't block the upload process. + +## Webiny SDK + +### New AI Service for API Development ([#5096](https://github.com/webiny/webiny-js/pull/5096)) + +A new `Ai` service is available for building AI-powered features in your Webiny extensions. It provides a unified interface for working with language models from multiple providers. + +```typescript +import { Ai, Logger, Route } from "webiny/api"; + +class MyApiRouteImpl implements Route.Interface { + constructor( + private logger: Logger.Interface, + private aiService: Ai.Interface + ) {} + + async execute(request: Route.Request, reply: Route.Reply) { + const { text } = await this.aiService.generateText({ + model: "anthropic/claude-sonnet-4-5", + prompt: "Summarize the key features of Webiny" + }); + + return reply.send({ message: text }); + } +} + +export default Route.createImplementation({ + implementation: MyApiRouteImpl, + dependencies: [Logger, Ai] +}); +``` + +The service supports both `generateText` and `streamText` methods. Switch providers by changing the model string — no other code changes needed. Configure API keys via environment variables: + +- `WEBINY_API_ANTHROPIC_API_KEY` +- `WEBINY_API_OPENAI_API_KEY` + +### Tasks SDK Methods ([#5125](https://github.com/webiny/webiny-js/pull/5125)) + +New SDK methods are available for working with background tasks: + +```typescript +// Trigger a new task +const result = await sdk.tasks.triggerTask({ + definition: "aiImageTagging", + input: { fileId: "abc123" } +}); + +// List running tasks +const tasks = await sdk.tasks.listTasks({ status: "running" }); + +// List available task definitions +const definitions = await sdk.tasks.listDefinitions(); + +// Abort a running task +await sdk.tasks.abortTask({ taskId: "task-123" }); + +// Get task logs +const logs = await sdk.tasks.listLogs({ taskId: "task-123" }); +``` + +## Infrastructure + +### Made Encryption Passphrase Optional ([#5112](https://github.com/webiny/webiny-js/pull/5112)) + +The encryption service now works without a passphrase configured. When no passphrase is set, `encrypt` and `decrypt` pass values through unchanged instead of throwing an error. This allows teams to adopt encryption gradually without requiring every environment to have a passphrase configured upfront. + +## Admin + +### Extended Form Model Capabilities ([#5125](https://github.com/webiny/webiny-js/pull/5125)) + +The Admin form system now supports additional field types and layout options: + +- **Object fields** — group related fields into nested structures +- **Vertical tabs renderer** — organize form sections into vertical tab layouts +- **Textarea renderer** — multiline text input +- **Passthrough renderer** — render custom components within forms From d3c5a48ebf25cbc53c629631e063c07c7401a66e Mon Sep 17 00:00:00 2001 From: adrians5j Date: Wed, 22 Apr 2026 14:05:49 +0200 Subject: [PATCH 05/49] wip --- docs/release-notes/6.3.0/changelog.mdx | 92 ++++++-------------------- 1 file changed, 22 insertions(+), 70 deletions(-) diff --git a/docs/release-notes/6.3.0/changelog.mdx b/docs/release-notes/6.3.0/changelog.mdx index c6d45dd7f..056c9c466 100644 --- a/docs/release-notes/6.3.0/changelog.mdx +++ b/docs/release-notes/6.3.0/changelog.mdx @@ -9,6 +9,28 @@ import { Alert } from "@/components/Alert"; +## AI PowerUps + +### AI-Powered Page Content Generation ([#5125](https://github.com/webiny/webiny-js/pull/5125), [#5117](https://github.com/webiny/webiny-js/pull/5117), [#5113](https://github.com/webiny/webiny-js/pull/5113), [#5111](https://github.com/webiny/webiny-js/pull/5111)) + +Webiny now includes AI-powered content generation capabilities through the new AI PowerUps feature. You can configure AI providers (OpenAI, Anthropic) and define personas in the Admin settings, then use AI to generate page content directly within the Page Builder. + +The feature includes: + +- **Provider configuration** — set up connections to OpenAI or Anthropic with your API keys +- **Personas** — define reusable AI personas with custom instructions for different content styles +- **Content generation** — generate page sections and content using natural language prompts +- **Tool pipeline** — AI-generated content is automatically processed through tools that convert text to Lexical editor format and resolve images + +### AI Image Enrichment for File Manager ([#5104](https://github.com/webiny/webiny-js/pull/5104), [#5123](https://github.com/webiny/webiny-js/pull/5123)) + +Images uploaded to the File Manager are now automatically enriched with AI-generated metadata: + +- **Tags** — AI analyzes the image and assigns relevant tags for improved searchability +- **Description** — a human-readable description is generated and stored with the file + +Both fields can be viewed and edited manually in the file details panel. This runs as a background task after upload, so it doesn't block the upload process. + ## Development ### Typescript Upgraded to 6.0.2 ([#5043](https://github.com/webiny/webiny-js/pull/5043)) @@ -39,72 +61,8 @@ The "API Playground" label in the admin interface has been renamed to "GraphQL P ![GraphQL Playground in the admin interface](./assets/graphql-playground.png) -## AI PowerUps - -### AI-Powered Page Content Generation ([#5125](https://github.com/webiny/webiny-js/pull/5125), [#5117](https://github.com/webiny/webiny-js/pull/5117), [#5113](https://github.com/webiny/webiny-js/pull/5113), [#5111](https://github.com/webiny/webiny-js/pull/5111)) - -Webiny now includes AI-powered content generation capabilities through the new AI PowerUps feature. You can configure AI providers (OpenAI, Anthropic) and define personas in the Admin settings, then use AI to generate page content directly within the Page Builder. - -The feature includes: - -- **Provider configuration** — set up connections to OpenAI or Anthropic with your API keys -- **Personas** — define reusable AI personas with custom instructions for different content styles -- **Content generation** — generate page sections and content using natural language prompts -- **Tool pipeline** — AI-generated content is automatically processed through tools that convert text to Lexical editor format and resolve images - -API keys are stored securely using a new encryption service. To enable encryption, configure the passphrase in your infrastructure: - -```typescript -import { Infra } from "webiny/api"; - - -``` - -### AI Image Enrichment for File Manager ([#5104](https://github.com/webiny/webiny-js/pull/5104), [#5123](https://github.com/webiny/webiny-js/pull/5123)) - -Images uploaded to the File Manager are now automatically enriched with AI-generated metadata: - -- **Tags** — AI analyzes the image and assigns relevant tags for improved searchability -- **Description** — a human-readable description is generated and stored with the file - -Both fields can be viewed and edited manually in the file details panel. This runs as a background task after upload, so it doesn't block the upload process. - ## Webiny SDK -### New AI Service for API Development ([#5096](https://github.com/webiny/webiny-js/pull/5096)) - -A new `Ai` service is available for building AI-powered features in your Webiny extensions. It provides a unified interface for working with language models from multiple providers. - -```typescript -import { Ai, Logger, Route } from "webiny/api"; - -class MyApiRouteImpl implements Route.Interface { - constructor( - private logger: Logger.Interface, - private aiService: Ai.Interface - ) {} - - async execute(request: Route.Request, reply: Route.Reply) { - const { text } = await this.aiService.generateText({ - model: "anthropic/claude-sonnet-4-5", - prompt: "Summarize the key features of Webiny" - }); - - return reply.send({ message: text }); - } -} - -export default Route.createImplementation({ - implementation: MyApiRouteImpl, - dependencies: [Logger, Ai] -}); -``` - -The service supports both `generateText` and `streamText` methods. Switch providers by changing the model string — no other code changes needed. Configure API keys via environment variables: - -- `WEBINY_API_ANTHROPIC_API_KEY` -- `WEBINY_API_OPENAI_API_KEY` - ### Tasks SDK Methods ([#5125](https://github.com/webiny/webiny-js/pull/5125)) New SDK methods are available for working with background tasks: @@ -129,12 +87,6 @@ await sdk.tasks.abortTask({ taskId: "task-123" }); const logs = await sdk.tasks.listLogs({ taskId: "task-123" }); ``` -## Infrastructure - -### Made Encryption Passphrase Optional ([#5112](https://github.com/webiny/webiny-js/pull/5112)) - -The encryption service now works without a passphrase configured. When no passphrase is set, `encrypt` and `decrypt` pass values through unchanged instead of throwing an error. This allows teams to adopt encryption gradually without requiring every environment to have a passphrase configured upfront. - ## Admin ### Extended Form Model Capabilities ([#5125](https://github.com/webiny/webiny-js/pull/5125)) From 67fcef1eb408eb1fa0d781915048c4a4afe92338 Mon Sep 17 00:00:00 2001 From: adrians5j Date: Wed, 22 Apr 2026 14:07:07 +0200 Subject: [PATCH 06/49] wip --- docs/release-notes/6.3.0/changelog.mdx | 36 ++++++++++++-------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/docs/release-notes/6.3.0/changelog.mdx b/docs/release-notes/6.3.0/changelog.mdx index 056c9c466..874098b54 100644 --- a/docs/release-notes/6.3.0/changelog.mdx +++ b/docs/release-notes/6.3.0/changelog.mdx @@ -31,6 +31,23 @@ Images uploaded to the File Manager are now automatically enriched with AI-gener Both fields can be viewed and edited manually in the file details panel. This runs as a background task after upload, so it doesn't block the upload process. +## Admin + +### API Playground Renamed to GraphQL Playground ([#5103](https://github.com/webiny/webiny-js/pull/5103)) + +The "API Playground" label in the admin interface has been renamed to "GraphQL Playground" for clarity. + +![GraphQL Playground in the admin interface](./assets/graphql-playground.png) + +### Extended Form Model Capabilities ([#5125](https://github.com/webiny/webiny-js/pull/5125)) + +The Admin form system now supports additional field types and layout options: + +- **Object fields** — group related fields into nested structures +- **Vertical tabs renderer** — organize form sections into vertical tab layouts +- **Textarea renderer** — multiline text input +- **Passthrough renderer** — render custom components within forms + ## Development ### Typescript Upgraded to 6.0.2 ([#5043](https://github.com/webiny/webiny-js/pull/5043)) @@ -53,14 +70,6 @@ Previously, downloading new Pulumi plugins would leave old versions behind, caus The second parameter in the Feature API's `register` method now correctly populates when defined via generics. -## Admin - -### API Playground Renamed to GraphQL Playground ([#5103](https://github.com/webiny/webiny-js/pull/5103)) - -The "API Playground" label in the admin interface has been renamed to "GraphQL Playground" for clarity. - -![GraphQL Playground in the admin interface](./assets/graphql-playground.png) - ## Webiny SDK ### Tasks SDK Methods ([#5125](https://github.com/webiny/webiny-js/pull/5125)) @@ -86,14 +95,3 @@ await sdk.tasks.abortTask({ taskId: "task-123" }); // Get task logs const logs = await sdk.tasks.listLogs({ taskId: "task-123" }); ``` - -## Admin - -### Extended Form Model Capabilities ([#5125](https://github.com/webiny/webiny-js/pull/5125)) - -The Admin form system now supports additional field types and layout options: - -- **Object fields** — group related fields into nested structures -- **Vertical tabs renderer** — organize form sections into vertical tab layouts -- **Textarea renderer** — multiline text input -- **Passthrough renderer** — render custom components within forms From 092466979c00cf65bff759b11c66bf2fa8473a21 Mon Sep 17 00:00:00 2001 From: adrians5j Date: Thu, 23 Apr 2026 20:43:00 +0200 Subject: [PATCH 07/49] wip --- docs/developer-docs/6.x/navigation.tsx | 1 + .../6.x/reference/sdk/overview.mdx | 7 +- .../6.x/reference/sdk/tasks.ai.txt | 40 ++++ .../6.x/reference/sdk/tasks.mdx | 171 ++++++++++++++++++ 4 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 docs/developer-docs/6.x/reference/sdk/tasks.ai.txt create mode 100644 docs/developer-docs/6.x/reference/sdk/tasks.mdx diff --git a/docs/developer-docs/6.x/navigation.tsx b/docs/developer-docs/6.x/navigation.tsx index f45f8cf45..8afe85780 100644 --- a/docs/developer-docs/6.x/navigation.tsx +++ b/docs/developer-docs/6.x/navigation.tsx @@ -209,6 +209,7 @@ export const Navigation = ({ children }: { children: React.ReactNode }) => { + {/* __SDK_PAGES_END__ */} {/* __REFERENCE_PAGES_END__ */} diff --git a/docs/developer-docs/6.x/reference/sdk/overview.mdx b/docs/developer-docs/6.x/reference/sdk/overview.mdx index d6e748a6e..c88eb41ed 100644 --- a/docs/developer-docs/6.x/reference/sdk/overview.mdx +++ b/docs/developer-docs/6.x/reference/sdk/overview.mdx @@ -8,9 +8,9 @@ import { Alert } from "@/components/Alert"; - - How to initialize the Webiny SDK? - - What configuration options are available? - - What sub-SDKs are available and what they do? +- How to initialize the Webiny SDK? +- What configuration options are available? +- What sub-SDKs are available and what they do? @@ -56,6 +56,7 @@ const sdk = new Webiny({ | `sdk.fileManager` | [`FileManagerSdk`](/{version}/reference/sdk/file-manager) | Upload and manage files | | `sdk.languages` | [`LanguagesSdk`](/{version}/reference/sdk/languages) | Retrieve configured languages | | `sdk.tenantManager` | [`TenantManagerSdk`](/{version}/reference/sdk/tenant-manager) | Create and manage tenants | +| `sdk.tasks` | [`TasksSdk`](/{version}/reference/sdk/tasks) | Trigger and manage background tasks | ## Error Handling diff --git a/docs/developer-docs/6.x/reference/sdk/tasks.ai.txt b/docs/developer-docs/6.x/reference/sdk/tasks.ai.txt new file mode 100644 index 000000000..3d8bdc80c --- /dev/null +++ b/docs/developer-docs/6.x/reference/sdk/tasks.ai.txt @@ -0,0 +1,40 @@ +AI Context: Tasks SDK Reference (tasks.mdx) + +Source of Information: +1. /Users/adrian/dev/wby-next2/packages/sdk/src/TasksSdk.ts — class definition +2. /Users/adrian/dev/wby-next2/packages/sdk/src/methods/tasks/taskTypes.ts — type definitions +3. /Users/adrian/dev/wby-next2/packages/sdk/src/methods/tasks/triggerTask.ts — TriggerTaskParams +4. /Users/adrian/dev/wby-next2/packages/sdk/src/methods/tasks/abortTask.ts — AbortTaskParams +5. /Users/adrian/dev/wby-next2/packages/sdk/src/methods/tasks/listLogs.ts — ListLogsParams +6. /Users/adrian/dev/wby-next2/packages/sdk/src/methods/tasks/listDefinitions.ts +7. /Users/adrian/dev/wby-next2/packages/sdk/src/methods/tasks/listTasks.ts + +Key Documentation Decisions: +- Follows the same pattern as cms.mdx, file-manager.mdx, languages.mdx, and tenant-manager.mdx +- Types section added before Methods since TasksSdk has richer type hierarchy than other SDKs +- TaskStatus listed as a union type, not a table, since it is a simple string union +- TaskRun fields are marked optional with plain descriptions (not "ISO timestamp or undefined") +- listDefinitions and listTasks have no parameters — stated explicitly as "None" for clarity +- abortTask.message is optional — the underlying GraphQL mutation accepts null + +Tasks SDK Understanding: +- sdk.tasks is exposed on the Webiny class as a TasksSdk instance +- All five methods delegate to standalone functions in methods/tasks/ +- The triggerTask `definition` param maps to WebinyBackgroundTaskDefinitionEnum in GraphQL +- abortTask stops a running task at the next safe checkpoint (checked inside controller.runtime.isAborted()) +- listLogs.where.task filters by task run ID, not definition ID +- TaskLog groups items per iteration; each item has a type field (log level/category) defined by the task itself + +Related Documents: +- /6.x/background-tasks/about — how to create task definitions and trigger them from backend code +- /6.x/reference/sdk/overview — SDK initialization, error types, Result pattern + +Key Code Locations: +- TasksSdk class: /Users/adrian/dev/wby-next2/packages/sdk/src/TasksSdk.ts +- Task types: /Users/adrian/dev/wby-next2/packages/sdk/src/methods/tasks/taskTypes.ts +- Webiny class: /Users/adrian/dev/wby-next2/packages/sdk/src/Webiny.ts + +Tone Guidelines: +- Reference doc tone: concise, API-focused, minimal prose +- Tables for parameters and type fields; signatures as typed TypeScript blocks +- No analogies, no marketing language diff --git a/docs/developer-docs/6.x/reference/sdk/tasks.mdx b/docs/developer-docs/6.x/reference/sdk/tasks.mdx new file mode 100644 index 000000000..67ee167ef --- /dev/null +++ b/docs/developer-docs/6.x/reference/sdk/tasks.mdx @@ -0,0 +1,171 @@ +--- +id: t4x8wqnr +title: Tasks SDK Reference +description: API reference for the Tasks SDK — methods for triggering, monitoring, and aborting background tasks. +--- + +import { Alert } from "@/components/Alert"; + + + +- What methods are available on `sdk.tasks`? +- What parameters each method accepts? +- What each method returns? + + + + + +See [Background Tasks](/{version}/background-tasks/about) for a guide on creating and managing background tasks. + + + +## Overview + +The `TasksSdk` is accessed via `sdk.tasks` on a `Webiny` instance. It provides methods for triggering and managing background task executions over GraphQL. All methods return a `Result` type — use `result.isOk()` and `result.isFail()` to handle success and error cases. See the [SDK overview](/{version}/reference/sdk/overview) for initialization and error handling details. + +## Types + +### TaskStatus + +```typescript +type TaskStatus = "pending" | "running" | "completed" | "failed" | "aborted" | "stopped"; +``` + +### TaskDefinition + +Represents a registered background task definition. + +| Field | Type | Description | +| ------------- | -------- | ------------------------------------------ | +| `id` | `string` | The unique task definition ID | +| `title` | `string` | Human-readable title of the task | +| `description` | `string` | Optional description of what the task does | + +### TaskRun + +Represents a single background task execution. + +| Field | Type | Description | +| --------------- | ------------ | ---------------------------------------------- | +| `id` | `string` | The task run ID | +| `definitionId` | `string` | ID of the task definition that was triggered | +| `taskStatus` | `TaskStatus` | Current status of the task run | +| `startedOn` | `string` | ISO timestamp when the task started | +| `finishedOn` | `string` | ISO timestamp when the task finished | +| `name` | `string` | Optional display name for the task run | +| `iterations` | `number` | Number of execution iterations completed | +| `parentId` | `string` | ID of the parent task, if this is a child task | +| `executionName` | `string` | Internal execution name used by the system | +| `eventResponse` | `unknown` | Raw response from the trigger event | +| `input` | `unknown` | Input data passed when the task was triggered | +| `output` | `unknown` | Output data produced by the task | + +### TaskLog + +Represents a log group for a single task execution iteration. + +| Field | Type | Description | +| --------------- | --------------- | -------------------------------------------- | +| `id` | `string` | The log ID | +| `createdOn` | `string` | ISO timestamp when the log was created | +| `executionName` | `string` | Execution name associated with the log | +| `iteration` | `number` | Iteration number this log belongs to | +| `items` | `TaskLogItem[]` | Individual log entries within this log group | + +### TaskLogItem + +| Field | Type | Description | +| ----------- | --------- | ------------------------------ | +| `message` | `string` | The log message | +| `createdOn` | `string` | ISO timestamp of the log entry | +| `type` | `string` | Log level or category | +| `data` | `unknown` | Optional structured data | +| `error` | `unknown` | Optional error details | + +## Methods + +### listDefinitions + +Lists all registered background task definitions in the system. + +**Signature:** + +```typescript +sdk.tasks.listDefinitions(): Promise> +``` + +**Parameters:** None. + +--- + +### listTasks + +Lists all background task runs. + +**Signature:** + +```typescript +sdk.tasks.listTasks(): Promise> +``` + +**Parameters:** None. + +--- + +### listLogs + +Lists execution logs for background tasks. Optionally filter by task ID. + +**Signature:** + +```typescript +sdk.tasks.listLogs(params?: ListLogsParams): Promise> +``` + +**Parameters:** + +| Parameter | Type | Required | Description | +| ------------ | -------- | :------: | ------------------------------------- | +| `where` | `object` | No | Optional filter conditions | +| `where.task` | `string` | No | Filter logs to a specific task run ID | + +--- + +### triggerTask + +Triggers a background task run. + +**Signature:** + +```typescript +sdk.tasks.triggerTask(params: TriggerTaskParams): Promise> +``` + +**Parameters:** + +| Parameter | Type | Required | Description | +| ------------ | ------------------------- | :------: | --------------------------------- | +| `definition` | `string` | Yes | The task definition ID to trigger | +| `input` | `Record` | No | Input data to pass to the task | + +--- + +### abortTask + +Aborts a running background task. + +**Signature:** + +```typescript +sdk.tasks.abortTask(params: AbortTaskParams): Promise> +``` + +**Parameters:** + +| Parameter | Type | Required | Description | +| --------- | -------- | :------: | ------------------------------------- | +| `id` | `string` | Yes | The task run ID to abort | +| `message` | `string` | No | Optional reason for aborting the task | + +--- From 005f35e23a34d38eef85a52a160ccf1df34c22ee Mon Sep 17 00:00:00 2001 From: adrians5j Date: Thu, 23 Apr 2026 20:43:06 +0200 Subject: [PATCH 08/49] wip --- docs/messages/encryption.mdx | 56 ++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 docs/messages/encryption.mdx diff --git a/docs/messages/encryption.mdx b/docs/messages/encryption.mdx new file mode 100644 index 000000000..3ec75a510 --- /dev/null +++ b/docs/messages/encryption.mdx @@ -0,0 +1,56 @@ +--- +id: encryption +title: Deploying to a Production Environment Requires Encryption to be Configured +--- + +Looks like you're trying to deploy to a **production environment** without encryption configured. + +Encryption protects sensitive data stored in your Webiny project's database. Deploying to a production environment without it is a security risk, which is why Webiny blocks the deployment and shows this message. + +## How to Fix + +**1. Set the `WEBINY_ENCRYPTION_PASSPHRASE` environment variable** + +Add the following to the `.env` file at the root of your Webiny project (create one if it doesn't exist): + +```bash +WEBINY_ENCRYPTION_PASSPHRASE=your-secure-passphrase +``` + +Use a strong, randomly generated passphrase. You can generate one with the following command: + +```bash +openssl rand -base64 32 +``` + +:::danger Keep this value safe +Once set and deployed, changing the passphrase will make previously encrypted data unreadable. Store it securely (e.g. in a secrets manager) and never commit it to version control. +::: + +**2. Add `` to your project config** + +Open your `webiny.config.ts` file and add the `` component inside an `` block: + +```tsx +import { Infra } from "webiny/extensions"; + +export const Extensions = () => { + return ( + <> + {/* Encryption MUST always be configured for production environments. */} + + + + {/* ... rest of your config */} + + ); +}; +``` + +Once both steps are complete, re-run your deploy command: + +```bash +yarn webiny deploy --env prod +``` + +If you still encounter issues, feel free to reach out in our [community Slack](https://www.webiny.com/slack). From 53958e2d802951f2b0613653516d4232aa5af91f Mon Sep 17 00:00:00 2001 From: adrians5j Date: Fri, 24 Apr 2026 11:32:55 +0200 Subject: [PATCH 09/49] wip --- .../6.x/website-builder/how-it-works.ai.txt | 1 + .../6.x/website-builder/how-it-works.mdx | 10 ++++++++++ .../6.x/website-builder/multi-tenant-nextjs.ai.txt | 6 ++++++ .../6.x/website-builder/multi-tenant-nextjs.mdx | 10 ++++++++++ 4 files changed, 27 insertions(+) diff --git a/docs/developer-docs/6.x/website-builder/how-it-works.ai.txt b/docs/developer-docs/6.x/website-builder/how-it-works.ai.txt index 55dc5ecf8..f74599360 100644 --- a/docs/developer-docs/6.x/website-builder/how-it-works.ai.txt +++ b/docs/developer-docs/6.x/website-builder/how-it-works.ai.txt @@ -59,6 +59,7 @@ SDK Responsibilities: - Component Registration: utilities to register components for editor - Rendering Utilities: helpers to render page components - Thin layer, no architectural constraints +- NOT designed for shared runtime environments (containers, long-running Node.js servers) — works only when each HTTP request runs in an isolated invocation (Vercel, OpenNext/Lambda). Warning alert added before Benefits section. Key Benefits: 1. Genuine WYSIWYG - editors see real app with real styles diff --git a/docs/developer-docs/6.x/website-builder/how-it-works.mdx b/docs/developer-docs/6.x/website-builder/how-it-works.mdx index 54b5fd093..2cab23049 100644 --- a/docs/developer-docs/6.x/website-builder/how-it-works.mdx +++ b/docs/developer-docs/6.x/website-builder/how-it-works.mdx @@ -120,6 +120,16 @@ The Website Builder SDK provides: Webiny currently provides the `@webiny/website-builder-nextjs` SDK for Next.js. The SDK is a thin layer that connects your app to Webiny without imposing constraints on your architecture. + + +The Website Builder SDK is not designed for shared runtime environments — environments where a single process handles multiple concurrent HTTP requests, such as a Node.js server running in a container. + +When it comes to Next.js, the SDK works correctly with Vercel or OpenNext because both platforms use serverless functions or Lambda for SSR, where each HTTP request runs in an isolated function invocation. In a shared runtime, concurrent requests may overwrite each other's SDK state. + +If your deployment target is a container or any long-running server process, ensure your SSR runtime provides per-request isolation before using the SDK. + + + ## Benefits **Genuine WYSIWYG** - Editors see your actual app with real styles, not a simulation. diff --git a/docs/developer-docs/6.x/website-builder/multi-tenant-nextjs.ai.txt b/docs/developer-docs/6.x/website-builder/multi-tenant-nextjs.ai.txt index b82e77955..b6fe0c513 100644 --- a/docs/developer-docs/6.x/website-builder/multi-tenant-nextjs.ai.txt +++ b/docs/developer-docs/6.x/website-builder/multi-tenant-nextjs.ai.txt @@ -17,6 +17,12 @@ Key Documentation Decisions: 7. Did not document the webinyApi.js GraphQL utility from presales — too project-specific 8. ContentSdkInitializer is a client component wrapping initializeContentSdk; passes tenantId from server layout to browser side +Runtime Environment Limitation: +- SDK not designed for shared runtime environments (containers, long-running Node.js servers) +- Works on Vercel and OpenNext because those use serverless functions/Lambda for SSR (1 request = 1 isolated invocation) +- In a shared runtime, concurrent requests can overwrite each other's SDK state, including tenant context +- Warning alert added in Overview section before the Next.js implementation details + Understanding Multi-Tenant Architecture: - SDK apiTenant param scopes all API calls to that tenant - Middleware is the canonical place to resolve tenant identity (runs before all rendering) diff --git a/docs/developer-docs/6.x/website-builder/multi-tenant-nextjs.mdx b/docs/developer-docs/6.x/website-builder/multi-tenant-nextjs.mdx index f4a44168b..76cfc8817 100644 --- a/docs/developer-docs/6.x/website-builder/multi-tenant-nextjs.mdx +++ b/docs/developer-docs/6.x/website-builder/multi-tenant-nextjs.mdx @@ -22,6 +22,16 @@ By default, the Website Builder SDK connects to one Webiny tenant configured via The core idea is the same regardless of framework: resolve the tenant from the incoming request, then pass it to the SDK initializer so all API calls are scoped to that tenant. + + +The Website Builder SDK is not designed for use in shared runtime environments. If you run your frontend app in a container or any long-running server process where multiple HTTP requests share the same Node.js process, the SDK will not behave correctly — concurrent requests may overwrite each other's SDK state, including the resolved tenant context. + +When it comes to Next.js, the SDK works correctly on Vercel or with OpenNext because those platforms use serverless functions or Lambda for SSR, where each HTTP request runs in an isolated function invocation (one request, one invocation). + +If your deployment target is a shared runtime, use a platform that provides per-request execution isolation before adopting this multi-tenant pattern. + + + ## Next.js ### How Tenant Scoping Works From 7dfae63c971edc0697c91d44f1e0c39e3d2eff24 Mon Sep 17 00:00:00 2001 From: adrians5j Date: Fri, 24 Apr 2026 22:38:05 +0200 Subject: [PATCH 10/49] ci: add regen notes workflow [no ci] --- .../workflows/regenerate-release-notes.yml | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/regenerate-release-notes.yml diff --git a/.github/workflows/regenerate-release-notes.yml b/.github/workflows/regenerate-release-notes.yml new file mode 100644 index 000000000..efd5a9000 --- /dev/null +++ b/.github/workflows/regenerate-release-notes.yml @@ -0,0 +1,50 @@ +name: Regenerate Release Notes + +on: + workflow_dispatch: + inputs: + version: + description: "Webiny version (e.g. 6.3.0)" + required: true + type: string + +jobs: + regenerate: + name: Regenerate changelog and upgrade guide + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout release branch + uses: actions/checkout@v4 + with: + ref: release/${{ inputs.version }} + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Enable Corepack (Yarn Berry) + run: corepack enable + + - name: Install dependencies + run: yarn install --immutable + + - name: Regenerate changelog + env: + CLAUDE_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + run: yarn generate:changelog --version ${{ inputs.version }} + + - name: Regenerate upgrade guide + run: yarn generate:upgrade-guide --version ${{ inputs.version }} + + - name: Commit and push + run: | + git config user.name "webiny-bot" + git config user.email "webiny-bot@webiny.com" + git add docs/release-notes/${{ inputs.version }}/ + git diff --cached --quiet || git commit -m "chore: regenerate release notes for ${{ inputs.version }}" + git push origin release/${{ inputs.version }} From fb80e66229617370ab8f704a6bec3c6505833055 Mon Sep 17 00:00:00 2001 From: adrians5j Date: Mon, 27 Apr 2026 13:20:03 +0200 Subject: [PATCH 11/49] wip: api routes --- AGENTS.md | 2 +- docs/developer-docs/6.x/navigation.tsx | 1 + .../6.x/webiny-api/api-routes.ai.txt | 49 ++++++ .../6.x/webiny-api/api-routes.mdx | 155 ++++++++++++++++++ 4 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 docs/developer-docs/6.x/webiny-api/api-routes.ai.txt create mode 100644 docs/developer-docs/6.x/webiny-api/api-routes.mdx diff --git a/AGENTS.md b/AGENTS.md index 9bad132dd..399e8b6c5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,7 @@ ``` docs/developer-docs/6.0.x/ ├── admin/ # Admin area customization (whitelabeling) -├── webiny-api/ # API customization (custom domain, extend GraphQL schema, universal API keys) +├── webiny-api/ # API customization (custom domain, extend GraphQL schema, custom routes, universal API keys) ├── cli/ # CLI commands reference (deploy, destroy, watch, etc.) ├── core-concepts/ # Foundational knowledge: architecture, applications, project structure, DI, Result pattern, multi-tenancy, local dev, env vars ├── get-started/ # Welcome, installation, upgrade to Business diff --git a/docs/developer-docs/6.x/navigation.tsx b/docs/developer-docs/6.x/navigation.tsx index 8afe85780..dec0c5060 100644 --- a/docs/developer-docs/6.x/navigation.tsx +++ b/docs/developer-docs/6.x/navigation.tsx @@ -55,6 +55,7 @@ export const Navigation = ({ children }: { children: React.ReactNode }) => { > + diff --git a/docs/developer-docs/6.x/webiny-api/api-routes.ai.txt b/docs/developer-docs/6.x/webiny-api/api-routes.ai.txt new file mode 100644 index 000000000..59fc6ce1e --- /dev/null +++ b/docs/developer-docs/6.x/webiny-api/api-routes.ai.txt @@ -0,0 +1,49 @@ +AI Context: Add Custom API Routes (api-routes.mdx) + +Source of Information: +1. docs/release-notes/6.2.0/changelog.mdx — introduced in 6.2.0, #5066 +2. github.com/webiny/webiny-js PR #5066 — full implementation details, SKILL.md, ApiRoute.tsx +3. docs/developer-docs/6.x/webiny-api/extend-graphql-schema.mdx — structural reference for sibling article + +Key Documentation Decisions: +1. Leads with a brief overview explaining the infra-level implication (redeployment required) upfront — this is the most important thing to convey +2. Covers path parameters and Route.Request/Route.Reply tables together since they are closely related +3. Props reference is a table under Registering the Route (not a separate top-level section) — keeps the page linear and practical +4. Avoided documenting BuildParams usage (available but not core to understanding routes) — keeps the article focused +5. Does not call out export default — it's universal across all extensions, not a special rule here + +Understanding: +Api.Route is a build-time extension in @webiny/project-aws (ApiRoute.tsx). At build time it: +- Injects a createContextPlugin into apps/api/graphql/src/extensions.ts (registers the handler class in DI) +- Injects a createRoute call (registers the Fastify route with hardcoded path/method from props) +At deploy time (Pulumi): +- Runs RegisterRoutesPulumi which calls graphql.addRoute({ name, path, method }) on the ApiGraphql Pulumi module +- This creates the actual API Gateway route + +Route.Interface: execute(request: Route.Request, reply: Route.Reply): Promise +Route.createImplementation({ implementation, dependencies }) — same pattern as all other Webiny abstractions +Supported methods: DELETE, GET, HEAD, PATCH, POST, PUT, OPTIONS, ANY +Path parameters use {paramName} syntax (API Gateway/Fastify convention) +Route.Request: body, headers, method, url, params, query +Route.Reply: send(data?), code(statusCode), header(key, value) — chainable + +Key Rules from PR: +- src must include .ts extension +- Changing path or method requires yarn webiny deploy api (watch mode won't reprovision infra) +- routeName prop is optional — auto-derived from path+method if omitted + +Related Documents: +- docs/developer-docs/6.x/webiny-api/extend-graphql-schema.mdx — alternative: add a GraphQL endpoint instead +- docs/developer-docs/6.x/core-concepts/di.mdx — DI pattern +- docs/developer-docs/6.x/cli/deploy.mdx — deploy command reference + +Key Code Locations: +- packages/project-aws/src/extensions/ApiRoute.tsx — extension implementation +- packages/project-aws/src/extensions/RegisterRoutesPulumi.ts — Pulumi route registration +- packages/handler/src/abstractions/Route.ts — Route.Interface, Route.Request, Route.Reply types +- extensions/MyApiRoute.ts — example handler file in project root + +Tone Guidelines: +- Practical and direct — lead with working code +- Redeploy requirement is a warning, not an afterthought — mention in overview and again as an Alert before the deploy section +- Same depth and style as extend-graphql-schema.mdx diff --git a/docs/developer-docs/6.x/webiny-api/api-routes.mdx b/docs/developer-docs/6.x/webiny-api/api-routes.mdx new file mode 100644 index 000000000..242ebaadc --- /dev/null +++ b/docs/developer-docs/6.x/webiny-api/api-routes.mdx @@ -0,0 +1,155 @@ +--- +id: m4r7vx2p +title: Add Custom API Routes +description: Learn how to register custom HTTP routes on the Webiny API using the Api.Route extension. +--- + +import { Alert } from "@/components/Alert"; + + + +- how to create a custom HTTP route handler with dependency injection +- how to register the route in `webiny.config.tsx` +- why a redeployment is required when adding routes + + + +## Overview + +The `Api.Route` extension lets you register custom HTTP routes on the Webiny API Gateway and GraphQL Lambda. Route handlers are plain TypeScript classes with full dependency injection support — the same DI pattern used throughout the rest of the API. + +Unlike pure application code changes, adding a route modifies both the runtime routing layer (Fastify) and the cloud infrastructure (API Gateway). A redeployment with `yarn webiny deploy api` is required whenever you add, change, or remove a route. + +## Creating a Route Handler + +Create a TypeScript file in your `extensions/` directory and implement `Route.Interface`: + +```typescript extensions/MyApiRoute.ts +import { Logger, Route } from "webiny/api"; + +class MyApiRouteImpl implements Route.Interface { + public constructor(private logger: Logger.Interface) {} + + public async execute(request: Route.Request, reply: Route.Reply): Promise { + this.logger.info("Handling request", { url: request.url }); + return reply.send({ message: "Hello from my custom route!" }); + } +} + +export default Route.createImplementation({ + implementation: MyApiRouteImpl, + dependencies: [Logger] +}); +``` + +## Registering the Route + +Add `Api.Route` to `webiny.config.tsx` with the `method`, `path`, and `src` props: + +```tsx webiny.config.tsx +import React from "react"; +import { Api } from "webiny/extensions"; + +export const Extensions = () => { + return ( + <> + {/* ... other extensions */} + + + ); +}; +``` + +The `src` path must include the `.ts` extension — omitting it will cause a build failure. + +### Props Reference + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `path` | `string` | Yes | Route path, must start with `/`. Use `{paramName}` for path parameters. | +| `method` | `string` | Yes | HTTP method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`, or `ANY` | +| `src` | `string` | Yes | Path to the handler file, including the `.ts` extension | +| `routeName` | `string` | No | Pulumi resource name (kebab-case). Auto-derived from path and method if omitted. | + +Use `ANY` as the method to match all HTTP methods on a given path. + +## Deploying the Route + + + +Adding, changing, or removing a route requires a full API redeployment. Route registration modifies API Gateway configuration — it is not a pure application code change. + + + +After registering a route in `webiny.config.tsx`, deploy the API: + +``` +yarn webiny deploy api +``` + +During development, use watch mode for automatic redeployment on code changes: + +``` +yarn webiny watch api +``` + +Watch mode does not re-provision infrastructure. If you change the `path` or `method` in `webiny.config.tsx`, run `yarn webiny deploy api` explicitly. + +## Working with Path Parameters and Request Data + +Use `{paramName}` in the path to capture path parameters, then access them via `request.params`: + +```typescript extensions/GetOrderRoute.ts +import { Logger, Route } from "webiny/api"; + +interface OrderParams { + orderId: string; +} + +class GetOrderRouteImpl implements Route.Interface { + public constructor(private logger: Logger.Interface) {} + + public async execute(request: Route.Request, reply: Route.Reply): Promise { + const { orderId } = request.params as OrderParams; + const { limit } = request.query as { limit?: string }; + + this.logger.info("Fetching order", { orderId }); + + return reply.code(200).send({ orderId, limit: limit ?? "10" }); + } +} + +export default Route.createImplementation({ + implementation: GetOrderRouteImpl, + dependencies: [Logger] +}); +``` + +```tsx webiny.config.tsx + +``` + +### Route.Request + +| Property | Type | Description | +| --- | --- | --- | +| `body` | `unknown` | Parsed request body | +| `headers` | `Record` | Request headers | +| `method` | `string` | HTTP method | +| `url` | `string` | Full request URL | +| `params` | `unknown` | Path parameters (e.g. `{ orderId: "abc" }`) | +| `query` | `unknown` | Query string parameters | + +### Route.Reply + +| Method | Description | +| --- | --- | +| `reply.send(data?)` | Send the response body | +| `reply.code(statusCode)` | Set the HTTP status code (chainable) | +| `reply.header(key, value)` | Set a response header (chainable) | + +Methods are chainable: + +```typescript +return reply.code(201).header("X-Request-Id", "abc123").send({ created: true }); +``` From 6bc8e3af1b5f11d402fe77b463eb68866a83f31c Mon Sep 17 00:00:00 2001 From: adrians5j Date: Mon, 27 Apr 2026 13:39:38 +0200 Subject: [PATCH 12/49] wip: extensions reference --- AGENTS.md | 2 +- docs/developer-docs/6.x/navigation.tsx | 9 +- .../6.x/reference/extensions/admin.ai.txt | 20 ++ .../6.x/reference/extensions/admin.mdx | 39 +++ .../6.x/reference/extensions/api.ai.txt | 25 ++ .../6.x/reference/extensions/api.mdx | 58 ++++ .../6.x/reference/extensions/cli.ai.txt | 17 + .../6.x/reference/extensions/cli.mdx | 27 ++ .../6.x/reference/extensions/infra.ai.txt | 45 +++ .../6.x/reference/extensions/infra.mdx | 316 ++++++++++++++++++ .../6.x/reference/extensions/project.ai.txt | 24 ++ .../6.x/reference/extensions/project.mdx | 86 +++++ 12 files changed, 666 insertions(+), 2 deletions(-) create mode 100644 docs/developer-docs/6.x/reference/extensions/admin.ai.txt create mode 100644 docs/developer-docs/6.x/reference/extensions/admin.mdx create mode 100644 docs/developer-docs/6.x/reference/extensions/api.ai.txt create mode 100644 docs/developer-docs/6.x/reference/extensions/api.mdx create mode 100644 docs/developer-docs/6.x/reference/extensions/cli.ai.txt create mode 100644 docs/developer-docs/6.x/reference/extensions/cli.mdx create mode 100644 docs/developer-docs/6.x/reference/extensions/infra.ai.txt create mode 100644 docs/developer-docs/6.x/reference/extensions/infra.mdx create mode 100644 docs/developer-docs/6.x/reference/extensions/project.ai.txt create mode 100644 docs/developer-docs/6.x/reference/extensions/project.mdx diff --git a/AGENTS.md b/AGENTS.md index 399e8b6c5..bd617861f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,7 +47,7 @@ docs/developer-docs/6.0.x/ ├── infrastructure/ # Cloud infrastructure (database setups, deployment modes, diagrams) ├── overview/ # Pricing, security features │ └── features/ -├── reference/ # Auto-generated API reference (extensions, admin, API) +├── reference/ # API reference (webiny.config.tsx extensions, SDK docs) ├── tasks/ # Background task system (about, reference, management) ├── security/ # Security (universal API keys, programmatic roles and teams) ├── tenant-manager/ # Multi-tenancy management (GraphQL API, model extension) diff --git a/docs/developer-docs/6.x/navigation.tsx b/docs/developer-docs/6.x/navigation.tsx index dec0c5060..28c1b1660 100644 --- a/docs/developer-docs/6.x/navigation.tsx +++ b/docs/developer-docs/6.x/navigation.tsx @@ -200,8 +200,15 @@ export const Navigation = ({ children }: { children: React.ReactNode }) => { + + + + + + + {/* __REFERENCE_PAGES_START__ */} {/* __SDK_PAGES_START__ */} diff --git a/docs/developer-docs/6.x/reference/extensions/admin.ai.txt b/docs/developer-docs/6.x/reference/extensions/admin.ai.txt new file mode 100644 index 000000000..15be56077 --- /dev/null +++ b/docs/developer-docs/6.x/reference/extensions/admin.ai.txt @@ -0,0 +1,20 @@ +AI Context: Admin Extensions Reference (admin.mdx) + +Source of Information: +1. ~/dev/wby-next/packages/project-aws/src/admin.ts — Admin namespace shape +2. ~/dev/wby-next/packages/project/src/extensions/AdminExtension.ts — Admin.Extension props +3. ~/dev/wby-next/packages/project/src/extensions/AdminBuildParam.ts — Admin.BuildParam props + +Key Documentation Decisions: +1. Mirrors api.mdx structure exactly — same two extensions, same prop shapes + +Understanding: +- Admin.Extension: loads any React component or admin config file as an Admin app extension +- Admin.BuildParam: values embedded in the webpack/Vite bundle at build time, accessible at runtime via BuildParams + +Related Documents: +- docs/developer-docs/6.x/core-concepts/extensions.mdx — conceptual overview +- docs/developer-docs/6.x/reference/extensions/api.mdx — parallel Api namespace reference + +Tone Guidelines: +- Reference: minimal prose, tables are the content diff --git a/docs/developer-docs/6.x/reference/extensions/admin.mdx b/docs/developer-docs/6.x/reference/extensions/admin.mdx new file mode 100644 index 000000000..09b70cab5 --- /dev/null +++ b/docs/developer-docs/6.x/reference/extensions/admin.mdx @@ -0,0 +1,39 @@ +--- +id: p5k7mz9w +title: Admin Extensions Reference +description: Reference for all Admin.* extensions available in webiny.config.tsx. +--- + +import { Alert } from "@/components/Alert"; + + + +- every `Admin.*` extension and the props it accepts + + + +## Overview + +`Admin.*` extensions customize the Admin application — loading custom UI code and passing build-time parameters into the bundle. + +### Admin.Extension + +Registers a custom extension in the Admin application (UI customizations, custom pages, branding, etc.). + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `src` | `string` | Yes | Path to the extension file | +| `exportName` | `string` | No | Export name to use. Defaults to the filename without extension. | + +Can be used multiple times. + +### Admin.BuildParam + +Passes a build-time parameter into the Admin app bundle, accessible at runtime. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `paramName` | `string` | Yes | Parameter name | +| `value` | `string \| number \| boolean \| object \| array` | Yes | Parameter value | + +Can be used multiple times. diff --git a/docs/developer-docs/6.x/reference/extensions/api.ai.txt b/docs/developer-docs/6.x/reference/extensions/api.ai.txt new file mode 100644 index 000000000..269f33a54 --- /dev/null +++ b/docs/developer-docs/6.x/reference/extensions/api.ai.txt @@ -0,0 +1,25 @@ +AI Context: Api Extensions Reference (api.mdx) + +Source of Information: +1. ~/dev/wby-next/packages/project-aws/src/api.ts — Api namespace shape +2. ~/dev/wby-next/packages/project-aws/src/extensions/ApiRoute.tsx — Api.Route props +3. ~/dev/wby-next/packages/project/src/extensions/ApiExtension.ts — Api.Extension props +4. ~/dev/wby-next/packages/project/src/extensions/ApiBuildParam.ts — Api.BuildParam props +5. github.com/webiny/webiny-js PR #5066 — Api.Route full implementation details + +Key Documentation Decisions: +1. Cross-references the full Api.Route guide (webiny-api/api-routes.mdx) rather than duplicating content +2. Includes the redeployment warning on Api.Route — it's the most important sharp edge for that extension + +Understanding: +- Api.Extension: loads any file that exports a default extension implementation (GraphQLSchemaFactory, event handlers, use cases, etc.) +- Api.Route: registers path+method on API Gateway + Fastify; requires deploy (not just watch) on path/method changes +- Api.BuildParam: values embedded at bundle time, accessed via BuildParams DI abstraction at runtime (NOT process.env) +- exportName prop is rarely needed — only when the file has non-standard exports + +Related Documents: +- docs/developer-docs/6.x/webiny-api/api-routes.mdx — full Api.Route guide +- docs/developer-docs/6.x/core-concepts/extensions.mdx — conceptual overview + +Tone Guidelines: +- Reference: minimal prose, tables are the content diff --git a/docs/developer-docs/6.x/reference/extensions/api.mdx b/docs/developer-docs/6.x/reference/extensions/api.mdx new file mode 100644 index 000000000..c6fb1594c --- /dev/null +++ b/docs/developer-docs/6.x/reference/extensions/api.mdx @@ -0,0 +1,58 @@ +--- +id: t2n8xr4v +title: Api Extensions Reference +description: Reference for all Api.* extensions available in webiny.config.tsx. +--- + +import { Alert } from "@/components/Alert"; + + + +- every `Api.*` extension and the props it accepts + + + +## Overview + +`Api.*` extensions customize the API Lambda — loading custom code, registering HTTP routes, and passing build-time parameters into the bundle. + +### Api.Extension + +Registers a custom extension on the API Lambda (GraphQL schema factories, event handlers, use cases, etc.). + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `src` | `string` | Yes | Path to the extension file | +| `exportName` | `string` | No | Export name to use. Defaults to the filename without extension. | + +Can be used multiple times. + +### Api.Route + +Registers a custom HTTP route on the API Gateway and GraphQL Lambda. See [Add Custom API Routes](/{version}/webiny-api/api-routes) for a full guide. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `path` | `string` | Yes | Route path, must start with `/`. Use `{paramName}` for path parameters. | +| `method` | `string` | Yes | HTTP method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`, or `ANY` | +| `src` | `string` | Yes | Path to the route handler file, including the `.ts` extension | +| `routeName` | `string` | No | Pulumi resource name (kebab-case). Auto-derived from `path` and `method` if omitted. | + +Can be used multiple times. + + + +Adding or removing a route requires `yarn webiny deploy api` — route registration modifies API Gateway configuration. + + + +### Api.BuildParam + +Passes a build-time parameter into the API Lambda bundle, accessible via `BuildParams` at runtime. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `paramName` | `string` | Yes | Parameter name | +| `value` | `string \| number \| boolean \| object \| array` | Yes | Parameter value | + +Can be used multiple times. diff --git a/docs/developer-docs/6.x/reference/extensions/cli.ai.txt b/docs/developer-docs/6.x/reference/extensions/cli.ai.txt new file mode 100644 index 000000000..222ad9277 --- /dev/null +++ b/docs/developer-docs/6.x/reference/extensions/cli.ai.txt @@ -0,0 +1,17 @@ +AI Context: Cli Extensions Reference (cli.mdx) + +Source of Information: +1. ~/dev/wby-next/packages/project-aws/src/cli.ts — Cli namespace shape +2. ~/dev/wby-next/packages/cli-core/src/extensions/CliCommand.ts — Cli.Command props + +Key Documentation Decisions: +1. Short page — only one extension in this namespace + +Understanding: +- Cli.Command: registers a custom CLI command; the implementation file must export a CliCommandFactory abstraction + +Related Documents: +- docs/developer-docs/6.x/core-concepts/extensions.mdx — conceptual overview + +Tone Guidelines: +- Reference: minimal prose diff --git a/docs/developer-docs/6.x/reference/extensions/cli.mdx b/docs/developer-docs/6.x/reference/extensions/cli.mdx new file mode 100644 index 000000000..40cfeb98a --- /dev/null +++ b/docs/developer-docs/6.x/reference/extensions/cli.mdx @@ -0,0 +1,27 @@ +--- +id: d9f1cs8e +title: Cli Extensions Reference +description: Reference for all Cli.* extensions available in webiny.config.tsx. +--- + +import { Alert } from "@/components/Alert"; + + + +- every `Cli.*` extension and the props it accepts + + + +## Overview + +`Cli.*` extensions add custom commands to the Webiny CLI. + +### Cli.Command + +Registers a custom command in the Webiny CLI, implemented as a `CliCommandFactory`. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `src` | `string` | Yes | Path to the implementation file (must follow `CliCommandFactory` abstraction) | + +Can be used multiple times. diff --git a/docs/developer-docs/6.x/reference/extensions/infra.ai.txt b/docs/developer-docs/6.x/reference/extensions/infra.ai.txt new file mode 100644 index 000000000..922af0845 --- /dev/null +++ b/docs/developer-docs/6.x/reference/extensions/infra.ai.txt @@ -0,0 +1,45 @@ +AI Context: Infra Extensions Reference (infra.mdx) + +Source of Information: +1. ~/dev/wby-next/packages/project-aws/src/infra.ts — full Infra namespace shape +2. ~/dev/wby-next/packages/project-aws/src/extensions/Encryption.tsx — Infra.Encryption props +3. ~/dev/wby-next/packages/project-aws/src/extensions/OpenSearch.ts — Infra.OpenSearch props +4. ~/dev/wby-next/packages/project-aws/src/extensions/AwsDefaultRegion.tsx — Infra.Aws.DefaultRegion props +5. ~/dev/wby-next/packages/project-aws/src/extensions/ApiLambdaFunction.tsx — Infra.Api.LambdaFunction props +6. ~/dev/wby-next/packages/project-aws/src/pulumi/extensions/Vpc.ts — Infra.Vpc props +7. ~/dev/wby-next/packages/project-aws/src/pulumi/extensions/BlueGreenDeployments.ts — Infra.BlueGreenDeployments props +8. ~/dev/wby-next/packages/project-aws/src/pulumi/extensions/AwsTags.ts — Infra.Aws.Tags props +9. ~/dev/wby-next/packages/project-aws/src/pulumi/extensions/AdminCustomDomains.ts — Infra.Admin.CustomDomains props +10. ~/dev/wby-next/packages/project-aws/src/pulumi/extensions/ApiCustomDomains.ts — Infra.Api.CustomDomains props +11. ~/dev/wby-next/packages/project/src/extensions/infra/Env.tsx — Infra.Env.Is/IsNot/IsProd/IsNotProd props +12. ~/dev/wby-next/packages/project/src/extensions/infra/Ci.tsx — Infra.Ci.Is/IsNot props +13. ~/dev/wby-next/packages/project/src/extensions/hooks/ — all lifecycle hooks +14. ~/dev/wby-next/packages/project/src/extensions/pulumi/ — Pulumi hooks and StackOutputValue +15. ~/dev/wby-next/packages/project/src/extensions/EnvVar.ts — Infra.EnvVar props +16. ~/dev/wby-next/packages/project/src/extensions/pulumi/PulumiResourceNamePrefix.ts — props +17. ~/dev/wby-next/packages/project/src/extensions/pulumi/ProductionEnvironments.ts — props + +Key Documentation Decisions: +1. Organized by sub-namespace: top-level, Infra.Aws, Infra.Env, Infra.Ci, Infra.Admin, Infra.Api, Infra.Core +2. Lifecycle hooks grouped into a summary table per namespace (all share `src: string`) to avoid repetition +3. Vpc and BlueGreenDeployments use TypeScript shapes for deeply nested props +4. CustomDomains cross-references the dedicated connect-custom-domain guides +5. Infra.Env.Is/IsNot multiple props are AND-ed (not OR-ed) — documented inline + +Understanding: +- All lifecycle hooks (BeforeBuild, AfterBuild, BeforeDeploy, AfterDeploy, BeforeWatch, Pulumi) accept only src: string +- Pulumi hooks inject Pulumi infrastructure-as-code; BeforeBuild/AfterBuild hooks inject application-level scripts +- StackOutputValue adds entries to the Pulumi stack's output JSON (visible after deploy via webiny output) +- Infra.Aws.Tags merges when used multiple times +- Infra.Env.Is/IsNot: env, variant, region props are AND-ed; each can be string or string[] (OR within the array) +- Infra.Env.IsProd/IsNotProd delegates to ProductionEnvironments list +- ACM cert for CustomDomains must be in us-east-1 (CloudFront requirement) + +Related Documents: +- docs/developer-docs/6.x/webiny-api/connect-custom-domain.mdx — Infra.Api.CustomDomains full guide +- docs/developer-docs/6.x/admin/connect-custom-domain.mdx — Infra.Admin.CustomDomains full guide +- docs/developer-docs/6.x/infrastructure/extensions/ — existing infrastructure extension guides + +Tone Guidelines: +- Reference: minimal prose, tables and TypeScript shapes are the content +- Lifecycle hooks compacted — don't repeat the same table 18 times diff --git a/docs/developer-docs/6.x/reference/extensions/infra.mdx b/docs/developer-docs/6.x/reference/extensions/infra.mdx new file mode 100644 index 000000000..4ff8b5123 --- /dev/null +++ b/docs/developer-docs/6.x/reference/extensions/infra.mdx @@ -0,0 +1,316 @@ +--- +id: h3q6yb2j +title: Infra Extensions Reference +description: Reference for all Infra.* extensions available in webiny.config.tsx. +--- + +import { Alert } from "@/components/Alert"; + + + +- every `Infra.*` extension and the props it accepts + + + +## Overview + +`Infra.*` extensions configure cloud infrastructure: AWS settings, encryption, networking, conditional rendering, and lifecycle hooks for each application (Admin, Api, Core). + +--- + +## Infra.Encryption + +Configures AES-GCM encryption for the API's `EncryptionService`, used for encrypting sensitive data stored in the database. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `passphrase` | `string` | Yes | Passphrase used to derive the AES key via scrypt | +| `salt` | `string` | No | scrypt salt. Ensures different keys for the same passphrase across environments. | +| `algorithm` | `"aes-128-gcm" \| "aes-192-gcm" \| "aes-256-gcm"` | No | AES-GCM variant. Defaults to `aes-256-gcm`. | + +## Infra.OpenSearch + +Enables and configures Amazon OpenSearch for the project. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `enabled` | `boolean` | No | Whether to enable OpenSearch. Defaults to `false`. | +| `domainName` | `string` | No | Existing OpenSearch domain name. When set, Webiny uses that domain instead of deploying a new one. | +| `indexPrefix` | `string` | No | Prefix added to all OpenSearch index names. | +| `sharedIndexes` | `boolean` | No | When `true`, a single set of indexes is shared across all environments. Defaults to `false`. | +| `endpoint` | `string` | No | Endpoint of an existing OpenSearch cluster (useful when using a custom domain). | +| `username` | `string` | No | Username for OpenSearch authentication. | +| `password` | `string` | No | Password for OpenSearch authentication. | + +## Infra.Vpc + +Deploys Webiny Lambda functions and OpenSearch into a VPC, or connects them to an existing one. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `enabled` | `boolean` | Yes | Whether to enable VPC integration. | +| `useVpcEndpoints` | `boolean` | No | Whether to create VPC endpoints for AWS services. | +| `useExistingVpc` | `object` | No | Connect to an existing VPC instead of creating one. See shape below. | + +**`useExistingVpc` shape:** + +```typescript +{ + lambdaFunctionsVpcConfig: { + securityGroupIds: string[]; + subnetIds: string[]; + }; + openSearchDomainVpcConfig?: { + securityGroupIds: string[]; + subnetIds: string[]; + }; +} +``` + +## Infra.BlueGreenDeployments + +Enables blue/green deployment support, routing traffic between two parallel environments via weighted CloudFront distributions. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `enabled` | `boolean` | Yes | Whether to enable blue/green deployments. | +| `domains` | `object` | Yes | Custom domain and certificate configuration. See shape below. | +| `deployments` | `[deployment, deployment]` | Yes | Exactly two deployment descriptors. See shape below. | + +**`domains` shape:** + +```typescript +{ + acmCertificateArn: string; + sslSupportMethod: "sni-only" | "vip"; + domains: { + api: string[]; // min 1 entry + admin: string[]; // min 1 entry + }; +} +``` + +**Each `deployment` object:** + +```typescript +{ + name: string; + env: string; + variant: string; +} +``` + +## Infra.PulumiResourceNamePrefix + +Overrides the default `wby-` prefix applied to all Pulumi resource names. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `prefix` | `string` | Yes | Custom prefix for all Pulumi-managed resource names | + +## Infra.ProductionEnvironments + +Declares which environment names are treated as production. Affects `Infra.Env.IsProd` and `Infra.Env.IsNotProd`. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `environments` | `string[]` | Yes | Environment names to treat as production (letters, numbers, dashes, and underscores only) | + +## Infra.EnvVar + +Sets an environment variable available during builds and deployments. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `varName` | `string` | Yes | Environment variable name | +| `value` | `string` | Yes | Environment variable value | + +Can be used multiple times. + +--- + +## Infra.Aws + +### Infra.Aws.DefaultRegion + +Sets the default AWS region for all deployments. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | `string` | Yes | AWS region name (e.g. `"us-east-1"`) | + +### Infra.Aws.Tags + +Applies AWS resource tags to all Pulumi-managed resources. Can be used multiple times — tags from multiple instances are merged. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `tags` | `Record` | Yes | Key-value map of AWS tags to apply | + +--- + +## Infra.Env + +Conditionally renders child extensions based on the current environment, variant, or AWS region. + +### Infra.Env.Is / Infra.Env.IsNot + +`Infra.Env.Is` renders children when conditions match; `Infra.Env.IsNot` renders when they do not. Multiple props are AND-ed. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `env` | `string \| string[]` | No | Environment name(s) to match | +| `variant` | `string \| string[]` | No | Variant name(s) to match | +| `region` | `string \| string[]` | No | AWS region(s) to match | +| `children` | `ReactNode` | Yes | Extensions to render when the condition is met | + +```tsx webiny.config.tsx + + + +``` + +### Infra.Env.IsProd / Infra.Env.IsNotProd + +Shorthand for `Infra.Env.Is` / `Infra.Env.IsNot` matched against the environments declared in `Infra.ProductionEnvironments`. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `children` | `ReactNode` | Yes | Extensions to render | + +--- + +## Infra.Ci + +Conditionally renders child extensions based on whether the process is running in a CI environment. + +### Infra.Ci.Is / Infra.Ci.IsNot + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `children` | `ReactNode` | Yes | Extensions to render | + +--- + +## Infra.Admin + +Extensions that target the **Admin** application's build, deployment, and cloud infrastructure. + +### Lifecycle hooks + +All six extensions accept a single `src` prop pointing to an implementation file. All can be used multiple times. + +| Extension | Triggered | +| --- | --- | +| `Infra.Admin.BeforeBuild` | Before the Admin app is built | +| `Infra.Admin.AfterBuild` | After the Admin app is built | +| `Infra.Admin.BeforeDeploy` | Before the Admin app is deployed | +| `Infra.Admin.AfterDeploy` | After the Admin app is deployed | +| `Infra.Admin.BeforeWatch` | Before `webiny watch` starts for Admin | +| `Infra.Admin.Pulumi` | During Pulumi deployment — modify Admin cloud infrastructure | + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `src` | `string` | Yes | Path to the implementation file | + +### Infra.Admin.CustomDomains + +Connects a custom domain to the Admin CloudFront distribution. See [Connect Custom Domain](/{version}/admin/connect-custom-domain) for a full guide. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `domains` | `string[]` | Yes | One or more custom domain names | +| `certificateArn` | `string` | Yes | ARN of the ACM certificate (must be in `us-east-1`) | +| `sslMethod` | `"sni-only" \| "vip"` | No | SSL/TLS method. Defaults to `"sni-only"`. | + +### Infra.Admin.StackOutputValue + +Adds a custom key-value entry to the Admin Pulumi stack outputs. Can be used multiple times. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `key` | `string` | Yes | Output key name | +| `value` | `any` | Yes | Output value | + +--- + +## Infra.Api + +Extensions that target the **API** application's build, deployment, and cloud infrastructure. + +### Lifecycle hooks + +All six extensions accept a single `src` prop pointing to an implementation file. All can be used multiple times. + +| Extension | Triggered | +| --- | --- | +| `Infra.Api.BeforeBuild` | Before the API app is built | +| `Infra.Api.AfterBuild` | After the API app is built | +| `Infra.Api.BeforeDeploy` | Before the API app is deployed | +| `Infra.Api.AfterDeploy` | After the API app is deployed | +| `Infra.Api.BeforeWatch` | Before `webiny watch` starts for API | +| `Infra.Api.Pulumi` | During Pulumi deployment — modify API cloud infrastructure | + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `src` | `string` | Yes | Path to the implementation file | + +### Infra.Api.CustomDomains + +Connects a custom domain to the API CloudFront distribution. See [Connect Custom Domain](/{version}/webiny-api/connect-custom-domain) for a full guide. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `domains` | `string[]` | Yes | One or more custom domain names | +| `certificateArn` | `string` | Yes | ARN of the ACM certificate (must be in `us-east-1`) | +| `sslMethod` | `"sni-only" \| "vip"` | No | SSL/TLS method. Defaults to `"sni-only"`. | + +### Infra.Api.LambdaFunction + +Adds a fully custom Lambda function to the API application, with both an application handler and Pulumi infrastructure code. Can be used multiple times. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `functionSrc` | `string` | Yes | Path to the Lambda handler source file | +| `pulumiSrc` | `string` | Yes | Path to the Pulumi infrastructure file (must follow `ApiPulumi` abstraction) | + +### Infra.Api.StackOutputValue + +Adds a custom key-value entry to the API Pulumi stack outputs. Can be used multiple times. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `key` | `string` | Yes | Output key name | +| `value` | `any` | Yes | Output value | + +--- + +## Infra.Core + +Extensions that target the **Core** application — the shared infrastructure layer (DynamoDB tables, Cognito user pool, S3 buckets). + +### Lifecycle hooks + +All six extensions accept a single `src` prop pointing to an implementation file. All can be used multiple times. + +| Extension | Triggered | +| --- | --- | +| `Infra.Core.BeforeBuild` | Before the Core app is built | +| `Infra.Core.AfterBuild` | After the Core app is built | +| `Infra.Core.BeforeDeploy` | Before the Core app is deployed | +| `Infra.Core.AfterDeploy` | After the Core app is deployed | +| `Infra.Core.BeforeWatch` | Before `webiny watch` starts for Core | +| `Infra.Core.Pulumi` | During Pulumi deployment — modify Core cloud infrastructure | + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `src` | `string` | Yes | Path to the implementation file | + +### Infra.Core.StackOutputValue + +Adds a custom key-value entry to the Core Pulumi stack outputs. Can be used multiple times. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `key` | `string` | Yes | Output key name | +| `value` | `any` | Yes | Output value | diff --git a/docs/developer-docs/6.x/reference/extensions/project.ai.txt b/docs/developer-docs/6.x/reference/extensions/project.ai.txt new file mode 100644 index 000000000..84895c0b1 --- /dev/null +++ b/docs/developer-docs/6.x/reference/extensions/project.ai.txt @@ -0,0 +1,24 @@ +AI Context: Project Extensions Reference (project.mdx) + +Source of Information: +1. ~/dev/wby-next/packages/project-aws/src/project.ts — Project namespace shape +2. ~/dev/wby-next/packages/project/src/extensions/ProjectId.ts — Project.Id props +3. ~/dev/wby-next/packages/project/src/extensions/Telemetry.ts — Project.Telemetry props +4. ~/dev/wby-next/packages/project-aws/src/extensions/ProjectAws/AutoInstall.ts — Project.AutoInstall props +5. ~/dev/wby-next/packages/project/src/extensions/FeatureFlags.tsx — Project.FeatureFlags props + +Key Documentation Decisions: +1. Project.AutoInstall includes a warning not to hardcode credentials — reference Infra.EnvVar instead +2. Project.FeatureFlags uses TypeScript shapes for nested objects (advancedAccessControlLayer, aiPowerups.options) rather than flat table rows +3. Project.DatabaseSetup is NOT included — it is not exported from packages/project-aws/src/project.ts + +Understanding: +- Project.Id: alphanumeric/dash/underscore/forward-slash only +- Project.AutoInstall: adminUser.password minimum 8 characters; adminUser.email must be valid email format +- Project.FeatureFlags: features correspond to WCP licensed capabilities; passing true/false per feature enables/disables them + +Related Documents: +- docs/developer-docs/6.x/reference/extensions/infra.mdx — Infra.EnvVar for safe credential passing + +Tone Guidelines: +- Reference: minimal prose, tables are the content diff --git a/docs/developer-docs/6.x/reference/extensions/project.mdx b/docs/developer-docs/6.x/reference/extensions/project.mdx new file mode 100644 index 000000000..3931f93db --- /dev/null +++ b/docs/developer-docs/6.x/reference/extensions/project.mdx @@ -0,0 +1,86 @@ +--- +id: g4w7nk5r +title: Project Extensions Reference +description: Reference for all Project.* extensions available in webiny.config.tsx. +--- + +import { Alert } from "@/components/Alert"; + + + +- every `Project.*` extension and the props it accepts + + + +## Overview + +`Project.*` extensions configure project-level settings: identity, telemetry, first-deploy automation, and feature flags. + +### Project.Id + +Sets the project identifier, used internally for telemetry and resource naming. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `id` | `string` | Yes | Project ID. Alphanumeric characters, dashes, underscores, and forward slashes only. | + +### Project.Telemetry + +Opts the project in or out of anonymous usage telemetry. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `enabled` | `boolean` | Yes | Whether to send telemetry. Defaults to `true`. | + +### Project.AutoInstall + +Automatically installs Webiny on first deploy, creating the initial admin user without a manual browser-based setup step. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `adminUser.firstName` | `string` | Yes | Admin user first name | +| `adminUser.lastName` | `string` | Yes | Admin user last name | +| `adminUser.email` | `string` | Yes | Admin user email (valid email format) | +| `adminUser.password` | `string` | Yes | Admin user password (minimum 8 characters) | + + + +Do not hardcode credentials in `webiny.config.tsx`. Reference them via environment variables using `Infra.EnvVar`. + + + +### Project.FeatureFlags + +Enables or disables Webiny Cloud Platform (WCP) licensed features. + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `features.multiTenancy` | `boolean` | No | Multi-tenancy support | +| `features.advancedPublishingWorkflow` | `boolean` | No | Advanced content publishing workflows | +| `features.advancedAccessControlLayer` | `boolean \| object` | No | Advanced ACL. Pass `true` to enable all, or an object to enable selectively (see below). | +| `features.auditLogs` | `boolean` | No | Audit logging | +| `features.recordLocking` | `boolean` | No | Record-level content locking | +| `features.fileManager.threatDetection` | `boolean` | No | Malware threat detection in File Manager uploads | +| `features.aiPowerups.enabled` | `boolean` | No | AI-powered features | +| `features.aiPowerups.options` | `object` | No | Granular AI feature toggles (see below) | + +**`advancedAccessControlLayer` object shape:** + +```typescript +{ + teams?: boolean; + privateFiles?: boolean; + folderLevelPermissions?: boolean; + hcmsFieldPermissions?: boolean; +} +``` + +**`aiPowerups.options` object shape:** + +```typescript +{ + websiteBuilder?: { pageGeneration?: boolean }; + fileManager?: { imageEnrichment?: boolean }; + lexicalGeneration?: boolean; +} +``` From de0db0a235ff124e4af836ae639c138f17d8ecc0 Mon Sep 17 00:00:00 2001 From: adrians5j Date: Mon, 27 Apr 2026 14:06:58 +0200 Subject: [PATCH 13/49] wip: build params --- .../6.x/admin/build-params.ai.txt | 36 +++++++ .../developer-docs/6.x/admin/build-params.mdx | 89 +++++++++++++++++ docs/developer-docs/6.x/navigation.tsx | 2 + .../6.x/webiny-api/build-params.ai.txt | 34 +++++++ .../6.x/webiny-api/build-params.mdx | 95 +++++++++++++++++++ 5 files changed, 256 insertions(+) create mode 100644 docs/developer-docs/6.x/admin/build-params.ai.txt create mode 100644 docs/developer-docs/6.x/admin/build-params.mdx create mode 100644 docs/developer-docs/6.x/webiny-api/build-params.ai.txt create mode 100644 docs/developer-docs/6.x/webiny-api/build-params.mdx diff --git a/docs/developer-docs/6.x/admin/build-params.ai.txt b/docs/developer-docs/6.x/admin/build-params.ai.txt new file mode 100644 index 000000000..91a68a73e --- /dev/null +++ b/docs/developer-docs/6.x/admin/build-params.ai.txt @@ -0,0 +1,36 @@ +AI Context: Build-Time Parameters — Admin (build-params.mdx) + +Source of Information: +1. ~/dev/wby-next/packages/project/src/extensions/AdminBuildParam.ts — extension definition, build step (generates BuildParam_.ts files + feature.ts) +2. ~/dev/wby-next/packages/app-admin/src/features/buildParams/abstractions.ts — BuildParam, BuildParams interfaces +3. ~/dev/wby-next/packages/app-admin/src/features/buildParams/BuildParams.ts — BuildParamsImpl +4. ~/dev/wby-next/packages/app-admin/src/presentation/buildParams/useBuildParams.ts — useBuildParams hook +5. ~/dev/wby-next/packages/webiny/src/admin/build-params.ts — public import path for useBuildParams +6. ~/dev/wby-next/skills/user-skills/generated/admin/build-params/SKILL.md — import paths +7. Slack thread — user pain point: process.env.* in React is replaced at bundle time (build-time substitution hack), not a runtime lookup; Pavel explicitly called it "a dirty hack that should be avoided" + +Key Documentation Decisions: +1. process.env warning is front and center as an Alert — the Slack thread shows this is the exact mistake users make +2. Shows useBuildParams in both a component AND a custom hook — both are common patterns +3. Kept short and practical + +Understanding: +- Admin.BuildParam extension: at build time, generates BuildParam_.ts files + a feature.ts that imports all of them + injects BuildParamsFeature and BuildParamsInternalFeature into Extensions.tsx via RegisterFeature +- At runtime: useBuildParams() calls useFeature(BuildParamsFeature) which resolves the BuildParams abstraction from the DI container; get(key) finds the value in the registered BuildParam instances +- Import path for the hook: `import { useBuildParams } from "webiny/admin/build-params"` +- get(key) returns T | null — null means key was not registered +- process.env: Vite/webpack replaces process.env.FOO at bundle time with the literal string value baked in during that specific build. This means it's NOT configurable per environment without a rebuild. BuildParams values also embed at build time but are registered in webiny.config.tsx — the central config file — rather than scattered across the codebase. + +Key Code Locations: +- packages/project/src/extensions/AdminBuildParam.ts — extension that generates the bundle files +- packages/app-admin/src/features/buildParams/ — runtime implementation +- packages/app-admin/src/presentation/buildParams/useBuildParams.ts — the hook +- packages/webiny/src/admin/build-params.ts — public import path + +Related Documents: +- docs/developer-docs/6.x/webiny-api/build-params.mdx — API equivalent (BuildParams DI injection) +- docs/developer-docs/6.x/reference/extensions/admin.mdx — Admin.BuildParam props reference + +Tone Guidelines: +- Short and practical — the warning about process.env is the most important thing to convey +- Show both component and hook usage patterns diff --git a/docs/developer-docs/6.x/admin/build-params.mdx b/docs/developer-docs/6.x/admin/build-params.mdx new file mode 100644 index 000000000..8b0b163bf --- /dev/null +++ b/docs/developer-docs/6.x/admin/build-params.mdx @@ -0,0 +1,89 @@ +--- +id: v9r4mf6c +title: Build-Time Parameters +description: Learn how to pass build-time configuration values into Admin components using Admin.BuildParam and useBuildParams. +--- + +import { Alert } from "@/components/Alert"; + + + +- how to register a build-time parameter in `webiny.config.tsx` +- how to access it in React components and hooks via `useBuildParams` + + + +## Overview + +`useBuildParams` is the correct way to read configuration values inside Admin React components. Values are registered in `webiny.config.tsx` using `Admin.BuildParam` and embedded into the Admin bundle at build time. + + + +Do not use `process.env.*` to read configuration in React components. Vite/webpack replaces `process.env` references at bundle time with their literal values, making them impossible to vary per environment without rebuilding. Use `Admin.BuildParam` and `useBuildParams` instead. + + + +## Registering a Parameter + +Declare parameters in `webiny.config.tsx` using `Admin.BuildParam`: + +```tsx webiny.config.tsx +import React from "react"; +import { Admin } from "webiny/extensions"; + +export const Extensions = () => { + return ( + <> + + + + ); +}; +``` + +The `value` can be a string, number, boolean, object, or array. + +## Accessing Parameters in a Component + +Use the `useBuildParams` hook anywhere in the Admin React tree: + +```tsx extensions/SupportBanner.tsx +import React from "react"; +import { useBuildParams } from "webiny/admin/build-params"; + +export const SupportBanner = () => { + const params = useBuildParams(); + const supportEmail = params.get("supportEmail"); + + if (!supportEmail) { + return null; + } + + return ( +
+ Need help? Email us at {supportEmail} +
+ ); +}; +``` + +`params.get(key)` returns the value typed as `T`, or `null` if the key was not registered. + +You can also call `useBuildParams` inside a custom hook: + +```typescript extensions/useSupportEmail.ts +import { useBuildParams } from "webiny/admin/build-params"; + +export const useSupportEmail = () => { + const params = useBuildParams(); + return params.get("supportEmail"); +}; +``` + +## Deploying + +After adding or changing an `Admin.BuildParam`, rebuild and redeploy the Admin app: + +``` +yarn webiny deploy admin +``` diff --git a/docs/developer-docs/6.x/navigation.tsx b/docs/developer-docs/6.x/navigation.tsx index 28c1b1660..8696490e0 100644 --- a/docs/developer-docs/6.x/navigation.tsx +++ b/docs/developer-docs/6.x/navigation.tsx @@ -47,6 +47,7 @@ export const Navigation = ({ children }: { children: React.ReactNode }) => { + { + diff --git a/docs/developer-docs/6.x/webiny-api/build-params.ai.txt b/docs/developer-docs/6.x/webiny-api/build-params.ai.txt new file mode 100644 index 000000000..1b3d4e97c --- /dev/null +++ b/docs/developer-docs/6.x/webiny-api/build-params.ai.txt @@ -0,0 +1,34 @@ +AI Context: Build-Time Parameters — API (build-params.mdx) + +Source of Information: +1. ~/dev/wby-next/packages/project/src/extensions/ApiBuildParam.ts — extension definition, build step (embeds values in bundle) +2. ~/dev/wby-next/packages/api-core/src/features/buildParams/abstractions.ts — BuildParam, BuildParams interfaces +3. ~/dev/wby-next/packages/api-core/src/features/buildParams/BuildParams.ts — BuildParamsImpl (get() scans BuildParam array) +4. ~/dev/wby-next/packages/api-core/src/features/encryption/EncryptionService.ts — real-world BuildParams usage pattern +5. ~/dev/wby-next/skills/user-skills/generated/api/build-params/SKILL.md — import paths +6. Slack thread — user pain point: process.env in React is a dirty hack; same principle applies to API + +Key Documentation Decisions: +1. Explicitly distinguishes BuildParams (build-time, from webiny.config.tsx) from process.env (Lambda env vars, set at deploy time via infrastructure) +2. Uses a GraphQL extension as the code example since it's the most common API extension type +3. Kept short — the concept is simple; the article mirrors the structure of extend-graphql-schema.mdx + +Understanding: +- Api.BuildParam extension: at build time, generates a BuildParam_.ts class file in apps/api/graphql/src/buildParams/ and injects it into extensions.ts via createRegisterExtensionPlugin +- At runtime: BuildParamsImpl resolves all registered BuildParam instances (injected as a multiple dependency), then get(key) finds the matching one +- Import path: `import { BuildParams } from "webiny/api"` — also available at "webiny/api/build-params" (deprecated sub-path) +- get(key) returns T | null — null means key was not registered +- Value types: string | number | boolean | object | array (Zod-validated) + +Key Code Locations: +- packages/project/src/extensions/ApiBuildParam.ts — extension that generates the bundle files +- packages/api-core/src/features/buildParams/ — runtime implementation +- packages/webiny/src/api.ts and api/build-params.ts — public import paths + +Related Documents: +- docs/developer-docs/6.x/admin/build-params.mdx — Admin equivalent (useBuildParams hook) +- docs/developer-docs/6.x/reference/extensions/api.mdx — Api.BuildParam props reference + +Tone Guidelines: +- Short and practical — the concept is simple, the code example does the explaining +- Lead with the distinction from process.env/Lambda env vars diff --git a/docs/developer-docs/6.x/webiny-api/build-params.mdx b/docs/developer-docs/6.x/webiny-api/build-params.mdx new file mode 100644 index 000000000..9115c9dff --- /dev/null +++ b/docs/developer-docs/6.x/webiny-api/build-params.mdx @@ -0,0 +1,95 @@ +--- +id: b5w2nx8k +title: Build-Time Parameters +description: Learn how to pass build-time configuration values into API extensions using Api.BuildParam and BuildParams. +--- + +import { Alert } from "@/components/Alert"; + + + +- how to register a build-time parameter in `webiny.config.tsx` +- how to access it inside an API extension via dependency injection + + + +## Overview + +`BuildParams` is the mechanism for passing configuration values — determined at build time — into API extensions. Values are registered in `webiny.config.tsx` using `Api.BuildParam` and embedded directly into the Lambda bundle during the build step. + +This is distinct from Lambda environment variables, which are set at deploy time through infrastructure and are available via `process.env`. `BuildParams` are for values that come from your project configuration, not from AWS infrastructure. + +## Registering a Parameter + +Declare parameters in `webiny.config.tsx` using `Api.BuildParam`: + +```tsx webiny.config.tsx +import React from "react"; +import { Api } from "webiny/extensions"; + +export const Extensions = () => { + return ( + <> + + + + ); +}; +``` + +The `value` can be a string, number, boolean, object, or array. + +## Accessing Parameters in an Extension + +Inject `BuildParams` into any API extension class: + +```typescript extensions/myApiExtension.ts +import { BuildParams, GraphQLSchemaFactory } from "webiny/api"; + +class MyApiExtension implements GraphQLSchemaFactory.Interface { + public constructor(private buildParams: BuildParams.Interface) {} + + public async execute(builder: GraphQLSchemaFactory.SchemaBuilder): GraphQLSchemaFactory.Return { + const apiUrl = this.buildParams.get("externalApiUrl"); + const maxRetries = this.buildParams.get("maxRetries"); + + builder.addTypeDefs(/* GraphQL */ ` + type MyConfig { + apiUrl: String + maxRetries: Int + } + extend type Query { + myConfig: MyConfig + } + `); + + builder.addResolver({ + path: "Query.myConfig", + dependencies: [], + resolver() { + return async () => ({ + apiUrl, + maxRetries + }); + } + }); + + return builder; + } +} + +export default GraphQLSchemaFactory.createImplementation({ + implementation: MyApiExtension, + dependencies: [BuildParams] +}); +``` + +`buildParams.get(key)` returns the value typed as `T`, or `null` if the key was not registered. + +## Deploying + +After adding or changing a `BuildParam`, rebuild and redeploy the API: + +``` +yarn webiny deploy api +``` From 9c6395753529444d7f2a5c87ca0677e28a621725 Mon Sep 17 00:00:00 2001 From: adrians5j Date: Mon, 27 Apr 2026 14:37:48 +0200 Subject: [PATCH 14/49] wip: build params --- .../developer-docs/6.x/admin/build-params.mdx | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/docs/developer-docs/6.x/admin/build-params.mdx b/docs/developer-docs/6.x/admin/build-params.mdx index 8b0b163bf..5a6ec95ef 100644 --- a/docs/developer-docs/6.x/admin/build-params.mdx +++ b/docs/developer-docs/6.x/admin/build-params.mdx @@ -10,18 +10,13 @@ import { Alert } from "@/components/Alert"; - how to register a build-time parameter in `webiny.config.tsx` - how to access it in React components and hooks via `useBuildParams` +- how to access it in non-React Admin code via `BuildParams` ## Overview -`useBuildParams` is the correct way to read configuration values inside Admin React components. Values are registered in `webiny.config.tsx` using `Admin.BuildParam` and embedded into the Admin bundle at build time. - - - -Do not use `process.env.*` to read configuration in React components. Vite/webpack replaces `process.env` references at bundle time with their literal values, making them impossible to vary per environment without rebuilding. Use `Admin.BuildParam` and `useBuildParams` instead. - - +`BuildParams` is the mechanism for passing configuration values — determined at build time — into the Admin application. Values are registered in `webiny.config.tsx` using `Admin.BuildParam` and embedded directly into the Admin bundle during the build step. Inside React components and hooks, they are accessed via the `useBuildParams` hook. In non-React Admin code (services, class-based extensions), inject `BuildParams` directly via dependency injection. ## Registering a Parameter @@ -47,10 +42,15 @@ The `value` can be a string, number, boolean, object, or array. Use the `useBuildParams` hook anywhere in the Admin React tree: + + +`useBuildParams` is currently imported from `webiny/admin/build-params`. It will also be available directly from `webiny/admin` in an upcoming release. + + + ```tsx extensions/SupportBanner.tsx import React from "react"; import { useBuildParams } from "webiny/admin/build-params"; - export const SupportBanner = () => { const params = useBuildParams(); const supportEmail = params.get("supportEmail"); @@ -73,13 +73,33 @@ You can also call `useBuildParams` inside a custom hook: ```typescript extensions/useSupportEmail.ts import { useBuildParams } from "webiny/admin/build-params"; - export const useSupportEmail = () => { const params = useBuildParams(); return params.get("supportEmail"); }; ``` +## Accessing Parameters in Non-React Code + +In class-based Admin extensions (services, event handlers, use cases), inject `BuildParams` via the constructor: + +```typescript extensions/MyAdminService.ts +import { BuildParams } from "webiny/admin/build-params"; +class MyAdminServiceImpl implements MyAdminService.Interface { + public constructor(private buildParams: BuildParams.Interface) {} + + public async execute(): Promise { + const apiUrl = this.buildParams.get("externalApiUrl"); + // ... + } +} + +export default MyAdminService.createImplementation({ + implementation: MyAdminServiceImpl, + dependencies: [BuildParams] +}); +``` + ## Deploying After adding or changing an `Admin.BuildParam`, rebuild and redeploy the Admin app: @@ -87,3 +107,9 @@ After adding or changing an `Admin.BuildParam`, rebuild and redeploy the Admin a ``` yarn webiny deploy admin ``` + +During development, rerunning watch is enough — no full deploy needed: + +``` +yarn webiny watch admin +``` From e2963fc909138fa63157b344b5bb95a2fbcc7f2f Mon Sep 17 00:00:00 2001 From: adrians5j Date: Mon, 27 Apr 2026 14:48:36 +0200 Subject: [PATCH 15/49] wip: build params --- docs/developer-docs/6.x/admin/build-params.mdx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/developer-docs/6.x/admin/build-params.mdx b/docs/developer-docs/6.x/admin/build-params.mdx index 5a6ec95ef..bd4d0c1d0 100644 --- a/docs/developer-docs/6.x/admin/build-params.mdx +++ b/docs/developer-docs/6.x/admin/build-params.mdx @@ -42,15 +42,9 @@ The `value` can be a string, number, boolean, object, or array. Use the `useBuildParams` hook anywhere in the Admin React tree: - - -`useBuildParams` is currently imported from `webiny/admin/build-params`. It will also be available directly from `webiny/admin` in an upcoming release. - - - ```tsx extensions/SupportBanner.tsx import React from "react"; -import { useBuildParams } from "webiny/admin/build-params"; +import { useBuildParams } from "webiny/admin"; export const SupportBanner = () => { const params = useBuildParams(); const supportEmail = params.get("supportEmail"); @@ -72,7 +66,7 @@ export const SupportBanner = () => { You can also call `useBuildParams` inside a custom hook: ```typescript extensions/useSupportEmail.ts -import { useBuildParams } from "webiny/admin/build-params"; +import { useBuildParams } from "webiny/admin"; export const useSupportEmail = () => { const params = useBuildParams(); return params.get("supportEmail"); @@ -84,7 +78,7 @@ export const useSupportEmail = () => { In class-based Admin extensions (services, event handlers, use cases), inject `BuildParams` via the constructor: ```typescript extensions/MyAdminService.ts -import { BuildParams } from "webiny/admin/build-params"; +import { BuildParams } from "webiny/admin"; class MyAdminServiceImpl implements MyAdminService.Interface { public constructor(private buildParams: BuildParams.Interface) {} From cb99579fe0c1352ac242257affe46e044844f94b Mon Sep 17 00:00:00 2001 From: adrians5j Date: Mon, 27 Apr 2026 17:01:04 +0200 Subject: [PATCH 16/49] docs: clarify shared OpenSearch cluster ownership Users were unclear whether Webiny provisions the shared cluster or whether they must bring their own. Make it explicit that the shared domain must be deployed and managed outside of Webiny (via AWS console, Terraform, Pulumi, or any other tool) before configuring the endpoint in webiny.config.tsx. --- .../6.x/infrastructure/extensions/opensearch.ai.txt | 1 + .../6.x/infrastructure/extensions/opensearch.mdx | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/developer-docs/6.x/infrastructure/extensions/opensearch.ai.txt b/docs/developer-docs/6.x/infrastructure/extensions/opensearch.ai.txt index 26b9e92b2..74e26def6 100644 --- a/docs/developer-docs/6.x/infrastructure/extensions/opensearch.ai.txt +++ b/docs/developer-docs/6.x/infrastructure/extensions/opensearch.ai.txt @@ -9,6 +9,7 @@ Key Documentation Decisions: - Dropped ElasticSearch section entirely — v6 only supports OpenSearch - Adjusting config section uses CorePulumi extension with onResource pattern (mirrors v5 onResource callback) - Shared domain example uses Infra.Env.Is to scope it to dev only, matching real-world usage +- Explicitly stated that the shared cluster must be deployed and managed outside of Webiny (AWS console, Terraform, Pulumi, etc.) — this was previously implicit and caused user confusion - indexPrefix explanation kept — non-obvious but important to prevent data bleed between envs - Warning about shared domain only for dev is retained from v5 diff --git a/docs/developer-docs/6.x/infrastructure/extensions/opensearch.mdx b/docs/developer-docs/6.x/infrastructure/extensions/opensearch.mdx index bb60e8d2d..57a9d43c9 100644 --- a/docs/developer-docs/6.x/infrastructure/extensions/opensearch.mdx +++ b/docs/developer-docs/6.x/infrastructure/extensions/opensearch.mdx @@ -72,7 +72,11 @@ export default CorePulumi.createImplementation({ ## Using a Shared OpenSearch Domain -For development purposes, you can share a single OpenSearch domain across multiple environments using the `domainName` and `indexPrefix` props: +For development purposes, you can share a single OpenSearch domain across multiple environments using the `domainName` and `indexPrefix` props. + +The shared OpenSearch cluster must be deployed and managed outside of Webiny — it is not provisioned by Webiny itself. You can deploy it using the AWS console, Terraform, Pulumi, or any other tool of your choice. + +Once the cluster is running, configure Webiny to connect to it: ```tsx webiny.config.tsx import React from "react"; From d99f21b9bd8941ee93abef8b605f8a043820746a Mon Sep 17 00:00:00 2001 From: adrians5j Date: Tue, 28 Apr 2026 13:24:27 +0200 Subject: [PATCH 17/49] wip:11 (fabzx) --- .../5.x/admin-area/basics/framework.mdx | 2 +- .../extending/environment-utilities.mdx | 10 +- .../5.x/architecture/api/overview.mdx | 4 +- .../5.x/architecture/introduction.mdx | 4 +- .../about-background-tasks.mdx | 9 +- .../background-task-management.mdx | 73 +- .../built-in-background-tasks.mdx | 6 +- .../how-background-tasks-work.mdx | 7 +- .../how-to-define-a-background-task.mdx | 102 +-- .../how-to-handle-a-background-task-run.mdx | 239 +++--- .../what-to-be-careful-about.mdx | 7 +- .../basics/environment-variables.mdx | 2 +- .../basics/extensions.mdx | 4 +- .../basics/logger.mdx | 87 +- .../basics/project-deployment.mdx | 2 +- .../basics/routes-and-events.mdx | 7 +- .../basics/tools-and-libraries.mdx | 2 +- .../basics/user-project-upgrade.mdx | 8 +- .../basics/watch-command.mdx | 16 +- .../basics/webiny-cli.mdx | 25 +- .../ci-cd/testing.mdx | 1 + .../development/local-development.mdx | 6 +- .../extend-graphql-api.mdx | 78 +- .../how-to-customize-elasticsearch-index.mdx | 44 +- .../aacl/folder-level-permissions.mdx | 2 - .../5.x/enterprise/aacl/private-files.mdx | 13 +- .../5.x/enterprise/aacl/teams.mdx | 2 +- .../enterprise/advanced-tenant-management.mdx | 8 +- .../5.x/enterprise/auth0-integration.mdx | 5 +- .../5.x/enterprise/cognito-federation.mdx | 10 +- .../5.x/enterprise/multi-tenancy.mdx | 1 - ...mation-dialog-for-folder-drag-and-drop.mdx | 17 +- .../extending/customize-file-actions.mdx | 62 +- .../extending/customize-file-list-actions.mdx | 42 +- .../customize-file-list-table-columns.mdx | 50 +- .../extending/customize-file-preview.mdx | 8 +- .../extending/customize-folder-fields.mdx | 10 +- .../5.x/get-started/build-with-llms-ai.mdx | 15 +- .../5.x/headless-cms/ai/smart-seo-open-ai.mdx | 27 +- .../content-modeling-best-practices.mdx | 22 +- .../using-graphql-api-advanced-filtering.mdx | 2 +- .../extending/conditional-rendering.mdx | 10 +- ...mation-dialog-for-folder-drag-and-drop.mdx | 17 +- .../extending/content-models-via-code.mdx | 401 +++++---- .../create-rich-text-content-renderer.mdx | 2 +- .../extending/custom-field-type.mdx | 2 +- .../extending/customize-entry-form-layout.mdx | 171 ++-- .../customize-entry-list-actions.mdx | 45 +- .../customize-entry-list-bulk-actions.mdx | 35 +- .../customize-entry-list-table-columns.mdx | 52 +- .../extending/customize-folder-fields.mdx | 10 +- .../extending/extend-graphql-api.mdx | 13 +- .../headless-cms/extending/private-models.mdx | 2 +- .../legacy-render-rich-text-content.mdx | 4 +- .../headless-cms/notes-app/introduction.mdx | 3 +- .../notes-app/react-notes-app.mdx | 117 ++- .../notes-app/webiny-infrastructure-setup.mdx | 269 +++--- .../5.x/headless-cms/overview.mdx | 128 +-- .../references/lifecycle-events.mdx | 30 +- .../aws/configure-aws-credentials.mdx | 4 +- .../basics/modify-cloud-infrastructure.mdx | 19 +- .../pulumi-iac/iac-with-pulumi.mdx | 4 +- .../a-b-testing-and-personalization.mdx | 6 +- .../5.x/overview/features/audit-logs.mdx | 14 +- .../5.x/overview/features/mailer.mdx | 70 +- .../overview/why-and-when-to-use-webiny.mdx | 32 +- ...mation-dialog-for-folder-drag-and-drop.mdx | 19 +- .../extending/create-a-page-element.mdx | 261 +++--- .../customize-button-callback-handler.mdx | 1 + .../extending/customize-folder-fields.mdx | 10 +- .../extending/customize-page-list-actions.mdx | 42 +- .../loading-data-in-page-elements.mdx | 113 +-- .../5.x/page-builder/overview.mdx | 109 +-- .../headless-cms/performance-tuning.mdx | 2 +- .../headless-cms/read-benchmark.mdx | 2 +- .../headless-cms/write-benchmark.mdx | 2 +- ...rogrammatically-create-roles-and-teams.mdx | 78 +- docs/developer-docs/5.x/security/overview.mdx | 8 +- .../build-with-ai/ai-assisted-development.mdx | 1 - .../6.x/core-concepts/security.mdx | 12 +- .../6.x/get-started/deploy-webiny.mdx | 1 - .../6.x/get-started/first-customization.mdx | 114 ++- .../6.x/get-started/local-development.mdx | 1 - .../6.x/get-started/quickstart.mdx | 1 - .../6.x/get-started/welcome.mdx | 11 +- docs/developer-docs/6.x/graphql/about.mdx | 2 +- docs/developer-docs/6.x/graphql/example.mdx | 271 +++--- docs/developer-docs/6.x/graphql/reference.mdx | 48 +- .../6.x/headless-cms/builder/field.mdx | 112 ++- .../6.x/headless-cms/builder/group.mdx | 31 +- .../6.x/headless-cms/builder/model.mdx | 183 ++-- .../event-handler/entry-extended.mdx | 104 +-- .../6.x/headless-cms/event-handler/entry.mdx | 786 +++++++++--------- .../6.x/headless-cms/event-handler/group.mdx | 211 +++-- .../6.x/headless-cms/event-handler/model.mdx | 277 +++--- .../headless-cms/examples/private-model.mdx | 101 ++- .../examples/single-entry-model.mdx | 74 +- .../6.x/headless-cms/ui/field-renderer.mdx | 39 +- .../6.x/headless-cms/use-case/entry.mdx | 96 +-- .../6.x/headless-cms/use-case/group.mdx | 104 +-- .../6.x/headless-cms/use-case/model.mdx | 208 +++-- .../6.x/infrastructure/opensearch.ai.txt | 38 + .../6.x/infrastructure/opensearch.mdx | 121 +++ docs/developer-docs/6.x/navigation.tsx | 3 +- .../6.x/reference/extensions/admin.mdx | 16 +- .../6.x/reference/extensions/api.mdx | 28 +- .../6.x/reference/extensions/cli.mdx | 6 +- .../6.x/reference/extensions/infra.mdx | 222 ++--- .../6.x/reference/extensions/project.mdx | 44 +- .../6.x/webiny-api/api-routes.mdx | 38 +- .../6.x/website-builder/custom-component.mdx | 4 +- .../website-builder/event-handler/pages.mdx | 264 +++--- .../event-handler/redirects.mdx | 199 +++-- .../6.x/website-builder/use-case/pages.mdx | 393 ++++----- .../website-builder/use-case/redirects.mdx | 2 +- .../deciding-what-features-to-build.mdx | 8 +- .../company/how-we-ensure-quality.mdx | 26 +- docs/handbook/company/how-we-got-here.mdx | 16 +- docs/handbook/company/how-we-make-money.mdx | 11 +- .../company/how-we-make-users-happy.mdx | 10 +- docs/handbook/company/our-values.mdx | 24 +- .../company/who-are-we-building-for.mdx | 3 +- .../company/why-we-chose-serverless.mdx | 6 +- docs/handbook/company/why-webiny-exists.mdx | 18 +- ...eployment-ddb-conditional-check-failed.mdx | 12 +- docs/messages/missing-stack-output.mdx | 2 +- .../webiny-package-versions-check.mdx | 4 +- docs/release-notes/6.1.0/upgrade-guide.mdx | 4 +- docs/release-notes/6.2.0/changelog.mdx | 10 +- docs/release-notes/upgrade-webiny.mdx | 20 +- ...apw-user-guides-outline-users-creation.mdx | 74 +- .../5.x/apw/essentials/define-workflow.mdx | 67 +- .../apw/essentials/introduction-to-apw.mdx | 22 +- .../5.x/apw/essentials/provide-sign-off.mdx | 24 +- .../5.x/apw/essentials/review-record.mdx | 15 +- .../apw/essentials/scheduled-publishing.mdx | 16 +- .../5.x/apw/essentials/submit-change.mdx | 17 +- .../essentials/submit-record-for-review.mdx | 36 +- .../file-manager/essentials/bulk-actions.mdx | 15 +- .../file-manager/essentials/file-aliases.mdx | 18 +- .../essentials/organizing-files.mdx | 27 +- .../file-manager/essentials/tagging-files.mdx | 22 +- .../file-manager/essentials/upload-file.mdx | 15 +- .../5.x/security/essentials/assign-role.mdx | 4 +- .../5.x/security/essentials/assign-team.mdx | 6 +- .../5.x/security/essentials/role-creation.mdx | 7 +- .../5.x/security/essentials/team-creation.mdx | 10 +- .../5.x/security/essentials/user-creation.mdx | 5 +- .../essentials/organizing-files.mdx | 33 +- .../file-manager/essentials/tagging-files.mdx | 43 +- .../file-manager/essentials/upload-file.mdx | 28 +- .../advanced/import-export-content-models.mdx | 4 +- .../essentials/add-validator-to-fields.mdx | 17 +- .../essentials/clone-content-model.mdx | 8 +- .../essentials/content-entry-revisions.mdx | 44 +- .../essentials/create-content-model-group.mdx | 10 +- .../essentials/create-content-model.mdx | 12 +- .../manage-content-model-settings.mdx | 16 +- .../essentials/reference-field.mdx | 62 +- .../6.x/security/essentials/assign-role.mdx | 11 +- .../6.x/security/essentials/assign-team.mdx | 10 +- .../6.x/security/essentials/create-team.mdx | 12 +- .../security/essentials/create-user-role.mdx | 32 +- .../6.x/security/essentials/create-user.mdx | 19 +- .../essentials/create-page.mdx | 17 +- .../essentials/custom-components.mdx | 31 +- .../website-builder/essentials/glossary.mdx | 3 +- .../essentials/nextjs-starter-kit.mdx | 48 +- scripts/generate-v5-redirects.js | 1 - src/components/CanIUseThis.js | 6 +- src/components/PageNotFound.js | 23 +- src/components/Tag.js | 12 +- src/components/landing-pages/Divider.js | 4 +- src/components/landing-pages/Hero.js | 35 +- .../landing-pages/InstallWebinyBanner.js | 49 +- src/components/landing-pages/Section.js | 83 +- src/components/landing-pages/utils.js | 9 +- src/components/mdxComponents.js | 16 +- src/components/page/Page.js | 7 +- src/components/page/PageProvider.js | 2 +- src/components/page/ViewLatestVersion.js | 5 +- src/layouts/ReleaseNotesLayout.js | 4 +- src/layouts/sidebar/components/IconHeader.js | 4 +- .../sidebar/components/SectionLabel.js | 52 +- src/layouts/sidebar/hooks/useAutoScroll.js | 3 +- src/pages/_error.js | 3 +- src/utils/stripBasePath.js | 1 - 187 files changed, 4326 insertions(+), 4341 deletions(-) create mode 100644 docs/developer-docs/6.x/infrastructure/opensearch.ai.txt create mode 100644 docs/developer-docs/6.x/infrastructure/opensearch.mdx diff --git a/docs/developer-docs/5.x/admin-area/basics/framework.mdx b/docs/developer-docs/5.x/admin-area/basics/framework.mdx index 7e2231b09..d9b70741b 100644 --- a/docs/developer-docs/5.x/admin-area/basics/framework.mdx +++ b/docs/developer-docs/5.x/admin-area/basics/framework.mdx @@ -120,7 +120,7 @@ export const App = () => { - We highly recommend using our [Extensions](/{version}/core-development-concepts/basics/extensions) to organize your code in a scalable and portable manner. +We highly recommend using our [Extensions](/{version}/core-development-concepts/basics/extensions) to organize your code in a scalable and portable manner. diff --git a/docs/developer-docs/5.x/admin-area/extending/environment-utilities.mdx b/docs/developer-docs/5.x/admin-area/extending/environment-utilities.mdx index 67cfd34bf..f6b68952e 100644 --- a/docs/developer-docs/5.x/admin-area/extending/environment-utilities.mdx +++ b/docs/developer-docs/5.x/admin-area/extending/environment-utilities.mdx @@ -7,8 +7,8 @@ description: Learn about environment-related utility functions available within import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; - - what are environment-related utility functions - - what environment-related utility functions are available + - what are environment-related utility functions - what environment-related utility functions are + available ## Overview @@ -17,7 +17,7 @@ This article covers a couple of environment-related utility functions that can b - To learn more about extensions in general, please visit the [Extensions](/{version}/core-development-concepts/basics/extensions) article. +To learn more about extensions in general, please visit the [Extensions](/{version}/core-development-concepts/basics/extensions) article. @@ -32,7 +32,7 @@ import { getHeadlessCmsGqlApiUrl, getLocaleCode, getTenantId, - isLocalhost, + isLocalhost } from "@webiny/app-admin"; // Use `@webiny/app` for versions 5.41.3 or older. // Returns URL of Webiny's backend API. @@ -56,6 +56,6 @@ isLocalhost(); // true - For versions 5.41.3 or older, instead of the `@webiny/app-admin` package, please use the `@webiny/app` package. +For versions 5.41.3 or older, instead of the `@webiny/app-admin` package, please use the `@webiny/app` package. diff --git a/docs/developer-docs/5.x/architecture/api/overview.mdx b/docs/developer-docs/5.x/architecture/api/overview.mdx index 6c8aab3a1..f0824d9b6 100644 --- a/docs/developer-docs/5.x/architecture/api/overview.mdx +++ b/docs/developer-docs/5.x/architecture/api/overview.mdx @@ -100,9 +100,9 @@ Learn more about the Headless CMS API in the [Headless CMS GraphQL API Overview] For background tasks (jobs)-related requirements, Webiny relies on [AWS Step Functions](https://aws.amazon.com/step-functions/) and AWS Lambda. The Step Functions are used to orchestrate the execution of the background tasks, while a single AWS Lambda function is used to execute the tasks themselves. -Note that background tasks are triggered via an Amazon EventBridge event. Amazon EventBridge is deployed as part of the **Core** project application (not shown on the diagram). +Note that background tasks are triggered via an Amazon EventBridge event. Amazon EventBridge is deployed as part of the **Core** project application (not shown on the diagram). -For more information on how to create and execute background tasks, check out the [Background Tasks](/{version}/core-development-concepts/background-tasks/about-background-tasks) article. +For more information on how to create and execute background tasks, check out the [Background Tasks](/{version}/core-development-concepts/background-tasks/about-background-tasks) article. ## Additional Information diff --git a/docs/developer-docs/5.x/architecture/introduction.mdx b/docs/developer-docs/5.x/architecture/introduction.mdx index e7944e7ed..37a1b37f5 100644 --- a/docs/developer-docs/5.x/architecture/introduction.mdx +++ b/docs/developer-docs/5.x/architecture/introduction.mdx @@ -59,12 +59,12 @@ By default, the development mode is used when deploying into any environment, ex - The **Admin Area** and **Website** project applications do not posses the ability to be deployed in development and production deployment modes, as it's simply not needed. +The **Admin Area** and **Website** project applications do not posses the ability to be deployed in development and production deployment modes, as it's simply not needed. - If needed, Webiny can also be deployed into a preexisting [Virtual Private Cloud (VPC)](https://aws.amazon.com/vpc/). For more information, please refer to the [Use Existing Amazon VPC](/{version}/enterprise/use-existing-amazon-vpc) guide. +If needed, Webiny can also be deployed into a preexisting [Virtual Private Cloud (VPC)](https://aws.amazon.com/vpc/). For more information, please refer to the [Use Existing Amazon VPC](/{version}/enterprise/use-existing-amazon-vpc) guide. diff --git a/docs/developer-docs/5.x/core-development-concepts/background-tasks/about-background-tasks.mdx b/docs/developer-docs/5.x/core-development-concepts/background-tasks/about-background-tasks.mdx index 64b7083f6..8fbc7f616 100644 --- a/docs/developer-docs/5.x/core-development-concepts/background-tasks/about-background-tasks.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/background-tasks/about-background-tasks.mdx @@ -4,9 +4,9 @@ title: About Background Tasks description: You will learn about Background Tasks, how to create new definitions, how to trigger them and how to handle the task run. --- -import {Alert} from "@/components/Alert"; -import {CanIUseThis} from "@/components/CanIUseThis"; -import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; +import { Alert } from "@/components/Alert"; +import { CanIUseThis } from "@/components/CanIUseThis"; +import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; @@ -27,12 +27,13 @@ The Background Task run limit is `1 year`, from the time when the task is trigge This functionality uses the `AWS Event Bridge`, `AWS Step Function` and `AWS Lambda`. ## AWS Services Used in the Background Tasks + To find out more about these services, please visit the following links: + - [`AWS Event Bridge`](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-what-is.html) - [`AWS Step Function`](https://docs.aws.amazon.com/step-functions/latest/dg/welcome.html) - [`AWS Lambda`](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html) - Currently, Background Tasks are not using the Task Token, so they are not resumable. We will be working on the solution for this in the future. diff --git a/docs/developer-docs/5.x/core-development-concepts/background-tasks/background-task-management.mdx b/docs/developer-docs/5.x/core-development-concepts/background-tasks/background-task-management.mdx index 484d79c73..9489d6dc4 100644 --- a/docs/developer-docs/5.x/core-development-concepts/background-tasks/background-task-management.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/background-tasks/background-task-management.mdx @@ -4,12 +4,11 @@ title: Background Task Management description: You will learn about Background Tasks, how to create new definitions, how to trigger them and how to handle the task run. --- +import { Alert } from "@/components/Alert"; +import { CanIUseThis } from "@/components/CanIUseThis"; +import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; -import {Alert} from "@/components/Alert"; -import {CanIUseThis} from "@/components/CanIUseThis"; -import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; - - + @@ -18,7 +17,6 @@ import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; - ## GraphQL API The Background Tasks do not have a section in the Admin UI yet, so they are accessible only via the GraphQL API. @@ -50,6 +48,7 @@ query ListTaskDefinitions { ``` #### List All Background Task Runs + ```graphql query ListTasks { backgroundTasks { @@ -85,10 +84,12 @@ query ListTasks { ```graphql query ListBackgroundTaskLogs { backgroundTasks { - listLogs(where: { - # you can list logs from a certain task if you like - task: "yourTaskId" - }) { + listLogs( + where: { + # you can list logs from a certain task if you like + task: "yourTaskId" + } + ) { data { id createdOn @@ -108,15 +109,19 @@ query ListBackgroundTaskLogs { ``` ### Mutations + #### Trigger a Background Task ```graphql mutation TriggerATask { backgroundTasks { - triggerTask(definition: testingRun, input: { - someVariableForTestingRunTaskToReceive: "someValue", - yetAnotherVariableForTestingRunTaskToReceive: "anotherValue" - }) { + triggerTask( + definition: testingRun + input: { + someVariableForTestingRunTaskToReceive: "someValue" + yetAnotherVariableForTestingRunTaskToReceive: "anotherValue" + } + ) { data { id definitionId @@ -186,11 +191,11 @@ const results = await context.tasks.listDefinitions(); ```typescript const results = await context.tasks.listTasks({ - // all properties are optional - where: {}, - sort: [], - limit: 100, - after: null + // all properties are optional + where: {}, + sort: [], + limit: 100, + after: null }); ``` @@ -198,13 +203,13 @@ const results = await context.tasks.listTasks({ ```typescript const result = await context.tasks.trigger({ - definition: "myTaskDefinition", - // optional input for the task run - input: { - variableToPassAsInput: "someValue" - }, - // optional name for the task run - name: "My Custom Task Name" + definition: "myTaskDefinition", + // optional input for the task run + input: { + variableToPassAsInput: "someValue" + }, + // optional name for the task run + name: "My Custom Task Name" }); ``` @@ -212,9 +217,9 @@ const result = await context.tasks.trigger({ ```typescript const result = await context.tasks.trigger({ - id: "yourTaskId", - // optional message - message: "My Reason for aborting the task" + id: "yourTaskId", + // optional message + message: "My Reason for aborting the task" }); ``` @@ -222,10 +227,10 @@ const result = await context.tasks.trigger({ ```typescript const results = await context.tasks.listLogs({ - // all properties are optional - where: {}, - sort: [], - limit: 100, - after: null + // all properties are optional + where: {}, + sort: [], + limit: 100, + after: null }); ``` diff --git a/docs/developer-docs/5.x/core-development-concepts/background-tasks/built-in-background-tasks.mdx b/docs/developer-docs/5.x/core-development-concepts/background-tasks/built-in-background-tasks.mdx index 3c2bc043e..9877b831c 100644 --- a/docs/developer-docs/5.x/core-development-concepts/background-tasks/built-in-background-tasks.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/background-tasks/built-in-background-tasks.mdx @@ -25,8 +25,8 @@ The following is a list of background tasks that are included in Webiny by defau - This task can be used with the Amazon DynamoDB + Amazon OpenSearch [database setup](/{version}/architecture/introduction#different-database-setups). It can be also used with the legacy Amazon DynamoDB + Amazon Elasticsearch setup. - +This task can be used with the Amazon DynamoDB + Amazon OpenSearch [database setup](/{version}/architecture/introduction#different-database-setups). It can be also used with the legacy Amazon DynamoDB + Amazon Elasticsearch setup. + The **Reindexing Task** is a background task that scans through the DynamoDB Elasticsearch table and pushes the data into the specific Elasticsearch/OpenSearch index. @@ -82,7 +82,7 @@ This is done to reduce the strain on the Elasticsearch/OpenSearch cluster as the - This task can be used with the Amazon DynamoDB + Amazon OpenSearch [database setup](/{version}/architecture/introduction#different-database-setups). It can be also used with the legacy Amazon DynamoDB + Amazon Elasticsearch setup. +This task can be used with the Amazon DynamoDB + Amazon OpenSearch [database setup](/{version}/architecture/introduction#different-database-setups). It can be also used with the legacy Amazon DynamoDB + Amazon Elasticsearch setup. diff --git a/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-background-tasks-work.mdx b/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-background-tasks-work.mdx index 7a0b2aa90..c31f99413 100644 --- a/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-background-tasks-work.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-background-tasks-work.mdx @@ -4,9 +4,9 @@ title: How Background Tasks Work? description: You will learn about Background Tasks, how to create new definitions, how to trigger them and how to handle the task run. --- -import {Alert} from "@/components/Alert"; -import {CanIUseThis} from "@/components/CanIUseThis"; -import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; +import { Alert } from "@/components/Alert"; +import { CanIUseThis } from "@/components/CanIUseThis"; +import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; import backgroundTaskStepFunction from "./assets/bg-task-step-function.png"; @@ -19,7 +19,6 @@ import backgroundTaskStepFunction from "./assets/bg-task-step-function.png"; - ## Overview A Background Task lifecycle is as follows: diff --git a/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-define-a-background-task.mdx b/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-define-a-background-task.mdx index d7c25e3d1..1285b0720 100644 --- a/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-define-a-background-task.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-define-a-background-task.mdx @@ -4,9 +4,9 @@ title: How to Define a Background Task description: You will learn about Background Tasks, how to create new definitions, how to trigger them and how to handle the task run. --- -import {Alert} from "@/components/Alert"; -import {CanIUseThis} from "@/components/CanIUseThis"; -import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; +import { Alert } from "@/components/Alert"; +import { CanIUseThis } from "@/components/CanIUseThis"; +import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; @@ -18,94 +18,96 @@ import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; - ## Overview First, you need to know that there are two types of Background Tasks: + - public - can be called via GraphQL API or the programmatic API - private - can be called only through the programmatic API Definition of the task is the same for both types of tasks, with a difference of the method name used to define the task. - ## Basic Definition To see all available properties, and information about the properties, for the task definition, check the [`ITaskDefinition`](https://github.com/webiny/webiny-js/blob/abb442c6d67c97980b8053c5e53db7fb4fec88b4/packages/tasks/src/types.ts#L328) interface. There are few properties, which should not be used: - - `fields` - not implemented yet - - `isPrivate` - set automatically when a user defines a task via `createPrivateTaskDefinition` + +- `fields` - not implemented yet +- `isPrivate` - set automatically when a user defines a task via `createPrivateTaskDefinition` ### Public Task + ```typescript import { createTaskDefinition } from "@webiny/tasks"; const myPublicTaskDefinition = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({response}) { - // your code here - return response.done(); - } + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ response }) { + // your code here + return response.done(); + } }); ``` ### Private Task + ```typescript import { createPrivateTaskDefinition } from "@webiny/tasks"; const myPrivateTaskDefinition = createPrivateTaskDefinition({ - id: "myPrivateTask", - title: "A Task Accessible Only Via The Code", - async run({response}) { - // your code here - return response.done(); - } + id: "myPrivateTask", + title: "A Task Accessible Only Via The Code", + async run({ response }) { + // your code here + return response.done(); + } }); ``` ## Advanced Definition - ```typescript const syncArticles = createTaskDefinition({ - id: "syncArticles", - title: "Sync Articles", - description: "A task which syncs Webiny articles with another system.", - // optional when defined via the createTaskDefinition or createPrivateTaskDefinition method - // default value is 500 - maxIterations: 1000, - async run({response}) { - // your code which syncs articles with another system - return response.done(); - }, - onBeforeTrigger: async({context, input}) => { - // check if articles are already syncing - // if yes, throw an error - }, - onDone: async({context, task}) => { - // notify another system that articles are synced - }, - onError: async({context, task}) => { - // notify another system that articles are not synced due to an error - }, - onAbort: async({context, task}) => { - // notify another system that articles are not synced due to user aborting the task - }, - onMaxIterations: async({context, task}) => { - // notify another system that articles are not synced due to reaching max iterations of the task - }, + id: "syncArticles", + title: "Sync Articles", + description: "A task which syncs Webiny articles with another system.", + // optional when defined via the createTaskDefinition or createPrivateTaskDefinition method + // default value is 500 + maxIterations: 1000, + async run({ response }) { + // your code which syncs articles with another system + return response.done(); + }, + onBeforeTrigger: async ({ context, input }) => { + // check if articles are already syncing + // if yes, throw an error + }, + onDone: async ({ context, task }) => { + // notify another system that articles are synced + }, + onError: async ({ context, task }) => { + // notify another system that articles are not synced due to an error + }, + onAbort: async ({ context, task }) => { + // notify another system that articles are not synced due to user aborting the task + }, + onMaxIterations: async ({ context, task }) => { + // notify another system that articles are not synced due to reaching max iterations of the task + } }); ``` ## Registering the Task Of course, as all other Webiny plugins, users must register the task definition in the `plugins` array of the `createHandler` function. + ```typescript export const handler = createHandler({ - plugins: [ - // ...rest of plugins - syncArticles - ] + plugins: [ + // ...rest of plugins + syncArticles + ] }); ``` diff --git a/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-handle-a-background-task-run.mdx b/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-handle-a-background-task-run.mdx index a517285fa..5e1b748f2 100644 --- a/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-handle-a-background-task-run.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/background-tasks/how-to-handle-a-background-task-run.mdx @@ -4,9 +4,9 @@ title: How to Handle a Background Task description: You will learn about Background Tasks, how to create new definitions, how to trigger them and how to handle the task run. --- -import {Alert} from "@/components/Alert"; -import {CanIUseThis} from "@/components/CanIUseThis"; -import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; +import { Alert } from "@/components/Alert"; +import { CanIUseThis } from "@/components/CanIUseThis"; +import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; @@ -36,17 +36,18 @@ Via the `context` object, you can access whole Webiny system. The base interface of the `context` object is [Context](https://github.com/webiny/webiny-js/blob/abb442c6d67c97980b8053c5e53db7fb4fec88b4/packages/tasks/src/types.ts#L261), but you can pass your own, which must extend the base interface. You can pass your own Ge interface/type of the `context` variable when defining the task: + ```typescript -import {createTaskDefinition} from "@webiny/tasks"; -import {MyCustomContext} from "./types"; +import { createTaskDefinition } from "@webiny/tasks"; +import { MyCustomContext } from "./types"; const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({context}) { - // context is of type MyCustomContext - await context.yourCustomMethod(); - } + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ context }) { + // context is of type MyCustomContext + await context.yourCustomMethod(); + } }); ``` @@ -55,21 +56,22 @@ const myTask = createTaskDefinition({ The `input` object is the input which was sent to the task when it was triggered. By default, it is of plain object type `Record`, but you can pass your own type/interface when defining the task: + ```typescript -import {createTaskDefinition} from "@webiny/tasks"; -import {MyCustomContext} from "./types"; +import { createTaskDefinition } from "@webiny/tasks"; +import { MyCustomContext } from "./types"; interface MyCustomInput { - myCustomProperty: string; + myCustomProperty: string; } const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({input}) { - // input is of type MyCustomInput - const {myCustomProperty} = input; - } + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ input }) { + // input is of type MyCustomInput + const { myCustomProperty } = input; + } }); ``` @@ -77,6 +79,7 @@ const myTask = createTaskDefinition({ The `response` object handles the output from the task run. Available methods are: + - done - continue - error @@ -86,23 +89,24 @@ Available methods are: This method signalizes that the task has finished its job and that the Step Function will finish as well. It also signalizes that task status should be set to `success`. Optional message will be stored as well. + ```typescript -import {createTaskDefinition} from "@webiny/tasks"; +import { createTaskDefinition } from "@webiny/tasks"; const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({response}) { - return response.done("Optional message about the task getting done."); - } + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ response }) { + return response.done("Optional message about the task getting done."); + } }); ``` Interface for the response object is defined [here](https://github.com/webiny/webiny-js/blob/84d28b3e100a0317a0c3d265d06efaea50971cd3/packages/tasks/src/response/abstractions/TaskResponse.ts#L62). + - `message` - optional message you want to store when the task is done - `output` - optional output object you want to store when the task is done - #### The `continue` Method This method signalizes that the task has not finished its job and that the Step Function should continue running the Lambda handler. @@ -115,50 +119,54 @@ The `continue` method accepts a second, optional, parameter via which you can se You can either send a `seconds` property, which takes a number, or a `date` property, which takes a `Date` object. ```typescript -import {createTaskDefinition} from "@webiny/tasks"; +import { createTaskDefinition } from "@webiny/tasks"; const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({input, response}) { - return response.continue({ - ...input, - aChangedInputProperty: 1, - }, - // optional options - { - seconds: 30, // wait 30 seconds before the next task run - date: new Date("2024-02-25T00:00:00Z") // wait until the specified date before the next task run - }); - } + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ input, response }) { + return response.continue( + { + ...input, + aChangedInputProperty: 1 + }, + // optional options + { + seconds: 30, // wait 30 seconds before the next task run + date: new Date("2024-02-25T00:00:00Z") // wait until the specified date before the next task run + } + ); + } }); ``` - If you are setting waiting time for the `continue` method, note that the maximum waiting time is 355 days, which is almost the maximum life time for the AWS Step Function. + If you are setting waiting time for the `continue` method, note that the maximum waiting time is + 355 days, which is almost the maximum life time for the AWS Step Function. #### The `error` Method This method signalizes that the task has finished its job with an error and that the Step Function will finish as well, in an error state. It also signalizes that task status should be set to `error`. + ```typescript -import {createTaskDefinition} from "@webiny/tasks"; +import { createTaskDefinition } from "@webiny/tasks"; const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({response}) { - return response.error({ - message: "Error message", - code: "ERROR_CODE", - data: { - // optional data - } - }); - } + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ response }) { + return response.error({ + message: "Error message", + code: "ERROR_CODE", + data: { + // optional data + } + }); + } }); -```` +``` #### The `abort` Method @@ -168,26 +176,26 @@ When you write your code, you must check if the task was aborted via the `isAbor This is meant to be used while the task has some code which loops continuously, like reading a lot of records from the database, with paginating through the records. ```typescript -import {createTaskDefinition} from "@webiny/tasks"; +import { createTaskDefinition } from "@webiny/tasks"; const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({response, isAborted, input}) { - let dbReadParams = { - ...input, - }; - let result: DbResponse | null = null; - while ((result = await listFromDb(dbReadParams))) { - if (isAborted()) { - return response.abort(); - } - // continue with the loop - } - return response.done(); + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ response, isAborted, input }) { + let dbReadParams = { + ...input + }; + let result: DbResponse | null = null; + while ((result = await listFromDb(dbReadParams))) { + if (isAborted()) { + return response.abort(); + } + // continue with the loop } + return response.done(); + } }); -```` +``` ### The `isCloseToTimeout` Method @@ -195,33 +203,33 @@ The `isCloseToTimeout` method is a method which returns a boolean value, which t This is useful when you have a lot of code to run, or you have some idea that the code will take some time to execute, and you want to check if you have enough time to finish the code execution. ```typescript -import {createTaskDefinition} from "@webiny/tasks"; +import { createTaskDefinition } from "@webiny/tasks"; interface MyInput { - after?: null | undefined; + after?: null | undefined; } const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({response, isCloseToTimeout, input}) { - let dbReadParams = { - ...input, - }; - let result: DbResponse; - while ((result = await listFromDb(dbReadParams))) { - // do your magic... - - // assign the cursor to the after property of the dbReadParams - dbReadParams.after = result.cursor; - if (isCloseToTimeout()) { - // on next iteration, we want to continue with new dbReadParams - return response.continue(dbReadParams); - } - // continue with the loop - } - return response.done(); + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ response, isCloseToTimeout, input }) { + let dbReadParams = { + ...input + }; + let result: DbResponse; + while ((result = await listFromDb(dbReadParams))) { + // do your magic... + + // assign the cursor to the after property of the dbReadParams + dbReadParams.after = result.cursor; + if (isCloseToTimeout()) { + // on next iteration, we want to continue with new dbReadParams + return response.continue(dbReadParams); + } + // continue with the loop } + return response.done(); + } }); ``` @@ -229,9 +237,9 @@ The `isCloseToTimeout` method accepts an optional parameter, which is a number o - As a developer, you are responsible for checking if the task is close to the timeout, and for handling the task run accordingly. +As a developer, you are responsible for checking if the task is close to the timeout, and for handling the task run accordingly. - Webiny provides you with the background task mechanism, but you must handle the task run, and timeouts, yourself. +Webiny provides you with the background task mechanism, but you must handle the task run, and timeouts, yourself. @@ -240,26 +248,26 @@ The `isCloseToTimeout` method accepts an optional parameter, which is a number o The `isAborted` method is a method which returns a boolean value, which tells you if the task was aborted by the user. ```typescript -import {createTaskDefinition} from "@webiny/tasks"; +import { createTaskDefinition } from "@webiny/tasks"; const myTask = createTaskDefinition({ - id: "myPublicTask", - title: "A Task Accessible Via API", - async run({response, isAborted, input}) { - let dbReadParams = { - ...input, - }; - let result: DbResponse | null = null; - while ((result = await listFromDb(dbReadParams))) { - if (isAborted()) { - return response.abort(); - } - // continue with the loop - } - return response.done(); + id: "myPublicTask", + title: "A Task Accessible Via API", + async run({ response, isAborted, input }) { + let dbReadParams = { + ...input + }; + let result: DbResponse | null = null; + while ((result = await listFromDb(dbReadParams))) { + if (isAborted()) { + return response.abort(); + } + // continue with the loop } + return response.done(); + } }); -```` +``` ### The `store` Object @@ -268,6 +276,7 @@ The `store` object is of `ITaskManagerStore` interface type, which is defined [h It is used for storing and retrieving data from the task run, including task logs. Available methods on the object are: + - `getTask` - `getStatus` - `updateTask` @@ -286,10 +295,10 @@ It means that each of the execution iteration gets its own record, which contain - Do not use the `addInfoLog` and `addErrorLog` methods for a lot of logging, or logging large amounts of data. - - The logs are stored in the database as you send them, and if you send a lot of logs, or large logs, you will hammer the database. - - Use the built-in logs only for important information, like starting or finishing a part of a task. +Do not use the `addInfoLog` and `addErrorLog` methods for a lot of logging, or logging large amounts of data. + +The logs are stored in the database as you send them, and if you send a lot of logs, or large logs, you will hammer the database. + +Use the built-in logs only for important information, like starting or finishing a part of a task. diff --git a/docs/developer-docs/5.x/core-development-concepts/background-tasks/what-to-be-careful-about.mdx b/docs/developer-docs/5.x/core-development-concepts/background-tasks/what-to-be-careful-about.mdx index 5a0d20b06..2c3f92728 100644 --- a/docs/developer-docs/5.x/core-development-concepts/background-tasks/what-to-be-careful-about.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/background-tasks/what-to-be-careful-about.mdx @@ -4,9 +4,9 @@ title: What to be careful about? description: You will learn about Background Tasks, how to create new definitions, how to trigger them and how to handle the task run. --- -import {Alert} from "@/components/Alert"; -import {CanIUseThis} from "@/components/CanIUseThis"; -import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; +import { Alert } from "@/components/Alert"; +import { CanIUseThis } from "@/components/CanIUseThis"; +import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; @@ -43,7 +43,6 @@ Default limit: `500`. To change the limit, see [`maxIterations`](#advanced-definition) parameter when defining the Background Task. - Currently, Background Tasks are not using the Task Token, so they are not resumable. We will be working on the solution for this in the future. diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/environment-variables.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/environment-variables.mdx index 80022f603..6d8d7e1df 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/environment-variables.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/environment-variables.mdx @@ -171,7 +171,7 @@ To manage how long deleted entries are retained within Webiny, developers can co For example, setting `WEBINY_TRASH_BIN_RETENTION_PERIOD_DAYS=7` will preserve deleted entries for 7 days. -Adjusting this variable allows developers to customize the retention period according to their needs. +Adjusting this variable allows developers to customize the retention period according to their needs. Setting `WEBINY_TRASH_BIN_RETENTION_PERIOD_DAYS=0` will apply the default retention period of 90 days. diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/extensions.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/extensions.mdx index 915cf7463..ffa51761d 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/extensions.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/extensions.mdx @@ -9,7 +9,7 @@ import webinyExtensionCommand from "./assets/webiny-extension-command.png"; - - what extensions are and how they can be used to extend Webiny's functionality. +- what extensions are and how they can be used to extend Webiny's functionality. @@ -41,7 +41,7 @@ Once all the questions are answered, the command creates the base code for the n - There are a couple of cases where the extension code is placed outside of the `extensions` folder. For example, when [modifying cloud infrastructure](/{version}/infrastructure/basics/modify-cloud-infrastructure), the code is placed in different `webiny.application.ts` files, located in the `apps` folder. +There are a couple of cases where the extension code is placed outside of the `extensions` folder. For example, when [modifying cloud infrastructure](/{version}/infrastructure/basics/modify-cloud-infrastructure), the code is placed in different `webiny.application.ts` files, located in the `apps` folder. diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/logger.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/logger.mdx index c142a9101..7ab18ff9a 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/logger.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/logger.mdx @@ -4,7 +4,7 @@ title: Logger description: Learn about Logger which we internally use --- -import {Alert} from "@/components/Alert"; +import { Alert } from "@/components/Alert"; @@ -14,7 +14,8 @@ import {Alert} from "@/components/Alert"; - This feature will change in the future. We cannot guarantee the data integrity after the system upgrade (in minor version, eg. 5.42.x to 5.43.0) + This feature will change in the future. We cannot guarantee the data integrity after the system + upgrade (in minor version, eg. 5.42.x to 5.43.0) ## Overview @@ -28,33 +29,34 @@ For Logger to work, we deploy a new DynamoDB table called `webiny-logs`. To use the Logger in your project, you can access it from the Webiny context. There are multiple levels of logging available: - - `debug` - - `notice` - - `info` - - `warn` - - `error` + +- `debug` +- `notice` +- `info` +- `warn` +- `error` When you want to log something, you can use the following code: ```typescript -import type {Context} from "./types"; -import {ContextPlugin} from "@webiny/handler-aws"; +import type { Context } from "./types"; +import { ContextPlugin } from "@webiny/handler-aws"; const myCustomPlugin = new ContextPlugin(async context => { - // some custom code - try { - await someAsyncFunction(); - } catch(ex) { - const log = context.logger.withSource("where-did-the-log-come-from"); - // it will log the message with the source "where-did-the-log-come-from" - // and the level "error" - log.error({ - message: "Something is wrong with my custom code!", - error: ex - }); - // maybe rethrow it? - throw ex; - } + // some custom code + try { + await someAsyncFunction(); + } catch (ex) { + const log = context.logger.withSource("where-did-the-log-come-from"); + // it will log the message with the source "where-did-the-log-come-from" + // and the level "error" + log.error({ + message: "Something is wrong with my custom code!", + error: ex + }); + // maybe rethrow it? + throw ex; + } }); ``` @@ -65,8 +67,9 @@ This is something you can use to filter logs later. ## How to Access Logger Logs? There are two ways to access the logs: - - directly from the DynamoDB table `webiny-logs` - - using the GraphQL API + +- directly from the DynamoDB table `webiny-logs` +- using the GraphQL API ### DynamoDB Table @@ -82,8 +85,8 @@ interface Log { type: string; // this is the data that was logged - it's always compressed data: { - compression: "GZIP", - value: string// compressed value + compression: "GZIP"; + value: string; // compressed value }; } ``` @@ -94,16 +97,15 @@ The data is always compressed using GZIP, so you must decompress it before you c You can also access the logs using the GraphQL API on `/graphql` endpoint. There are multiple queries and mutations available: - - `listLogs` - to list all logs - - `getLog` - to get a single log - - `deleteLog` - to delete a single - - `deleteLogs` - to delete multiple logs - - `pruneLogs` - to delete all logs older than 60 seconds +- `listLogs` - to list all logs +- `getLog` - to get a single log +- `deleteLog` - to delete a single +- `deleteLogs` - to delete multiple logs +- `pruneLogs` - to delete all logs older than 60 seconds - - For detailed information on how to use the GraphQL API, check out the API Playground in your Webiny Admin UI. - + For detailed information on how to use the GraphQL API, check out the API Playground in your + Webiny Admin UI. #### List Logs @@ -114,15 +116,11 @@ To list all logs, you can use the `listLogs` query. Here is an example of the qu query ListLogs { logs { listLogs( - where: { - tenant: "root", - source:"myCustomSource", - type: error - }, - sort: DESC, - limit: 100, - after: "cursorFromPreviousRequest" -) { + where: { tenant: "root", source: "myCustomSource", type: error } + sort: DESC + limit: 100 + after: "cursorFromPreviousRequest" + ) { data { id type @@ -144,6 +142,7 @@ query ListLogs { } } ``` + All arguments in `listTags` query are optional. You can filter the logs by `tenant`, `source` and `type`. Or you can just list all logs, without any filtering applied. diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/project-deployment.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/project-deployment.mdx index 8b007f949..02ceb831d 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/project-deployment.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/project-deployment.mdx @@ -247,7 +247,7 @@ C:\my-new-project\.webiny\pulumi-cli\win32\pulumi\credentials.json ### Can I deploy Webiny on personal server or platforms like Docker or Digital Ocean? -The short answer is no. +The short answer is no. Webiny cannot be deployed on a personal server or platforms like Docker or Digital Ocean because Webiny is a serverless application, meaning it doesn't need a server for installation. Instead, it's built on top of serverless infrastructure like AWS Lambda and DynamoDB to enable scalability and reliability. diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/routes-and-events.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/routes-and-events.mdx index 6908071d0..0ab7d09e8 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/routes-and-events.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/routes-and-events.mdx @@ -4,10 +4,9 @@ title: Routes and Events description: You will learn about new handlers for routes and events, and how to add new routes and event definitions. --- - -import {Alert} from "@/components/Alert"; -import {CanIUseThis} from "@/components/CanIUseThis"; -import {WhatYouWillLearn} from "@/components/WhatYouWillLearn"; +import { Alert } from "@/components/Alert"; +import { CanIUseThis } from "@/components/CanIUseThis"; +import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/tools-and-libraries.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/tools-and-libraries.mdx index 8cd5152ae..46a4cc7cb 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/tools-and-libraries.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/tools-and-libraries.mdx @@ -13,7 +13,7 @@ import logos from "./assets/tools-libraries/logos.png"; - + Webiny is an open-source project, and as such, it is also relying on other open-source software and tools, to bring the best possible experience and increase development productivity. diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/user-project-upgrade.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/user-project-upgrade.mdx index 778420d49..3a5a531a3 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/user-project-upgrade.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/user-project-upgrade.mdx @@ -4,7 +4,7 @@ title: User Project Upgrade description: Learn about how to do a project upgrade --- -import {Alert} from "@/components/Alert"; +import { Alert } from "@/components/Alert"; @@ -20,7 +20,8 @@ As a part of our upgrade process, we provide a way to update your codebase on ea To start the project upgrade use the `yarn webiny upgrade` command. - The upgrade process can only update one minor version at a time. So you can upgrade from 5.40.x to 5.41.x, but not from 5.40.x to 5.42.x. + The upgrade process can only update one minor version at a time. So you can upgrade from 5.40.x to + 5.41.x, but not from 5.40.x to 5.42.x. ## Steps @@ -32,6 +33,7 @@ There might even be some breaking changes, so we need to notify the user about t ### 1. Notify the User about Breaking Changes The first step is to notify the user about breaking changes: + ```text webiny warning: Note that Webiny 5.xx.x introduces potential breaking changes! Before continuing, please review the upgrade guide located at https://webiny.link/upgrade/5.xx.x. @@ -52,7 +54,7 @@ Next step, which will always be executed, is to check if the user project depend This does not mean that we use newer versions of the dependencies than our users do in their projects. We ship Webiny with the dependencies that are working with our code. If it is possible, our users should keep their dependencies in sync with Webiny ones to avoid any unexpected problems. - + ```text diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/watch-command.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/watch-command.mdx index b857a243c..8d5fb35cb 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/watch-command.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/watch-command.mdx @@ -45,7 +45,7 @@ yarn webiny watch website --env dev - Extensions are the primary way to develop on top Webiny and extend it. To learn more, check out the [Extensions](/{version}/core-development-concepts/basics/extensions) article. +Extensions are the primary way to develop on top Webiny and extend it. To learn more, check out the [Extensions](/{version}/core-development-concepts/basics/extensions) article. @@ -56,30 +56,26 @@ yarn webiny watch website --env dev When it comes to frontend development (**Admin** and **Website** project applications), the watch command offers an experience similar to other existing frontend development solutions out there. Once started, the watch command: 1. spins up a local development server that serves your application -2. the application is automatically rebuilt and refreshed in the browser whenever a code change is detected +2. the application is automatically rebuilt and refreshed in the browser whenever a code change is detected - Note that you must have the **API** project application already deployed before watching **Admin** and **Website** project applications. This is because of the fact that these applications depend on Webiny's backend APIs to work as expected. +Note that you must have the **API** project application already deployed before watching **Admin** and **Website** project applications. This is because of the fact that these applications depend on Webiny's backend APIs to work as expected. - ### Backend Development When it comes to backend development (**API** project application), the watch command doesn't spin up a local development server, but it watches for changes and continuously deploys them the cloud (AWS Lambda). This approach sort of emulates the local development server experience, because changes are automatically reflected in the cloud (as soon as they are deployed). - With the 5.41.0 release and with the introduction of the New Watch Command (Local AWS Lambda Development), we've made significant improvements to the way backend development is done. The feature is still in beta, but we encourage you to try it out and provide feedback. - +With the 5.41.0 release and with the introduction of the New Watch Command (Local AWS Lambda Development), we've made significant improvements to the way backend development is done. The feature is still in beta, but we encourage you to try it out and provide feedback. + ## FAQ ### Can I run Webiny fully locally, without deploying it to AWS? -Because Webiny is built on top of AWS and its proprietary services, it's not possible to run Webiny fully locally. At the very least, you need to deploy the **API** project application to AWS, because it's the backbone of the entire system. The **Admin** and **Website** project applications can be run locally, but they depend on the **API** project application to work as expected. - - - +Because Webiny is built on top of AWS and its proprietary services, it's not possible to run Webiny fully locally. At the very least, you need to deploy the **API** project application to AWS, because it's the backbone of the entire system. The **Admin** and **Website** project applications can be run locally, but they depend on the **API** project application to work as expected. diff --git a/docs/developer-docs/5.x/core-development-concepts/basics/webiny-cli.mdx b/docs/developer-docs/5.x/core-development-concepts/basics/webiny-cli.mdx index 266edbaaf..69e904e8b 100644 --- a/docs/developer-docs/5.x/core-development-concepts/basics/webiny-cli.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/basics/webiny-cli.mdx @@ -10,8 +10,8 @@ import webinyAboutCommand from "./assets/webiny-about-command.png"; - - what is the Webiny CLI - - what are the commonly used commands +- what is the Webiny CLI +- what are the commonly used commands @@ -25,10 +25,11 @@ Additionally, the Webiny CLI is pluggable, meaning you can easily create your ow - For a full list of commands, in your terminal of choice, make sure to run: - ```bash - yarn webiny --help - ``` +For a full list of commands, in your terminal of choice, make sure to run: + +```bash +yarn webiny --help +``` @@ -56,7 +57,7 @@ The `--env` argument is required. - For more hands-on information on the above listed commands, please visit the [Deploy Your Project](/{version}/core-development-concepts/basics/project-deployment) and [Destroy Cloud Infrastructure](/{version}/infrastructure/basics/destroy-cloud-infrastructure) guides. +For more hands-on information on the above listed commands, please visit the [Deploy Your Project](/{version}/core-development-concepts/basics/project-deployment) and [Destroy Cloud Infrastructure](/{version}/infrastructure/basics/destroy-cloud-infrastructure) guides. @@ -66,7 +67,7 @@ Provides a way to execute Pulumi specific commands directly via the Pulumi CLI. - For more information, please visit the [Execute Pulumi Commands](/{version}/infrastructure/pulumi-iac/execute-pulumi-commands) guide. +For more information, please visit the [Execute Pulumi Commands](/{version}/infrastructure/pulumi-iac/execute-pulumi-commands) guide. @@ -78,13 +79,13 @@ Returns Pulumi stack output for the specified project application and environmen Here are the most commonly used development-related commands. -#### `yarn webiny watch APP --env ENV` +#### `yarn webiny watch APP --env ENV` Starts local development session for the specified project application, in the specified environment. - For more information, please visit the [Use Watch Command](/{version}/core-development-concepts/basics/watch-command) guide. +For more information, please visit the [Use Watch Command](/{version}/core-development-concepts/basics/watch-command) guide. @@ -92,7 +93,7 @@ Starts local development session for the specified project application, in the s Builds the specified project application, for the specified environment. -#### `yarn webiny extension` +#### `yarn webiny extension` Makes it easy to start developing new Webiny extensions. To learn more about extensions, please visit the [Extensions](/{version}/core-development-concepts/basics/extensions) article. @@ -118,7 +119,7 @@ Completely disables collection of anonymous usage information. - By default, Webiny collects anonymous usage information, which is exclusively used for improving the product and understanding usage patterns. Please take a look at our [Telemetry](https://www.webiny.com/telemetry/) page for more information on this subject. +By default, Webiny collects anonymous usage information, which is exclusively used for improving the product and understanding usage patterns. Please take a look at our [Telemetry](https://www.webiny.com/telemetry/) page for more information on this subject. diff --git a/docs/developer-docs/5.x/core-development-concepts/ci-cd/testing.mdx b/docs/developer-docs/5.x/core-development-concepts/ci-cd/testing.mdx index d76327c35..f3f23d450 100644 --- a/docs/developer-docs/5.x/core-development-concepts/ci-cd/testing.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/ci-cd/testing.mdx @@ -61,6 +61,7 @@ Static tests are used to check issues in our code, without actually executing it All of the mentioned tools (and some others) are already included and configured for you in every Webiny project. + Usually, these tests are reasonably fast to run, and sometimes, even the fastest. diff --git a/docs/developer-docs/5.x/core-development-concepts/development/local-development.mdx b/docs/developer-docs/5.x/core-development-concepts/development/local-development.mdx index 989305db4..3382e9aa6 100644 --- a/docs/developer-docs/5.x/core-development-concepts/development/local-development.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/development/local-development.mdx @@ -15,7 +15,7 @@ import { Alert } from "@/components/Alert"; ## Overview -Serverless development has slightly different principles from traditional application development. In the traditional development process, developers typically develop and test their project locally before deploying it to a server. However, in the serverless world, this process is slightly different. +Serverless development has slightly different principles from traditional application development. In the traditional development process, developers typically develop and test their project locally before deploying it to a server. However, in the serverless world, this process is slightly different. In this article, we will look at how to do local development in Webiny, a serverless CMS. If you are absolutely beginner to serverless, we will recommend you to read [this article](https://www.webiny.com/knowledge-base/serverless). @@ -37,7 +37,7 @@ As previously mentioned, for the API and infrastructure related portion of the d Webiny utilizes a variety of AWS serverless services, such as AWS Lambda, Amazon DynamoDB, Amazon S3, etc. -And while simulating Lambda locally is not that hard (although it's still not trivial), it's still hard to emulate cloud native mechanisms, like reacting to Amazon S3 objects-related events, Amazon Event Bridge events, emulating services like Amazon Cognito, Amazon SQS, and more. +And while simulating Lambda locally is not that hard (although it's still not trivial), it's still hard to emulate cloud native mechanisms, like reacting to Amazon S3 objects-related events, Amazon Event Bridge events, emulating services like Amazon Cognito, Amazon SQS, and more. With that in mind, we don't recommend using tools like [LocalStack](https://www.localstack.cloud/). Still, if you find it challenging to work with any part of local development, get in touch with us on our [Community Slack](https://www.webiny.com/slack). It will help us offer some advice or improve the local development experience. @@ -48,7 +48,7 @@ Webiny supports [two database setups](/{version}/architecture/introduction#diffe 1. Amazon DynamoDB 2. Amazon DynamoDB + Amazon OpenSearch -The Amazon DynamoDB database setup fully utilizes serverless services, which means it fully follows pay-per-use pricing. This means that for development, the cost is typically minimal or free. +The Amazon DynamoDB database setup fully utilizes serverless services, which means it fully follows pay-per-use pricing. This means that for development, the cost is typically minimal or free. On the other hand, with the Amazon DynamoDB + Amazon OpenSearch version, [Amazon OpenSearch Service](https://aws.amazon.com/opensearch-service/) is the only non-serverless service used by Webiny, which is not priced on a pay-per-use basis. This means that you will be charged for the time the service is running, regardless of whether you are using it or not. diff --git a/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/extend-graphql-api.mdx b/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/extend-graphql-api.mdx index d657d4b97..21732cdd5 100644 --- a/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/extend-graphql-api.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/extend-graphql-api.mdx @@ -21,7 +21,7 @@ In this article, we explain how to extend Webiny's main GraphQL API. Note that W - To learn more about the Headless CMS GraphQL API, different API types, support for multiple locales, and more, make sure to check out the [Headless CMS GraphQL API](/{version}/headless-cms/basics/graphql-api) key topic. +To learn more about the Headless CMS GraphQL API, different API types, support for multiple locales, and more, make sure to check out the [Headless CMS GraphQL API](/{version}/headless-cms/basics/graphql-api) key topic. @@ -33,14 +33,16 @@ In this article, we explain how to extend Webiny's main GraphQL API. Note that W dependencies={"@webiny/api-serverless-cms"} scaffoldCommandExtraInfo={ <> - In the example below, we'll be using the createGraphQLSchemaPlugin plugin factory from the @webiny/api-serverless-cms package, so we've included it in the list of dependencies. + In the example below, we'll be using the createGraphQLSchemaPlugin plugin factory + from the @webiny/api-serverless-cms package, so we've included it in the list of + dependencies. } /> ## Extending the Main GraphQL API -Let's extend Webiny's main GraphQL API with the new `listBooks` query. +Let's extend Webiny's main GraphQL API with the new `listBooks` query. To do this, we use the `createGraphQLSchemaPlugin` plugin factory from the `@webiny/api-serverless-cms` package: @@ -48,37 +50,37 @@ To do this, we use the `createGraphQLSchemaPlugin` plugin factory from the `@web import { createGraphQLSchemaPlugin } from "@webiny/api-serverless-cms"; export const createExtension = () => { - return [ - createGraphQLSchemaPlugin({ - typeDefs: /* GraphQL */ ` - type Book { - title: String - description: String - } - extend type Query { - # Returns a list of all users. - listBooks: [Book] - } - `, - resolvers: { - Query: { - listBooks: async (_, args, context) => { - // In a real life application, these would be loaded from a database. - const books = [ - { title: "First book", description: "This is the first book." }, - { title: "Second book", description: "This is the second book." } - ]; - - return books; - } - } - } - }) - ]; + return [ + createGraphQLSchemaPlugin({ + typeDefs: /* GraphQL */ ` + type Book { + title: String + description: String + } + extend type Query { + # Returns a list of all users. + listBooks: [Book] + } + `, + resolvers: { + Query: { + listBooks: async (_, args, context) => { + // In a real life application, these would be loaded from a database. + const books = [ + { title: "First book", description: "This is the first book." }, + { title: "Second book", description: "This is the second book." } + ]; + + return books; + } + } + } + }) + ]; }; ``` -With this code in place, we should be able to run the following GraphQL query: +With this code in place, we should be able to run the following GraphQL query: ```graphql { @@ -89,19 +91,23 @@ With this code in place, we should be able to run the following GraphQL query: } ``` - - The easiest way to test it is by using the [API Playground](https://www.webiny.com/docs/{version}/admin-area/basics/api-playground) in the Admin app: -Testing the listBooks query in the API Playground} /> + + Testing the listBooks query in the API Playground + + } +/> - Run `yarn webiny watch admin --env ENVIRONMENT_NAME` to start the Admin app in the development mode. Replace `ENVIRONMENT_NAME` with the name of the environment you're working on. +Run `yarn webiny watch admin --env ENVIRONMENT_NAME` to start the Admin app in the development mode. Replace `ENVIRONMENT_NAME` with the name of the environment you're working on. - ## Additional Examples - [Headless CMS - Extend the GraphQL API](/{version}/headless-cms/extending/extend-graphql-api) diff --git a/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/how-to-customize-elasticsearch-index.mdx b/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/how-to-customize-elasticsearch-index.mdx index fecd8c080..a216915b0 100644 --- a/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/how-to-customize-elasticsearch-index.mdx +++ b/docs/developer-docs/5.x/core-development-concepts/extending-and-customizing/how-to-customize-elasticsearch-index.mdx @@ -101,9 +101,8 @@ We intended it to be used to check if the index configuration can be applied giv Index name can contain dashes (-), underscores (\_), numbers (0-9) and english letters (a-z). It is created by our configuration method, so you do not need to worry about that. - Note that each tenant, locale and CMS model combination creates it's own index. - - Page Builder, Form Builder, and some other apps, always create a single index per tenant and locale combination. + Note that each tenant, locale and CMS model combination creates it's own index. Page Builder, Form + Builder, and some other apps, always create a single index per tenant and locale combination. For example, creating an Article model in `root` tenant, `en-US` locale will create an index with the name: `root-headless-cms-en-us-articles`. @@ -127,57 +126,58 @@ This is due to the nature of the Elasticsearch/OpenSearch field mapping and conf ###### **OpenSearch** To make the `OpenSearch` share indexes, you need to add the following configuration to your `webiny.project.ts` file: + ```typescript import { createCoreApp } from "@webiny/serverless-cms-aws"; export default createCoreApp({ - pulumiResourceNamePrefix: "wby-", - openSearch: { - sharedIndexes: true - } + pulumiResourceNamePrefix: "wby-", + openSearch: { + sharedIndexes: true + } }); ``` - ```typescript import { createApiApp } from "@webiny/serverless-cms-aws"; export default createApiApp({ - pulumiResourceNamePrefix: "wby-", - openSearch: { - sharedIndexes: true - } + pulumiResourceNamePrefix: "wby-", + openSearch: { + sharedIndexes: true + } }); ``` ###### **Elasticsearch** To make the `Elasticesearch` share indexes, you need to add the following configuration to your `webiny.project.ts` file: + ```typescript import { createCoreApp } from "@webiny/serverless-cms-aws"; export default createCoreApp({ - pulumiResourceNamePrefix: "wby-", - elasticSearch: { - sharedIndexes: true - } + pulumiResourceNamePrefix: "wby-", + elasticSearch: { + sharedIndexes: true + } }); ``` - ```typescript import { createApiApp } from "@webiny/serverless-cms-aws"; export default createApiApp({ - pulumiResourceNamePrefix: "wby-", - elasticSearch: { - sharedIndexes: true - } + pulumiResourceNamePrefix: "wby-", + elasticSearch: { + sharedIndexes: true + } }); ``` - You should `NOT` change the `sharedIndexes` configuration after the initial deployment. If you do, you will lose all the data. + You should `NOT` change the `sharedIndexes` configuration after the initial deployment. If you do, + you will lose all the data. ##### Index Prefix diff --git a/docs/developer-docs/5.x/enterprise/aacl/folder-level-permissions.mdx b/docs/developer-docs/5.x/enterprise/aacl/folder-level-permissions.mdx index 80213cae6..5582fbc4a 100644 --- a/docs/developer-docs/5.x/enterprise/aacl/folder-level-permissions.mdx +++ b/docs/developer-docs/5.x/enterprise/aacl/folder-level-permissions.mdx @@ -87,5 +87,3 @@ This means that existing security roles and security teams are still the first t ### Can I Use Folder Level Permissions with API Tokens? At the moment, the answer is no. API tokens are not subject to folder level permissions. They will always have access to all folders. - - diff --git a/docs/developer-docs/5.x/enterprise/aacl/private-files.mdx b/docs/developer-docs/5.x/enterprise/aacl/private-files.mdx index 33cdbad84..13368ebee 100644 --- a/docs/developer-docs/5.x/enterprise/aacl/private-files.mdx +++ b/docs/developer-docs/5.x/enterprise/aacl/private-files.mdx @@ -17,20 +17,19 @@ import { Alert } from "@/components/Alert"; ## Overview -With the 5.39.0 release, Webiny received a feature that enables you to control who can see and access files inside File Manager. This feature is designed to protect highly sensitive files from leaking or being publicly shared. +With the 5.39.0 release, Webiny received a feature that enables you to control who can see and access files inside File Manager. This feature is designed to protect highly sensitive files from leaking or being publicly shared. {"Private -With this feature, after uploading a file inside the File Manager, users can set the Access Control setting on the newly uploaded file. The Access Control setting can take two values: +With this feature, after uploading a file inside the File Manager, users can set the Access Control setting on the newly uploaded file. The Access Control setting can take two values: + - **Public** -> Anyone on the public internet can access the file given the link to the file - **Private** -> Only registered Webiny Admin users can access and view the file Once a file is marked as `Private` even if a direct link to the file is shared with 3rd party users, they will not be able to access the file. The feature works regardless of the file type. You can protect images, documents, videos or any other file type. -It's important to note that public files are automatically cached on the CDN and in the browser. In case you switch a file from public to private, the CDN cache will be flushed, but users who have previously accessed the file might still have it in their browser cache. + It's important to note that public files are automatically cached on the CDN and in the browser. + In case you switch a file from public to private, the CDN cache will be flushed, but users who + have previously accessed the file might still have it in their browser cache. - - - - diff --git a/docs/developer-docs/5.x/enterprise/aacl/teams.mdx b/docs/developer-docs/5.x/enterprise/aacl/teams.mdx index 3e53f6f3f..e25d940b5 100644 --- a/docs/developer-docs/5.x/enterprise/aacl/teams.mdx +++ b/docs/developer-docs/5.x/enterprise/aacl/teams.mdx @@ -33,7 +33,7 @@ And although this approach might work for some users, it can quickly become cumb This feature is especially useful for larger organizations, where it's common to have multiple teams working on different projects. Also, it's a great way to simplify the process of managing permissions for multiple users, as you can simply assign a role to a team, instead of assigning it to each individual user. -Additionally, the Teams feature can be used in conjunction with [Folder Level Permissions (FLP)](/{version}/enterprise/aacl/folder-level-permissions) to further enhance the security of your Webiny project. Instead of just being able to define folder level permissions for individual users, you can now define them for teams as well. Make sure to check out the FLP documentation to learn more about this feature. +Additionally, the Teams feature can be used in conjunction with [Folder Level Permissions (FLP)](/{version}/enterprise/aacl/folder-level-permissions) to further enhance the security of your Webiny project. Instead of just being able to define folder level permissions for individual users, you can now define them for teams as well. Make sure to check out the FLP documentation to learn more about this feature. ## Enabling Teams and Feature Overview diff --git a/docs/developer-docs/5.x/enterprise/advanced-tenant-management.mdx b/docs/developer-docs/5.x/enterprise/advanced-tenant-management.mdx index 0bb29101a..ba220430b 100644 --- a/docs/developer-docs/5.x/enterprise/advanced-tenant-management.mdx +++ b/docs/developer-docs/5.x/enterprise/advanced-tenant-management.mdx @@ -111,18 +111,14 @@ The way we do it is, we decorate the `usePermission` hook, and we always return - This customization is optional. If you _do_ want to allow Companies to be published, you will have to make some changes on the API side of things. There are pros and cons to both approaches, so do reach out to us on our [community slack](https://www.webiny.com/slack) and we can discuss the best way forward for your project. +This customization is optional. If you _do_ want to allow Companies to be published, you will have to make some changes on the API side of things. There are pros and cons to both approaches, so do reach out to us on our [community slack](https://www.webiny.com/slack) and we can discuss the best way forward for your project. ## Tenant Theming -Each Company is allowed to have its own brand attributes, like colors, logo, etc. The [ApplyTenantTheme](https://github.com/webiny/webiny-examples/blob/master/cms-tenant-management/5.40.x/extensions/admin/src/ApplyCompanyTheme.tsx) component demonstrates how you can programmatically modify the Admin app theme, when a user is accessing the Admin app for a particular Company. +Each Company is allowed to have its own brand attributes, like colors, logo, etc. The [ApplyTenantTheme](https://github.com/webiny/webiny-examples/blob/master/cms-tenant-management/5.40.x/extensions/admin/src/ApplyCompanyTheme.tsx) component demonstrates how you can programmatically modify the Admin app theme, when a user is accessing the Admin app for a particular Company. In our demo, we're only showing customization of colors and logo, but following this approach, you can customize pretty much anything related to the UI: navigation menu items, dashboard, columns, even functional plugins. - - - - diff --git a/docs/developer-docs/5.x/enterprise/auth0-integration.mdx b/docs/developer-docs/5.x/enterprise/auth0-integration.mdx index 0ddafb76b..a6b40e9ce 100644 --- a/docs/developer-docs/5.x/enterprise/auth0-integration.mdx +++ b/docs/developer-docs/5.x/enterprise/auth0-integration.mdx @@ -289,15 +289,14 @@ export const App = () => { }; ``` -Next time the system renders the **NotAuthorizedError** component, your decorator will intercept the output, and render your custom UI. +Next time the system renders the **NotAuthorizedError** component, your decorator will intercept the output, and render your custom UI. ## 8) Tenant Access Control (Optional) -If you want to restrict which identity can access individual tenants, you can do it in an optional `canAccessTenant` function. +If you want to restrict which identity can access individual tenants, you can do it in an optional `canAccessTenant` function. Let's imagine you want to restrict tenant access using the Auth0 client ID (it is stored in the idToken, in the `aud` claim). First you would assign the client ID value to your identity, and then, in the `canAccessTenant`, you would perform your checks: - ```diff-ts apps/api/graphql/src/security.ts createAuth0({ domain: String(process.env.AUTH0_DOMAIN), diff --git a/docs/developer-docs/5.x/enterprise/cognito-federation.mdx b/docs/developer-docs/5.x/enterprise/cognito-federation.mdx index 21ff08c8c..056332b2e 100644 --- a/docs/developer-docs/5.x/enterprise/cognito-federation.mdx +++ b/docs/developer-docs/5.x/enterprise/cognito-federation.mdx @@ -339,7 +339,7 @@ Open your `apps/api/graphql/src/security.ts` file, and update the existing `cogn identityType: "admin", + getIdentity({ identity, token }) { + const federatedIdentity = Boolean(token.identities); -+ ++ + return { + ...identity, + group: federatedIdentity ? "full-access" : undefined @@ -412,7 +412,7 @@ To pull this value from the Core app, and inject it into the Admin app, there's createAdminAppConfig, + configureAdminCognitoUserPoolDomain } from "@webiny/serverless-cms-aws"; - + export default createAdminAppConfig(modifier => { + configureAdminCognitoUserPoolDomain(modifier); }); @@ -504,7 +504,7 @@ To only use the sign-in UI provided by Webiny, you only need to swap the `Cognit } from "@webiny/app-admin-users-cognito"; + import { ButtonPrimary } from "@webiny/ui/Button"; import "./App.scss"; - + + const cognitoConfig: CreateAuthenticationConfig = { + oauth: { + domain: String(process.env.REACT_APP_USER_POOL_DOMAIN), @@ -520,7 +520,7 @@ To only use the sign-in UI provided by Webiny, you only need to swap the `Cognit + } + ] + }; - + + // Decorate the SignIn view, and customize props. + const SignInDecorator = Components.SignIn.createDecorator(Original => { + return function SignIn(props) { @@ -534,7 +534,7 @@ To only use the sign-in UI provided by Webiny, you only need to swap the `Cognit + ); + }; + }); - + export const App = () => { return ( diff --git a/docs/developer-docs/5.x/enterprise/multi-tenancy.mdx b/docs/developer-docs/5.x/enterprise/multi-tenancy.mdx index 0e28a6a38..a45550e09 100644 --- a/docs/developer-docs/5.x/enterprise/multi-tenancy.mdx +++ b/docs/developer-docs/5.x/enterprise/multi-tenancy.mdx @@ -8,7 +8,6 @@ import { Alert } from "@/components/Alert"; - - how to enable multi-tenancy in the existing Webiny project diff --git a/docs/developer-docs/5.x/file-manager/extending/confirmation-dialog-for-folder-drag-and-drop.mdx b/docs/developer-docs/5.x/file-manager/extending/confirmation-dialog-for-folder-drag-and-drop.mdx index 59614f2e2..885824bf7 100644 --- a/docs/developer-docs/5.x/file-manager/extending/confirmation-dialog-for-folder-drag-and-drop.mdx +++ b/docs/developer-docs/5.x/file-manager/extending/confirmation-dialog-for-folder-drag-and-drop.mdx @@ -10,12 +10,11 @@ import { WhatYouWillLearn } from "@/components/WhatYouWillLearn"; import folderDropConfirmation from "./assets/fm-folder-drop-confirmation.gif"; - - - how to prevent accidental folder moves +- how to prevent accidental folder moves @@ -28,13 +27,13 @@ import { FileManagerViewConfig } from "@webiny/app-file-manager"; const { Browser } = FileManagerViewConfig; - - + +; ``` -When active, this feature prompts users to confirm their action before moving a folder from one location to another. - - - - +When active, this feature prompts users to confirm their action before moving a folder from one location to another. + diff --git a/docs/developer-docs/5.x/file-manager/extending/customize-file-actions.mdx b/docs/developer-docs/5.x/file-manager/extending/customize-file-actions.mdx index 332d5f3e7..89ef689b4 100644 --- a/docs/developer-docs/5.x/file-manager/extending/customize-file-actions.mdx +++ b/docs/developer-docs/5.x/file-manager/extending/customize-file-actions.mdx @@ -20,19 +20,23 @@ import fmFilesGridDetailsActionPlay from "./assets/fm-files-details-action-play. ## Overview -Files in File Manager can have different actions associated with them. These actions are displayed in the file browser (when hovering over a file) and file details panel. +Files in File Manager can have different actions associated with them. These actions are displayed in the file browser (when hovering over a file) and file details panel. -On top of the built-in actions, it's also possible to add custom actions, which can be displayed for all types of files (global actions) or for specific file types. +On top of the built-in actions, it's also possible to add custom actions, which can be displayed for all types of files (global actions) or for specific file types. In this guide, we show how to add custom actions for both scenarios. ## Getting Started - + ## Global File Actions @@ -47,37 +51,37 @@ import { ReactComponent as PlayIcon } from "@material-design-icons/svg/round/pla const { Browser, FileDetails } = FileManagerViewConfig; const GridItemAction = () => { - return ( - } - onAction={() => { - alert("In the future, this will be useful."); - }} - /> - ); + return ( + } + onAction={() => { + alert("In the future, this will be useful."); + }} + /> + ); }; const FileDetailsAction = () => { - return ( - } - onAction={() => { - alert("In the future, this will be useful."); - }} - /> - ); + return ( + } + onAction={() => { + alert("In the future, this will be useful."); + }} + /> + ); }; export const Extension = () => { - return ( - <> - - } /> - } /> - - - ); + return ( + <> + + } /> + } /> + + + ); }; ``` @@ -91,7 +95,7 @@ With this code in place, the **Play Video** action will be displayed for all typ ## File Type Specific File Actions -Besides global actions, we can also introduce actions for specific file types. +Besides global actions, we can also introduce actions for specific file types. To do that, in the `GridItemAction` and `FileDetailsAction` components, we use the `useFile` hook to get the current file and check its `type` property. If the file type is not `video/quicktime`, we return `null` and the action is not displayed. diff --git a/docs/developer-docs/5.x/file-manager/extending/customize-file-list-actions.mdx b/docs/developer-docs/5.x/file-manager/extending/customize-file-list-actions.mdx index 34ce56829..eb4d81c02 100644 --- a/docs/developer-docs/5.x/file-manager/extending/customize-file-list-actions.mdx +++ b/docs/developer-docs/5.x/file-manager/extending/customize-file-list-actions.mdx @@ -100,19 +100,15 @@ export const CopyFolderData = () => { You can pass the custom component to the folder action definition using the `element` prop. ```tsx - - } - /> - + + } /> + ``` This is the whole process of registering a new folder action element. - ### Discover Folder Actions This section demonstrates how you can discover the names of existing folder actions. This is important for further sections on positioning, removing, and replacing existing actions. @@ -127,11 +123,7 @@ To position your custom folder action before or after an existing action, you ca ```tsx - } - before={"delete"} - /> + } before={"delete"} /> ``` @@ -153,10 +145,7 @@ To replace an existing action with a new action element, you need to reference a ```tsx - A new action!} - /> + A new action!} /> ``` @@ -200,19 +189,15 @@ export const CopyFileData = () => { You can pass the custom component to the file action definition using the `element` prop. ```tsx - - } - /> - + + } /> + ``` This is the whole process of registering a new file action element. - ### Discover File Actions This section demonstrates how you can discover the names of existing file actions. This is important for further sections on positioning, removing, and replacing existing actions. @@ -227,11 +212,7 @@ To position your custom file action before or after an existing action, you can ```tsx - } - before={"delete"} - /> + } before={"delete"} /> ``` @@ -253,9 +234,6 @@ To replace an existing action with a new action element, you need to reference a ```tsx - A new action!} - /> + A new action!} /> ``` diff --git a/docs/developer-docs/5.x/file-manager/extending/customize-file-list-table-columns.mdx b/docs/developer-docs/5.x/file-manager/extending/customize-file-list-table-columns.mdx index 5976c1d9f..a43316f98 100644 --- a/docs/developer-docs/5.x/file-manager/extending/customize-file-list-table-columns.mdx +++ b/docs/developer-docs/5.x/file-manager/extending/customize-file-list-table-columns.mdx @@ -38,7 +38,6 @@ File Manager allows you to view files and folders in a table or grid format. The - **Created**: presenting the `createdOn` field - **Modified**: presenting the `savedOn` field - ## Using the code examples @@ -79,7 +78,7 @@ To add a new column, use the `Browser.Table.Column` component and mount it withi ### Simple Column -Here is an example of creating a column to show the `copyright` field data within the table. +Here is an example of creating a column to show the `copyright` field data within the table. The `Browser.Table.Column` component receives the following mandatory props: - `name` used to target the field you want to show and serves as a unique identifier @@ -87,11 +86,8 @@ The `Browser.Table.Column` component receives the following mandatory props: ```tsx - - + + ``` This is the whole process of registering a column. @@ -106,8 +102,7 @@ For example, you could create a `CellCopyright` component that displays: - a dash for folder rows - a dash in case the copyright is not set -- the `copyright` field value prepended by the `©` symbol - +- the `copyright` field value prepended by the `©` symbol ```tsx export const CellCopyright = () => { @@ -139,13 +134,14 @@ Using the `cell` prop, you can pass the custom component to the column definitio name={"extensions.copyright"} header={"Copyright"} + cell={} - /> - + /> + ``` ### Custom column size + To set the initial size of a column, you can use the `size` property. By default, the size is set to `100`. However, this is not a value in pixels but more of a proportion with the other columns within the table. If you want to double the size of a specific column, you can pass `200` as the value. @@ -158,9 +154,9 @@ In addition, you can allow or disallow users to adjust the column width accordin name={"extensions.copyright"} header={"Copyright"} + size={75} -+ resizable={false} // The column is not resizable by the user. - /> - ++ resizable={false} // The column is not resizable by the user. + /> + ``` ### Custom column visibility @@ -175,8 +171,8 @@ Users have the ability to show/hide columns by using the column settings menu. name={"extensions.copyright"} header={"Copyright"} + visible={false} - /> - + /> + ``` @@ -192,8 +188,8 @@ When the `hideable` property is set to false, users are restricted from dynamica header={"Price"} modelIds={["property"]} + hideable={false} - /> - + /> + ``` @@ -208,8 +204,8 @@ You can easily add custom CSS class names to columns using the `className` prope name={"extensions.copyright"} header={"Copyright"} + className={"custom-copyright-className"} - /> - + /> + ``` ## Discover Columns @@ -226,11 +222,7 @@ To position your column before or after an existing column, you can use the `bef ```tsx - + ``` @@ -250,10 +242,10 @@ To replace an existing column with a new cell renderer, you need to reference an ```tsx - {"Custom Type Cell"}} + {"Custom Type Cell"}} /> ``` diff --git a/docs/developer-docs/5.x/file-manager/extending/customize-file-preview.mdx b/docs/developer-docs/5.x/file-manager/extending/customize-file-preview.mdx index adb38e950..851ab0b7a 100644 --- a/docs/developer-docs/5.x/file-manager/extending/customize-file-preview.mdx +++ b/docs/developer-docs/5.x/file-manager/extending/customize-file-preview.mdx @@ -18,7 +18,7 @@ import fmFilesPreviewsVideo2 from "./assets/fm-files-previews-video-2.png"; ## Overview -In File Manager, every file has a preview that is shown to the user when browsing files. This preview is a small thumbnail that gives the user a quick overview of the file content. +In File Manager, every file has a preview that is shown to the user when browsing files. This preview is a small thumbnail that gives the user a quick overview of the file content. For example, in the following screenshot, we can see that the image file on the left has a small thumbnail immediately shown to the user. This makes it easier to quickly identity the file the user is looking for. @@ -30,7 +30,11 @@ But, if required, this can be implemented, which is what we cover in this articl ## Getting Started - + ## File Preview For Specific File Type diff --git a/docs/developer-docs/5.x/file-manager/extending/customize-folder-fields.mdx b/docs/developer-docs/5.x/file-manager/extending/customize-folder-fields.mdx index ede1621b8..9059635fe 100644 --- a/docs/developer-docs/5.x/file-manager/extending/customize-folder-fields.mdx +++ b/docs/developer-docs/5.x/file-manager/extending/customize-folder-fields.mdx @@ -113,11 +113,11 @@ import "./App.scss"; + const ExtensionFieldDecorator = Browser.Folder.ExtensionField.createDecorator(Original => { + return function FieldDecorator(props) { + const form = useForm(); -+ ++ + if (form.data.id) { + return null; + } -+ ++ + return ; + }; + }); @@ -135,6 +135,7 @@ export const App = () => { ``` ### Understanding the Implementation + - `useForm` **Hook**: This hook provides access to the current form inside the New/Edit Folder dialog. By leveraging `useForm`, you can dynamically control field visibility and behavior based on the form properties. - `ExtensionFieldDecorator`: This decorator ensures the field is only rendered when specific conditions are met. In this case, the field is hidden if an existing folder is being edited. - `FileManagerViewConfig`: This component acts as a wrapper, enabling access to configuration utilities for the File Manager List view. It must be mounted as a parent of other config components to ensure the configurations are applied correctly. @@ -149,7 +150,4 @@ Using this approach, you can: Once you run your Admin app and open the File Manager, edit an existing folder to see the custom renderer in action. While this example hides the field, you can extend it to include more advanced field customizations. - + diff --git a/docs/developer-docs/5.x/get-started/build-with-llms-ai.mdx b/docs/developer-docs/5.x/get-started/build-with-llms-ai.mdx index 4307d64c9..a39c5699d 100644 --- a/docs/developer-docs/5.x/get-started/build-with-llms-ai.mdx +++ b/docs/developer-docs/5.x/get-started/build-with-llms-ai.mdx @@ -26,7 +26,6 @@ You can ensure your AI tools have current Webiny knowledge through the Webiny MC - ### Server Details - **Name**: Webiny Docs @@ -69,7 +68,10 @@ To manually connect to the Webiny MCP server in VS Code, add the following to yo Learn more in the [VS Code documentation](https://code.visualstudio.com/docs/copilot/chat/mcp-servers). - Sometimes, in VS Code, you may need to perform a one-time setup. When you ask a question and include a phrase like “use Webiny Docs”, then VS Code will prompt you to enable the Webiny MCP server for all sessions. Allow it, and the setup will be complete. This step is required only once. Please refer to the image below for reference. + Sometimes, in VS Code, you may need to perform a one-time setup. When you ask a question and + include a phrase like “use Webiny Docs”, then VS Code will prompt you to enable the Webiny MCP + server for all sessions. Allow it, and the setup will be complete. This step is required only + once. Please refer to the image below for reference. ![VS Code Webiny MCP Server](./assets/vs-code-webiny-mcp.png) @@ -87,10 +89,7 @@ To connect to the Webiny MCP server in Claude Desktop, add the following to your "mcpServers": { "webiny": { "command": "npx", - "args": [ - "@modelcontextprotocol/server-fetch", - "https://mcp.docs.webiny.com/mcp" - ] + "args": ["@modelcontextprotocol/server-fetch", "https://mcp.docs.webiny.com/mcp"] } } } @@ -103,6 +102,7 @@ Learn more in the [Claude Desktop documentation](https://docs.anthropic.com/en/d Many tools support a common JSON configuration format for MCP servers. If there are not specific instructions for your chosen tool, you may be able to add the Webiny Docs MCP server by including the following configuration in your tool's MCP settings: **Streamable HTTP:** + ```json { "mcpServers": { @@ -115,6 +115,7 @@ Many tools support a common JSON configuration format for MCP servers. If there ``` **Local Proxy:** + ```json { "mcpServers": { @@ -275,7 +276,6 @@ Refer to the [OpenAI MCP documentation](https://platform.openai.com/docs/mcp#tes Once configured, you can ask your AI tool questions about Webiny, and it will retrieve information directly from the latest docs. Coding agents will be able to consult the latest documentation when performing coding tasks, and chatbots will be able to accurately answer questions about Webiny features, APIs, and best practices. - ## Troubleshooting If you encounter issues: @@ -287,7 +287,6 @@ If you encounter issues: If you are still having problems, please reach out to our [community support channels](https://www.webiny.com/slack) for assistance. - ## AI Assistant in Documentation The Webiny documentation is equipped with an AI Assistant that can answer your questions and help you build customizations with Webiny. To open the AI Assistant, Click the "Ask AI" button in the bottom right corner of the documentation. diff --git a/docs/developer-docs/5.x/headless-cms/ai/smart-seo-open-ai.mdx b/docs/developer-docs/5.x/headless-cms/ai/smart-seo-open-ai.mdx index aa34c6bcb..316d8dce7 100644 --- a/docs/developer-docs/5.x/headless-cms/ai/smart-seo-open-ai.mdx +++ b/docs/developer-docs/5.x/headless-cms/ai/smart-seo-open-ai.mdx @@ -1,5 +1,5 @@ --- -id: +id: title: Smart SEO - OpenAI description: Learn how to integrate OpenAI with Webiny to build a Smart SEO tool for Headless CMS. This tool automatically generates SEO titles, descriptions, and tags for your articles. --- @@ -72,6 +72,7 @@ yarn webiny watch admin --env ENVIRONMENT_NAME ``` ## All Set! + You’re now ready to generate SEO titles and tags for your articles using OpenAI. Simply navigate to the **Article - Smart SEO** Model, create a new article, and click the “AI-Optimized SEO” button to generate SEO titles and tags effortlessly.
*/} -{/* { /> + ` | Yes | Key-value map of AWS tags to apply | +| Prop | Type | Required | Description | +| ------ | ------------------------ | -------- | ---------------------------------- | +| `tags` | `Record` | Yes | Key-value map of AWS tags to apply | --- @@ -157,12 +157,12 @@ Conditionally renders child extensions based on the current environment, variant `Infra.Env.Is` renders children when conditions match; `Infra.Env.IsNot` renders when they do not. Multiple props are AND-ed. -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `env` | `string \| string[]` | No | Environment name(s) to match | -| `variant` | `string \| string[]` | No | Variant name(s) to match | -| `region` | `string \| string[]` | No | AWS region(s) to match | -| `children` | `ReactNode` | Yes | Extensions to render when the condition is met | +| Prop | Type | Required | Description | +| ---------- | -------------------- | -------- | ---------------------------------------------- | +| `env` | `string \| string[]` | No | Environment name(s) to match | +| `variant` | `string \| string[]` | No | Variant name(s) to match | +| `region` | `string \| string[]` | No | AWS region(s) to match | +| `children` | `ReactNode` | Yes | Extensions to render when the condition is met | ```tsx webiny.config.tsx @@ -174,9 +174,9 @@ Conditionally renders child extensions based on the current environment, variant Shorthand for `Infra.Env.Is` / `Infra.Env.IsNot` matched against the environments declared in `Infra.ProductionEnvironments`. -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `children` | `ReactNode` | Yes | Extensions to render | +| Prop | Type | Required | Description | +| ---------- | ----------- | -------- | -------------------- | +| `children` | `ReactNode` | Yes | Extensions to render | --- @@ -186,9 +186,9 @@ Conditionally renders child extensions based on whether the process is running i ### Infra.Ci.Is / Infra.Ci.IsNot -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `children` | `ReactNode` | Yes | Extensions to render | +| Prop | Type | Required | Description | +| ---------- | ----------- | -------- | -------------------- | +| `children` | `ReactNode` | Yes | Extensions to render | --- @@ -200,37 +200,37 @@ Extensions that target the **Admin** application's build, deployment, and cloud All six extensions accept a single `src` prop pointing to an implementation file. All can be used multiple times. -| Extension | Triggered | -| --- | --- | -| `Infra.Admin.BeforeBuild` | Before the Admin app is built | -| `Infra.Admin.AfterBuild` | After the Admin app is built | -| `Infra.Admin.BeforeDeploy` | Before the Admin app is deployed | -| `Infra.Admin.AfterDeploy` | After the Admin app is deployed | -| `Infra.Admin.BeforeWatch` | Before `webiny watch` starts for Admin | -| `Infra.Admin.Pulumi` | During Pulumi deployment — modify Admin cloud infrastructure | +| Extension | Triggered | +| -------------------------- | ------------------------------------------------------------ | +| `Infra.Admin.BeforeBuild` | Before the Admin app is built | +| `Infra.Admin.AfterBuild` | After the Admin app is built | +| `Infra.Admin.BeforeDeploy` | Before the Admin app is deployed | +| `Infra.Admin.AfterDeploy` | After the Admin app is deployed | +| `Infra.Admin.BeforeWatch` | Before `webiny watch` starts for Admin | +| `Infra.Admin.Pulumi` | During Pulumi deployment — modify Admin cloud infrastructure | -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `src` | `string` | Yes | Path to the implementation file | +| Prop | Type | Required | Description | +| ----- | -------- | -------- | ------------------------------- | +| `src` | `string` | Yes | Path to the implementation file | ### Infra.Admin.CustomDomains Connects a custom domain to the Admin CloudFront distribution. See [Connect Custom Domain](/{version}/admin/connect-custom-domain) for a full guide. -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `domains` | `string[]` | Yes | One or more custom domain names | -| `certificateArn` | `string` | Yes | ARN of the ACM certificate (must be in `us-east-1`) | -| `sslMethod` | `"sni-only" \| "vip"` | No | SSL/TLS method. Defaults to `"sni-only"`. | +| Prop | Type | Required | Description | +| ---------------- | --------------------- | -------- | --------------------------------------------------- | +| `domains` | `string[]` | Yes | One or more custom domain names | +| `certificateArn` | `string` | Yes | ARN of the ACM certificate (must be in `us-east-1`) | +| `sslMethod` | `"sni-only" \| "vip"` | No | SSL/TLS method. Defaults to `"sni-only"`. | ### Infra.Admin.StackOutputValue Adds a custom key-value entry to the Admin Pulumi stack outputs. Can be used multiple times. -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `key` | `string` | Yes | Output key name | -| `value` | `any` | Yes | Output value | +| Prop | Type | Required | Description | +| ------- | -------- | -------- | --------------- | +| `key` | `string` | Yes | Output key name | +| `value` | `any` | Yes | Output value | --- @@ -242,46 +242,46 @@ Extensions that target the **API** application's build, deployment, and cloud in All six extensions accept a single `src` prop pointing to an implementation file. All can be used multiple times. -| Extension | Triggered | -| --- | --- | -| `Infra.Api.BeforeBuild` | Before the API app is built | -| `Infra.Api.AfterBuild` | After the API app is built | -| `Infra.Api.BeforeDeploy` | Before the API app is deployed | -| `Infra.Api.AfterDeploy` | After the API app is deployed | -| `Infra.Api.BeforeWatch` | Before `webiny watch` starts for API | -| `Infra.Api.Pulumi` | During Pulumi deployment — modify API cloud infrastructure | +| Extension | Triggered | +| ------------------------ | ---------------------------------------------------------- | +| `Infra.Api.BeforeBuild` | Before the API app is built | +| `Infra.Api.AfterBuild` | After the API app is built | +| `Infra.Api.BeforeDeploy` | Before the API app is deployed | +| `Infra.Api.AfterDeploy` | After the API app is deployed | +| `Infra.Api.BeforeWatch` | Before `webiny watch` starts for API | +| `Infra.Api.Pulumi` | During Pulumi deployment — modify API cloud infrastructure | -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `src` | `string` | Yes | Path to the implementation file | +| Prop | Type | Required | Description | +| ----- | -------- | -------- | ------------------------------- | +| `src` | `string` | Yes | Path to the implementation file | ### Infra.Api.CustomDomains Connects a custom domain to the API CloudFront distribution. See [Connect Custom Domain](/{version}/webiny-api/connect-custom-domain) for a full guide. -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `domains` | `string[]` | Yes | One or more custom domain names | -| `certificateArn` | `string` | Yes | ARN of the ACM certificate (must be in `us-east-1`) | -| `sslMethod` | `"sni-only" \| "vip"` | No | SSL/TLS method. Defaults to `"sni-only"`. | +| Prop | Type | Required | Description | +| ---------------- | --------------------- | -------- | --------------------------------------------------- | +| `domains` | `string[]` | Yes | One or more custom domain names | +| `certificateArn` | `string` | Yes | ARN of the ACM certificate (must be in `us-east-1`) | +| `sslMethod` | `"sni-only" \| "vip"` | No | SSL/TLS method. Defaults to `"sni-only"`. | ### Infra.Api.LambdaFunction Adds a fully custom Lambda function to the API application, with both an application handler and Pulumi infrastructure code. Can be used multiple times. -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `functionSrc` | `string` | Yes | Path to the Lambda handler source file | -| `pulumiSrc` | `string` | Yes | Path to the Pulumi infrastructure file (must follow `ApiPulumi` abstraction) | +| Prop | Type | Required | Description | +| ------------- | -------- | -------- | ---------------------------------------------------------------------------- | +| `functionSrc` | `string` | Yes | Path to the Lambda handler source file | +| `pulumiSrc` | `string` | Yes | Path to the Pulumi infrastructure file (must follow `ApiPulumi` abstraction) | ### Infra.Api.StackOutputValue Adds a custom key-value entry to the API Pulumi stack outputs. Can be used multiple times. -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `key` | `string` | Yes | Output key name | -| `value` | `any` | Yes | Output value | +| Prop | Type | Required | Description | +| ------- | -------- | -------- | --------------- | +| `key` | `string` | Yes | Output key name | +| `value` | `any` | Yes | Output value | --- @@ -293,24 +293,24 @@ Extensions that target the **Core** application — the shared infrastructure la All six extensions accept a single `src` prop pointing to an implementation file. All can be used multiple times. -| Extension | Triggered | -| --- | --- | -| `Infra.Core.BeforeBuild` | Before the Core app is built | -| `Infra.Core.AfterBuild` | After the Core app is built | -| `Infra.Core.BeforeDeploy` | Before the Core app is deployed | -| `Infra.Core.AfterDeploy` | After the Core app is deployed | -| `Infra.Core.BeforeWatch` | Before `webiny watch` starts for Core | -| `Infra.Core.Pulumi` | During Pulumi deployment — modify Core cloud infrastructure | +| Extension | Triggered | +| ------------------------- | ----------------------------------------------------------- | +| `Infra.Core.BeforeBuild` | Before the Core app is built | +| `Infra.Core.AfterBuild` | After the Core app is built | +| `Infra.Core.BeforeDeploy` | Before the Core app is deployed | +| `Infra.Core.AfterDeploy` | After the Core app is deployed | +| `Infra.Core.BeforeWatch` | Before `webiny watch` starts for Core | +| `Infra.Core.Pulumi` | During Pulumi deployment — modify Core cloud infrastructure | -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `src` | `string` | Yes | Path to the implementation file | +| Prop | Type | Required | Description | +| ----- | -------- | -------- | ------------------------------- | +| `src` | `string` | Yes | Path to the implementation file | ### Infra.Core.StackOutputValue Adds a custom key-value entry to the Core Pulumi stack outputs. Can be used multiple times. -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `key` | `string` | Yes | Output key name | -| `value` | `any` | Yes | Output value | +| Prop | Type | Required | Description | +| ------- | -------- | -------- | --------------- | +| `key` | `string` | Yes | Output key name | +| `value` | `any` | Yes | Output value | diff --git a/docs/developer-docs/6.x/reference/extensions/project.mdx b/docs/developer-docs/6.x/reference/extensions/project.mdx index 3931f93db..91440079f 100644 --- a/docs/developer-docs/6.x/reference/extensions/project.mdx +++ b/docs/developer-docs/6.x/reference/extensions/project.mdx @@ -20,28 +20,28 @@ import { Alert } from "@/components/Alert"; Sets the project identifier, used internally for telemetry and resource naming. -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `id` | `string` | Yes | Project ID. Alphanumeric characters, dashes, underscores, and forward slashes only. | +| Prop | Type | Required | Description | +| ---- | -------- | -------- | ----------------------------------------------------------------------------------- | +| `id` | `string` | Yes | Project ID. Alphanumeric characters, dashes, underscores, and forward slashes only. | ### Project.Telemetry Opts the project in or out of anonymous usage telemetry. -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `enabled` | `boolean` | Yes | Whether to send telemetry. Defaults to `true`. | +| Prop | Type | Required | Description | +| --------- | --------- | -------- | ---------------------------------------------- | +| `enabled` | `boolean` | Yes | Whether to send telemetry. Defaults to `true`. | ### Project.AutoInstall Automatically installs Webiny on first deploy, creating the initial admin user without a manual browser-based setup step. -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `adminUser.firstName` | `string` | Yes | Admin user first name | -| `adminUser.lastName` | `string` | Yes | Admin user last name | -| `adminUser.email` | `string` | Yes | Admin user email (valid email format) | -| `adminUser.password` | `string` | Yes | Admin user password (minimum 8 characters) | +| Prop | Type | Required | Description | +| --------------------- | -------- | -------- | ------------------------------------------ | +| `adminUser.firstName` | `string` | Yes | Admin user first name | +| `adminUser.lastName` | `string` | Yes | Admin user last name | +| `adminUser.email` | `string` | Yes | Admin user email (valid email format) | +| `adminUser.password` | `string` | Yes | Admin user password (minimum 8 characters) | @@ -53,16 +53,16 @@ Do not hardcode credentials in `webiny.config.tsx`. Reference them via environme Enables or disables Webiny Cloud Platform (WCP) licensed features. -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `features.multiTenancy` | `boolean` | No | Multi-tenancy support | -| `features.advancedPublishingWorkflow` | `boolean` | No | Advanced content publishing workflows | -| `features.advancedAccessControlLayer` | `boolean \| object` | No | Advanced ACL. Pass `true` to enable all, or an object to enable selectively (see below). | -| `features.auditLogs` | `boolean` | No | Audit logging | -| `features.recordLocking` | `boolean` | No | Record-level content locking | -| `features.fileManager.threatDetection` | `boolean` | No | Malware threat detection in File Manager uploads | -| `features.aiPowerups.enabled` | `boolean` | No | AI-powered features | -| `features.aiPowerups.options` | `object` | No | Granular AI feature toggles (see below) | +| Prop | Type | Required | Description | +| -------------------------------------- | ------------------- | -------- | ---------------------------------------------------------------------------------------- | +| `features.multiTenancy` | `boolean` | No | Multi-tenancy support | +| `features.advancedPublishingWorkflow` | `boolean` | No | Advanced content publishing workflows | +| `features.advancedAccessControlLayer` | `boolean \| object` | No | Advanced ACL. Pass `true` to enable all, or an object to enable selectively (see below). | +| `features.auditLogs` | `boolean` | No | Audit logging | +| `features.recordLocking` | `boolean` | No | Record-level content locking | +| `features.fileManager.threatDetection` | `boolean` | No | Malware threat detection in File Manager uploads | +| `features.aiPowerups.enabled` | `boolean` | No | AI-powered features | +| `features.aiPowerups.options` | `object` | No | Granular AI feature toggles (see below) | **`advancedAccessControlLayer` object shape:** diff --git a/docs/developer-docs/6.x/webiny-api/api-routes.mdx b/docs/developer-docs/6.x/webiny-api/api-routes.mdx index 242ebaadc..ba4f9e27e 100644 --- a/docs/developer-docs/6.x/webiny-api/api-routes.mdx +++ b/docs/developer-docs/6.x/webiny-api/api-routes.mdx @@ -64,12 +64,12 @@ The `src` path must include the `.ts` extension — omitting it will cause a bui ### Props Reference -| Prop | Type | Required | Description | -| --- | --- | --- | --- | -| `path` | `string` | Yes | Route path, must start with `/`. Use `{paramName}` for path parameters. | -| `method` | `string` | Yes | HTTP method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`, or `ANY` | -| `src` | `string` | Yes | Path to the handler file, including the `.ts` extension | -| `routeName` | `string` | No | Pulumi resource name (kebab-case). Auto-derived from path and method if omitted. | +| Prop | Type | Required | Description | +| ----------- | -------- | -------- | --------------------------------------------------------------------------------- | +| `path` | `string` | Yes | Route path, must start with `/`. Use `{paramName}` for path parameters. | +| `method` | `string` | Yes | HTTP method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`, or `ANY` | +| `src` | `string` | Yes | Path to the handler file, including the `.ts` extension | +| `routeName` | `string` | No | Pulumi resource name (kebab-case). Auto-derived from path and method if omitted. | Use `ANY` as the method to match all HTTP methods on a given path. @@ -131,22 +131,22 @@ export default Route.createImplementation({ ### Route.Request -| Property | Type | Description | -| --- | --- | --- | -| `body` | `unknown` | Parsed request body | -| `headers` | `Record` | Request headers | -| `method` | `string` | HTTP method | -| `url` | `string` | Full request URL | -| `params` | `unknown` | Path parameters (e.g. `{ orderId: "abc" }`) | -| `query` | `unknown` | Query string parameters | +| Property | Type | Description | +| --------- | ------------------------------------------------- | ------------------------------------------- | +| `body` | `unknown` | Parsed request body | +| `headers` | `Record` | Request headers | +| `method` | `string` | HTTP method | +| `url` | `string` | Full request URL | +| `params` | `unknown` | Path parameters (e.g. `{ orderId: "abc" }`) | +| `query` | `unknown` | Query string parameters | ### Route.Reply -| Method | Description | -| --- | --- | -| `reply.send(data?)` | Send the response body | -| `reply.code(statusCode)` | Set the HTTP status code (chainable) | -| `reply.header(key, value)` | Set a response header (chainable) | +| Method | Description | +| -------------------------- | ------------------------------------ | +| `reply.send(data?)` | Send the response body | +| `reply.code(statusCode)` | Set the HTTP status code (chainable) | +| `reply.header(key, value)` | Set a response header (chainable) | Methods are chainable: diff --git a/docs/developer-docs/6.x/website-builder/custom-component.mdx b/docs/developer-docs/6.x/website-builder/custom-component.mdx index 6175cc1ad..fa079b1fd 100644 --- a/docs/developer-docs/6.x/website-builder/custom-component.mdx +++ b/docs/developer-docs/6.x/website-builder/custom-component.mdx @@ -275,8 +275,8 @@ export function Banner({ colorTheme === "primary" ? "bg-primary" : colorTheme === "secondary" - ? "bg-secondary" - : "bg-success"; + ? "bg-secondary" + : "bg-success"; return (
diff --git a/docs/developer-docs/6.x/website-builder/event-handler/pages.mdx b/docs/developer-docs/6.x/website-builder/event-handler/pages.mdx index a5199721d..17e7ac38b 100644 --- a/docs/developer-docs/6.x/website-builder/event-handler/pages.mdx +++ b/docs/developer-docs/6.x/website-builder/event-handler/pages.mdx @@ -4,7 +4,7 @@ title: Page Event Handlers description: Page Event Handlers --- -import {Alert} from "@/components/Alert"; +import { Alert } from "@/components/Alert"; @@ -28,20 +28,20 @@ import { PageBeforeCreateHandler } from "webiny/api/website-builder/page"; import { MyCustomPageModifier } from "./MyCustomPageModifier.js"; class OnPageBeforeCreateHandler implements PageBeforeCreateHandler.Interface { - public constructor(private modifier: MyCustomPageModifier.Interface) {} - - public async handle(params: PageBeforeCreateHandler.Event) { - const { payload } = params; - const page = payload.input; - await this.modifier.execute({ - input: page - }); - } + public constructor(private modifier: MyCustomPageModifier.Interface) {} + + public async handle(params: PageBeforeCreateHandler.Event) { + const { payload } = params; + const page = payload.input; + await this.modifier.execute({ + input: page + }); + } } export default PageBeforeCreateHandler.createImplementation({ - implementation: OnPageBeforeCreateHandler, - dependencies: [MyCustomPageModifier] + implementation: OnPageBeforeCreateHandler, + dependencies: [MyCustomPageModifier] }); ``` @@ -52,17 +52,17 @@ import { PageAfterCreateHandler } from "webiny/api/website-builder/page"; import { Logger } from "webiny/api/logger"; class OnPageAfterCreateHandler implements PageAfterCreateHandler.Interface { - public constructor(private logger: Logger.Interface) {} + public constructor(private logger: Logger.Interface) {} - public async handle(params: PageAfterCreateHandler.Event) { - const { payload } = params; - this.logger.info("Page created", { page: payload.page }); - } + public async handle(params: PageAfterCreateHandler.Event) { + const { payload } = params; + this.logger.info("Page created", { page: payload.page }); + } } export default PageAfterCreateHandler.createImplementation({ - implementation: OnPageAfterCreateHandler, - dependencies: [Logger] + implementation: OnPageAfterCreateHandler, + dependencies: [Logger] }); ``` @@ -72,21 +72,23 @@ export default PageAfterCreateHandler.createImplementation({ import { PageBeforeCreateRevisionFromHandler } from "webiny/api/website-builder/page"; import { MyCustomPageModifier } from "./MyCustomPageModifier.js"; -class OnPageBeforeCreateRevisionFromHandler implements PageBeforeCreateRevisionFromHandler.Interface { - public constructor(private modifier: MyCustomPageModifier.Interface) {} - - public async handle(params: PageBeforeCreateRevisionFromHandler.Event) { - const { payload } = params; - const page = payload.input; - await this.modifier.execute({ - input: page - }); - } +class OnPageBeforeCreateRevisionFromHandler + implements PageBeforeCreateRevisionFromHandler.Interface +{ + public constructor(private modifier: MyCustomPageModifier.Interface) {} + + public async handle(params: PageBeforeCreateRevisionFromHandler.Event) { + const { payload } = params; + const page = payload.input; + await this.modifier.execute({ + input: page + }); + } } export default PageBeforeCreateRevisionFromHandler.createImplementation({ - implementation: OnPageBeforeCreateRevisionFromHandler, - dependencies: [MyCustomPageModifier] + implementation: OnPageBeforeCreateRevisionFromHandler, + dependencies: [MyCustomPageModifier] }); ``` @@ -97,21 +99,20 @@ import { PageAfterCreateRevisionFromHandler } from "webiny/api/website-builder/p import { Logger } from "webiny/api/logger"; class OnPageAfterCreateRevisionFromHandler implements PageAfterCreateRevisionFromHandler.Interface { - public constructor(private logger: Logger.Interface) {} + public constructor(private logger: Logger.Interface) {} - public async handle(params: PageAfterCreateRevisionFromHandler.Event) { - const { payload } = params; - this.logger.info("Page revision created", { page: payload.page }); - } + public async handle(params: PageAfterCreateRevisionFromHandler.Event) { + const { payload } = params; + this.logger.info("Page revision created", { page: payload.page }); + } } export default PageAfterCreateRevisionFromHandler.createImplementation({ - implementation: OnPageAfterCreateRevisionFromHandler, - dependencies: [Logger] + implementation: OnPageAfterCreateRevisionFromHandler, + dependencies: [Logger] }); ``` - ## On Before Page Update ```typescript @@ -119,20 +120,20 @@ import { PageBeforeUpdateHandler } from "webiny/api/website-builder/page"; import { MyCustomPageModifier } from "./MyCustomPageModifier.js"; class OnPageBeforeUpdateHandler implements PageBeforeUpdateHandler.Interface { - public constructor(private modifier: MyCustomPageModifier.Interface) {} - - public async handle(params: PageBeforeUpdateHandler.Event) { - const { payload } = params; - const page = payload.input; - await this.modifier.execute({ - input: page - }); - } + public constructor(private modifier: MyCustomPageModifier.Interface) {} + + public async handle(params: PageBeforeUpdateHandler.Event) { + const { payload } = params; + const page = payload.input; + await this.modifier.execute({ + input: page + }); + } } export default PageBeforeUpdateHandler.createImplementation({ - implementation: OnPageBeforeUpdateHandler, - dependencies: [MyCustomPageModifier] + implementation: OnPageBeforeUpdateHandler, + dependencies: [MyCustomPageModifier] }); ``` @@ -143,17 +144,17 @@ import { PageAfterUpdateHandler } from "webiny/api/website-builder/page"; import { Logger } from "webiny/api/logger"; class OnPageAfterUpdateHandler implements PageAfterUpdateHandler.Interface { - public constructor(private logger: Logger.Interface) {} + public constructor(private logger: Logger.Interface) {} - public async handle(params: PageAfterUpdateHandler.Event) { - const { payload } = params; - this.logger.info("Page updated", { page: payload.page }); - } + public async handle(params: PageAfterUpdateHandler.Event) { + const { payload } = params; + this.logger.info("Page updated", { page: payload.page }); + } } export default PageAfterUpdateHandler.createImplementation({ - implementation: OnPageAfterUpdateHandler, - dependencies: [Logger] + implementation: OnPageAfterUpdateHandler, + dependencies: [Logger] }); ``` @@ -164,17 +165,17 @@ import { PageBeforeDeleteHandler } from "webiny/api/website-builder/page"; import { Logger } from "webiny/api/logger"; class OnPageBeforeDeleteHandler implements PageBeforeDeleteHandler.Interface { - public constructor(private logger: Logger.Interface) {} + public constructor(private logger: Logger.Interface) {} - public async handle(params: PageBeforeDeleteHandler.Event) { - const { payload } = params; - this.logger.info("Trying to delete page", { page: payload.page }); - } + public async handle(params: PageBeforeDeleteHandler.Event) { + const { payload } = params; + this.logger.info("Trying to delete page", { page: payload.page }); + } } export default PageBeforeDeleteHandler.createImplementation({ - implementation: OnPageBeforeDeleteHandler, - dependencies: [Logger] + implementation: OnPageBeforeDeleteHandler, + dependencies: [Logger] }); ``` @@ -185,17 +186,17 @@ import { PageAfterDeleteHandler } from "webiny/api/website-builder/page"; import { Logger } from "webiny/api/logger"; class OnPageAfterDeleteHandler implements PageAfterDeleteHandler.Interface { - public constructor(private logger: Logger.Interface) {} + public constructor(private logger: Logger.Interface) {} - public async handle(params: PageAfterDeleteHandler.Event) { - const { payload } = params; - this.logger.info("Page deleted", { page: payload.page }); - } + public async handle(params: PageAfterDeleteHandler.Event) { + const { payload } = params; + this.logger.info("Page deleted", { page: payload.page }); + } } export default PageAfterDeleteHandler.createImplementation({ - implementation: OnPageAfterDeleteHandler, - dependencies: [Logger] + implementation: OnPageAfterDeleteHandler, + dependencies: [Logger] }); ``` @@ -206,20 +207,20 @@ import { PageBeforeDuplicateHandler } from "webiny/api/website-builder/page"; import { MyCustomPageModifier } from "./MyCustomPageModifier.js"; class OnPageBeforeDuplicateHandler implements PageBeforeDuplicateHandler.Interface { - public constructor(private modifier: MyCustomPageModifier.Interface) {} - - public async handle(params: PageBeforeDuplicateHandler.Event) { - const { payload } = params; - const page = payload.input; - await this.modifier.execute({ - input: page - }); - } + public constructor(private modifier: MyCustomPageModifier.Interface) {} + + public async handle(params: PageBeforeDuplicateHandler.Event) { + const { payload } = params; + const page = payload.input; + await this.modifier.execute({ + input: page + }); + } } export default PageBeforeDuplicateHandler.createImplementation({ - implementation: OnPageBeforeDuplicateHandler, - dependencies: [MyCustomPageModifier] + implementation: OnPageBeforeDuplicateHandler, + dependencies: [MyCustomPageModifier] }); ``` @@ -230,17 +231,17 @@ import { PageAfterDuplicateHandler } from "webiny/api/website-builder/page"; import { Logger } from "webiny/api/logger"; class OnPageAfterDuplicateHandler implements PageAfterDuplicateHandler.Interface { - public constructor(private logger: Logger.Interface) {} + public constructor(private logger: Logger.Interface) {} - public async handle(params: PageAfterDuplicateHandler.Event) { - const { payload } = params; - this.logger.info("Page duplicated", { page: payload.page }); - } + public async handle(params: PageAfterDuplicateHandler.Event) { + const { payload } = params; + this.logger.info("Page duplicated", { page: payload.page }); + } } export default PageAfterDuplicateHandler.createImplementation({ - implementation: OnPageAfterDuplicateHandler, - dependencies: [Logger] + implementation: OnPageAfterDuplicateHandler, + dependencies: [Logger] }); ``` @@ -251,17 +252,17 @@ import { PageBeforeMoveHandler } from "webiny/api/website-builder/page"; import { Logger } from "webiny/api/logger"; class OnPageBeforeMoveHandler implements PageBeforeMoveHandler.Interface { - public constructor(private logger: Logger.Interface) {} + public constructor(private logger: Logger.Interface) {} - public async handle(params: PageBeforeMoveHandler.Event) { - const { payload } = params; - this.logger.info("Trying to move page", { page: payload.page }); - } + public async handle(params: PageBeforeMoveHandler.Event) { + const { payload } = params; + this.logger.info("Trying to move page", { page: payload.page }); + } } export default PageBeforeMoveHandler.createImplementation({ - implementation: OnPageBeforeMoveHandler, - dependencies: [Logger] + implementation: OnPageBeforeMoveHandler, + dependencies: [Logger] }); ``` @@ -272,17 +273,17 @@ import { PageAfterMoveHandler } from "webiny/api/website-builder/page"; import { Logger } from "webiny/api/logger"; class OnPageAfterMoveHandler implements PageAfterMoveHandler.Interface { - public constructor(private logger: Logger.Interface) {} + public constructor(private logger: Logger.Interface) {} - public async handle(params: PageAfterMoveHandler.Event) { - const { payload } = params; - this.logger.info("Page moved", { page: payload.page }); - } + public async handle(params: PageAfterMoveHandler.Event) { + const { payload } = params; + this.logger.info("Page moved", { page: payload.page }); + } } export default PageAfterMoveHandler.createImplementation({ - implementation: OnPageAfterMoveHandler, - dependencies: [Logger] + implementation: OnPageAfterMoveHandler, + dependencies: [Logger] }); ``` @@ -293,17 +294,17 @@ import { PageBeforePublishHandler } from "webiny/api/website-builder/page"; import { Logger } from "webiny/api/logger"; class OnPageBeforePublishHandler implements PageBeforePublishHandler.Interface { - public constructor(private logger: Logger.Interface) {} + public constructor(private logger: Logger.Interface) {} - public async handle(params: PageBeforePublishHandler.Event) { - const { payload } = params; - this.logger.info("Trying to publish page", { page: payload.page }); - } + public async handle(params: PageBeforePublishHandler.Event) { + const { payload } = params; + this.logger.info("Trying to publish page", { page: payload.page }); + } } export default PageBeforePublishHandler.createImplementation({ - implementation: OnPageBeforePublishHandler, - dependencies: [Logger] + implementation: OnPageBeforePublishHandler, + dependencies: [Logger] }); ``` @@ -314,17 +315,17 @@ import { PageAfterPublishHandler } from "webiny/api/website-builder/page"; import { Logger } from "webiny/api/logger"; class OnPageAfterPublishHandler implements PageAfterPublishHandler.Interface { - public constructor(private logger: Logger.Interface) {} + public constructor(private logger: Logger.Interface) {} - public async handle(params: PageAfterPublishHandler.Event) { - const { payload } = params; - this.logger.info("Page published", { page: payload.page }); - } + public async handle(params: PageAfterPublishHandler.Event) { + const { payload } = params; + this.logger.info("Page published", { page: payload.page }); + } } export default PageAfterPublishHandler.createImplementation({ - implementation: OnPageAfterPublishHandler, - dependencies: [Logger] + implementation: OnPageAfterPublishHandler, + dependencies: [Logger] }); ``` @@ -335,17 +336,17 @@ import { PageBeforeUnpublishHandler } from "webiny/api/website-builder/page"; import { Logger } from "webiny/api/logger"; class OnPageBeforeUnpublishHandler implements PageBeforeUnpublishHandler.Interface { - public constructor(private logger: Logger.Interface) {} + public constructor(private logger: Logger.Interface) {} - public async handle(params: PageBeforeUnpublishHandler.Event) { - const { payload } = params; - this.logger.info("Trying to unpublish page", { page: payload.page }); - } + public async handle(params: PageBeforeUnpublishHandler.Event) { + const { payload } = params; + this.logger.info("Trying to unpublish page", { page: payload.page }); + } } export default PageBeforeUnpublishHandler.createImplementation({ - implementation: OnPageBeforeUnpublishHandler, - dependencies: [Logger] + implementation: OnPageBeforeUnpublishHandler, + dependencies: [Logger] }); ``` @@ -356,17 +357,16 @@ import { PageAfterUnpublishHandler } from "webiny/api/website-builder/page"; import { Logger } from "webiny/api/logger"; class OnPageAfterUnpublishHandler implements PageAfterUnpublishHandler.Interface { - public constructor(private logger: Logger.Interface) {} + public constructor(private logger: Logger.Interface) {} - public async handle(params: PageAfterUnpublishHandler.Event) { - const { payload } = params; - this.logger.info("Page unpublished", { page: payload.page }); - } + public async handle(params: PageAfterUnpublishHandler.Event) { + const { payload } = params; + this.logger.info("Page unpublished", { page: payload.page }); + } } export default PageAfterUnpublishHandler.createImplementation({ - implementation: OnPageAfterUnpublishHandler, - dependencies: [Logger] + implementation: OnPageAfterUnpublishHandler, + dependencies: [Logger] }); ``` - diff --git a/docs/developer-docs/6.x/website-builder/event-handler/redirects.mdx b/docs/developer-docs/6.x/website-builder/event-handler/redirects.mdx index 0c4a752fe..13da8dc4c 100644 --- a/docs/developer-docs/6.x/website-builder/event-handler/redirects.mdx +++ b/docs/developer-docs/6.x/website-builder/event-handler/redirects.mdx @@ -4,7 +4,7 @@ title: Redirect Event Handlers description: Redirect Event Handlers --- -import {Alert} from "@/components/Alert"; +import { Alert } from "@/components/Alert"; @@ -28,21 +28,21 @@ import { RedirectBeforeCreateHandler } from "webiny/api/website-builder/redirect import { Logger } from "webiny/api/logger"; class OnRedirectBeforeCreateHandler implements RedirectBeforeCreateHandler.Interface { - public constructor(private logger: Logger.Interface) {} - - public async handle(params: RedirectBeforeCreateHandler.Event) { - const { payload, occurredAt, eventType } = params; - this.logger.info("Trying to create a redirect:", { - input: payload.input, - occurredAt, - eventType - }); - } + public constructor(private logger: Logger.Interface) {} + + public async handle(params: RedirectBeforeCreateHandler.Event) { + const { payload, occurredAt, eventType } = params; + this.logger.info("Trying to create a redirect:", { + input: payload.input, + occurredAt, + eventType + }); + } } export default RedirectBeforeCreateHandler.createImplementation({ - implementation: OnRedirectBeforeCreateHandler, - dependencies: [Logger] + implementation: OnRedirectBeforeCreateHandler, + dependencies: [Logger] }); ``` @@ -53,21 +53,21 @@ import { RedirectAfterCreateHandler } from "webiny/api/website-builder/redirect" import { Logger } from "webiny/api/logger"; class OnRedirectAfterCreateHandler implements RedirectAfterCreateHandler.Interface { - public constructor(private logger: Logger.Interface) {} - - public async handle(params: RedirectAfterCreateHandler.Event) { - const { payload, occurredAt, eventType } = params; - this.logger.info("Created a redirect:", { - redirect: payload.redirect, - occurredAt, - eventType - }); - } + public constructor(private logger: Logger.Interface) {} + + public async handle(params: RedirectAfterCreateHandler.Event) { + const { payload, occurredAt, eventType } = params; + this.logger.info("Created a redirect:", { + redirect: payload.redirect, + occurredAt, + eventType + }); + } } export default RedirectAfterCreateHandler.createImplementation({ - implementation: OnRedirectAfterCreateHandler, - dependencies: [Logger] + implementation: OnRedirectAfterCreateHandler, + dependencies: [Logger] }); ``` @@ -78,22 +78,22 @@ import { RedirectBeforeUpdateHandler } from "webiny/api/website-builder/redirect import { Logger } from "webiny/api/logger"; class OnRedirectBeforeUpdateHandler implements RedirectBeforeUpdateHandler.Interface { - public constructor(private logger: Logger.Interface) {} - - public async handle(params: RedirectBeforeUpdateHandler.Event) { - const { payload, occurredAt, eventType } = params; - this.logger.info("Trying to update a redirect:", { - original: payload.original, - input: payload.input, - occurredAt, - eventType - }); - } + public constructor(private logger: Logger.Interface) {} + + public async handle(params: RedirectBeforeUpdateHandler.Event) { + const { payload, occurredAt, eventType } = params; + this.logger.info("Trying to update a redirect:", { + original: payload.original, + input: payload.input, + occurredAt, + eventType + }); + } } export default RedirectBeforeUpdateHandler.createImplementation({ - implementation: OnRedirectBeforeUpdateHandler, - dependencies: [Logger] + implementation: OnRedirectBeforeUpdateHandler, + dependencies: [Logger] }); ``` @@ -104,21 +104,21 @@ import { RedirectAfterUpdateHandler } from "webiny/api/website-builder/redirect" import { Logger } from "webiny/api/logger"; class OnRedirectAfterUpdateHandler implements RedirectAfterUpdateHandler.Interface { - public constructor(private logger: Logger.Interface) {} - - public async handle(params: RedirectAfterUpdateHandler.Event) { - const { payload, occurredAt, eventType } = params; - this.logger.info("Updated a redirect:", { - redirect: payload.redirect, - occurredAt, - eventType - }); - } + public constructor(private logger: Logger.Interface) {} + + public async handle(params: RedirectAfterUpdateHandler.Event) { + const { payload, occurredAt, eventType } = params; + this.logger.info("Updated a redirect:", { + redirect: payload.redirect, + occurredAt, + eventType + }); + } } export default RedirectAfterUpdateHandler.createImplementation({ - implementation: OnRedirectAfterUpdateHandler, - dependencies: [Logger] + implementation: OnRedirectAfterUpdateHandler, + dependencies: [Logger] }); ``` @@ -129,21 +129,21 @@ import { RedirectBeforeDeleteHandler } from "webiny/api/website-builder/redirect import { Logger } from "webiny/api/logger"; class OnRedirectBeforeDeleteHandler implements RedirectBeforeDeleteHandler.Interface { - public constructor(private logger: Logger.Interface) {} - - public async handle(params: RedirectBeforeDeleteHandler.Event) { - const { payload, occurredAt, eventType } = params; - this.logger.info("Trying to delete a redirect:", { - redirect: payload.redirect, - occurredAt, - eventType - }); - } + public constructor(private logger: Logger.Interface) {} + + public async handle(params: RedirectBeforeDeleteHandler.Event) { + const { payload, occurredAt, eventType } = params; + this.logger.info("Trying to delete a redirect:", { + redirect: payload.redirect, + occurredAt, + eventType + }); + } } export default RedirectBeforeDeleteHandler.createImplementation({ - implementation: OnRedirectBeforeDeleteHandler, - dependencies: [Logger] + implementation: OnRedirectBeforeDeleteHandler, + dependencies: [Logger] }); ``` @@ -154,21 +154,21 @@ import { RedirectAfterDeleteHandler } from "webiny/api/website-builder/redirect" import { Logger } from "webiny/api/logger"; class OnRedirectAfterDeleteHandler implements RedirectAfterDeleteHandler.Interface { - public constructor(private logger: Logger.Interface) {} - - public async handle(params: RedirectAfterDeleteHandler.Event) { - const { payload, occurredAt, eventType } = params; - this.logger.info("Deleted a redirect:", { - redirect: payload.redirect, - occurredAt, - eventType - }); - } + public constructor(private logger: Logger.Interface) {} + + public async handle(params: RedirectAfterDeleteHandler.Event) { + const { payload, occurredAt, eventType } = params; + this.logger.info("Deleted a redirect:", { + redirect: payload.redirect, + occurredAt, + eventType + }); + } } export default RedirectAfterDeleteHandler.createImplementation({ - implementation: OnRedirectAfterDeleteHandler, - dependencies: [Logger] + implementation: OnRedirectAfterDeleteHandler, + dependencies: [Logger] }); ``` @@ -179,22 +179,22 @@ import { RedirectBeforeMoveHandler } from "webiny/api/website-builder/redirect"; import { Logger } from "webiny/api/logger"; class OnRedirectBeforeMoveHandler implements RedirectBeforeMoveHandler.Interface { - public constructor(private logger: Logger.Interface) {} - - public async handle(params: RedirectBeforeMoveHandler.Event) { - const { payload, occurredAt, eventType } = params; - this.logger.info("Trying to move a redirect:", { - redirect: payload.redirect, - folderId: payload.folderId, - occurredAt, - eventType - }); - } + public constructor(private logger: Logger.Interface) {} + + public async handle(params: RedirectBeforeMoveHandler.Event) { + const { payload, occurredAt, eventType } = params; + this.logger.info("Trying to move a redirect:", { + redirect: payload.redirect, + folderId: payload.folderId, + occurredAt, + eventType + }); + } } export default RedirectBeforeMoveHandler.createImplementation({ - implementation: OnRedirectBeforeMoveHandler, - dependencies: [Logger] + implementation: OnRedirectBeforeMoveHandler, + dependencies: [Logger] }); ``` @@ -205,21 +205,20 @@ import { RedirectAfterMoveHandler } from "webiny/api/website-builder/redirect"; import { Logger } from "webiny/api/logger"; class OnRedirectAfterMoveHandler implements RedirectAfterMoveHandler.Interface { - public constructor(private logger: Logger.Interface) {} - - public async handle(params: RedirectAfterMoveHandler.Event) { - const { payload, occurredAt, eventType } = params; - this.logger.info("Moved a redirect:", { - redirect: payload.redirect, - occurredAt, - eventType - }); - } + public constructor(private logger: Logger.Interface) {} + + public async handle(params: RedirectAfterMoveHandler.Event) { + const { payload, occurredAt, eventType } = params; + this.logger.info("Moved a redirect:", { + redirect: payload.redirect, + occurredAt, + eventType + }); + } } export default RedirectAfterMoveHandler.createImplementation({ - implementation: OnRedirectAfterMoveHandler, - dependencies: [Logger] + implementation: OnRedirectAfterMoveHandler, + dependencies: [Logger] }); ``` - diff --git a/docs/developer-docs/6.x/website-builder/use-case/pages.mdx b/docs/developer-docs/6.x/website-builder/use-case/pages.mdx index 125dc8cb6..434f219c2 100644 --- a/docs/developer-docs/6.x/website-builder/use-case/pages.mdx +++ b/docs/developer-docs/6.x/website-builder/use-case/pages.mdx @@ -4,7 +4,7 @@ title: Page Use Cases description: Use cases for pages in the Website Builder. --- -import {Alert} from "@/components/Alert"; +import { Alert } from "@/components/Alert"; @@ -28,21 +28,21 @@ import { createAbstraction } from "webiny/api"; import type { CreatePageUseCase } from "webiny/api/website-builder/page"; export interface ICreatePageParams { - input: CreatePageUseCase.Params; + input: CreatePageUseCase.Params; } export type ICreatePageReturn = Promise; export interface ICreatePage { - execute(params: ICreatePageParams): ICreatePageReturn; + execute(params: ICreatePageParams): ICreatePageReturn; } export const CreatePage = createAbstraction("CreatePage"); export namespace CreatePage { - export type Interface = ICreatePage; - export type Params = ICreatePageParams; - export type Return = ICreatePageReturn; + export type Interface = ICreatePage; + export type Params = ICreatePageParams; + export type Return = ICreatePageReturn; } ``` @@ -51,20 +51,20 @@ import { CreatePageUseCase } from "webiny/api/website-builder/page"; import { CreatePage } from "./abstractions.js"; class CreatePageImpl implements CreatePage.Interface { - constructor(private createPageUseCase: CreatePageUseCase.Interface) {} - - async execute({ input }: CreatePage.Params): CreatePage.Return { - const result = await this.createPageUseCase.execute(input); - if (result.isFail()) { - throw result.error; - } - return result.value; + constructor(private createPageUseCase: CreatePageUseCase.Interface) {} + + async execute({ input }: CreatePage.Params): CreatePage.Return { + const result = await this.createPageUseCase.execute(input); + if (result.isFail()) { + throw result.error; } + return result.value; + } } export default CreatePage.createImplementation({ - implementation: CreatePageImpl, - dependencies: [CreatePageUseCase] + implementation: CreatePageImpl, + dependencies: [CreatePageUseCase] }); ``` @@ -75,21 +75,22 @@ import { createAbstraction } from "webiny/api"; import type { CreatePageRevisionFromUseCase } from "webiny/api/website-builder/page"; export interface ICreatePageRevisionFromParams { - input: CreatePageRevisionFromUseCase.Params; + input: CreatePageRevisionFromUseCase.Params; } export type ICreatePageRevisionFromReturn = Promise; export interface ICreatePageRevisionFrom { - execute(params: ICreatePageRevisionFromParams): ICreatePageRevisionFromReturn; + execute(params: ICreatePageRevisionFromParams): ICreatePageRevisionFromReturn; } -export const CreatePageRevisionFrom = createAbstraction("CreatePageRevisionFrom"); +export const CreatePageRevisionFrom = + createAbstraction("CreatePageRevisionFrom"); export namespace CreatePageRevisionFrom { - export type Interface = ICreatePageRevisionFrom; - export type Params = ICreatePageRevisionFromParams; - export type Return = ICreatePageRevisionFromReturn; + export type Interface = ICreatePageRevisionFrom; + export type Params = ICreatePageRevisionFromParams; + export type Return = ICreatePageRevisionFromReturn; } ``` @@ -98,20 +99,20 @@ import { CreatePageRevisionFromUseCase } from "webiny/api/website-builder/page"; import { CreatePageRevisionFrom } from "./abstractions.js"; class CreatePageRevisionFromImpl implements CreatePageRevisionFrom.Interface { - constructor(private createPageRevisionFromUseCase: CreatePageRevisionFromUseCase.Interface) {} - - async execute({ input }: CreatePageRevisionFrom.Params): CreatePageRevisionFrom.Return { - const result = await this.createPageRevisionFromUseCase.execute(input); - if (result.isFail()) { - throw result.error; - } - return result.value; + constructor(private createPageRevisionFromUseCase: CreatePageRevisionFromUseCase.Interface) {} + + async execute({ input }: CreatePageRevisionFrom.Params): CreatePageRevisionFrom.Return { + const result = await this.createPageRevisionFromUseCase.execute(input); + if (result.isFail()) { + throw result.error; } + return result.value; + } } export default CreatePageRevisionFrom.createImplementation({ - implementation: CreatePageRevisionFromImpl, - dependencies: [CreatePageRevisionFromUseCase] + implementation: CreatePageRevisionFromImpl, + dependencies: [CreatePageRevisionFromUseCase] }); ``` @@ -122,22 +123,22 @@ import { createAbstraction } from "webiny/api"; import type { UpdatePageUseCase } from "webiny/api/website-builder/page"; export interface IUpdatePageParams { - id: string; - data: UpdatePageUseCase.UpdateData; + id: string; + data: UpdatePageUseCase.UpdateData; } export type IUpdatePageReturn = Promise; export interface IUpdatePage { - execute(params: IUpdatePageParams): IUpdatePageReturn; + execute(params: IUpdatePageParams): IUpdatePageReturn; } export const UpdatePage = createAbstraction("UpdatePage"); export namespace UpdatePage { - export type Interface = IUpdatePage; - export type Params = IUpdatePageParams; - export type Return = IUpdatePageReturn; + export type Interface = IUpdatePage; + export type Params = IUpdatePageParams; + export type Return = IUpdatePageReturn; } ``` @@ -146,20 +147,20 @@ import { UpdatePageUseCase } from "webiny/api/website-builder/page"; import { UpdatePage } from "./abstractions.js"; class UpdatePageImpl implements UpdatePage.Interface { - constructor(private updatePageUseCase: UpdatePageUseCase.Interface) {} - - async execute({ id, data }: UpdatePage.Params): UpdatePage.Return { - const result = await this.updatePageUseCase.execute(id, data); - if (result.isFail()) { - throw result.error; - } - return result.value; + constructor(private updatePageUseCase: UpdatePageUseCase.Interface) {} + + async execute({ id, data }: UpdatePage.Params): UpdatePage.Return { + const result = await this.updatePageUseCase.execute(id, data); + if (result.isFail()) { + throw result.error; } + return result.value; + } } export default UpdatePage.createImplementation({ - implementation: UpdatePageImpl, - dependencies: [UpdatePageUseCase] + implementation: UpdatePageImpl, + dependencies: [UpdatePageUseCase] }); ``` @@ -169,21 +170,21 @@ export default UpdatePage.createImplementation({ import { createAbstraction } from "webiny/api"; export interface IDeletePageParams { - id: string; + id: string; } export type IDeletePageReturn = Promise; export interface IDeletePage { - execute(params: IDeletePageParams): IDeletePageReturn; + execute(params: IDeletePageParams): IDeletePageReturn; } export const DeletePage = createAbstraction("DeletePage"); export namespace DeletePage { - export type Interface = IDeletePage; - export type Params = IDeletePageParams; - export type Return = IDeletePageReturn; + export type Interface = IDeletePage; + export type Params = IDeletePageParams; + export type Return = IDeletePageReturn; } ``` @@ -192,21 +193,21 @@ import { DeletePageUseCase } from "webiny/api/website-builder/page"; import { DeletePage } from "./abstractions.js"; class DeletePageImpl implements DeletePage.Interface { - constructor(private deletePageUseCase: DeletePageUseCase.Interface) {} - - async execute({ id }: DeletePage.Params): DeletePage.Return { - const result = await this.deletePageUseCase.execute({ - id - }); - if (result.isFail()) { - throw result.error; - } + constructor(private deletePageUseCase: DeletePageUseCase.Interface) {} + + async execute({ id }: DeletePage.Params): DeletePage.Return { + const result = await this.deletePageUseCase.execute({ + id + }); + if (result.isFail()) { + throw result.error; } + } } export default DeletePage.createImplementation({ - implementation: DeletePageImpl, - dependencies: [DeletePageUseCase] + implementation: DeletePageImpl, + dependencies: [DeletePageUseCase] }); ``` @@ -217,21 +218,21 @@ import { createAbstraction } from "webiny/api"; import type { PublishPageUseCase } from "webiny/api/website-builder/page"; export interface IPublishPageParams { - id: string; + id: string; } export type IPublishPageReturn = Promise; export interface IPublishPage { - execute(params: IPublishPageParams): IPublishPageReturn; + execute(params: IPublishPageParams): IPublishPageReturn; } export const PublishPage = createAbstraction("PublishPage"); export namespace PublishPage { - export type Interface = IPublishPage; - export type Params = IPublishPageParams; - export type Return = IPublishPageReturn; + export type Interface = IPublishPage; + export type Params = IPublishPageParams; + export type Return = IPublishPageReturn; } ``` @@ -240,22 +241,22 @@ import { PublishPageUseCase } from "webiny/api/website-builder/page"; import { PublishPage } from "./abstractions.js"; class PublishPageImpl implements PublishPage.Interface { - constructor(private publishPageUseCase: PublishPageUseCase.Interface) {} - - async execute({ id }: PublishPage.Params): PublishPage.Return { - const result = await this.publishPageUseCase.execute({ - id - }); - if (result.isFail()) { - throw result.error; - } - return result.value; + constructor(private publishPageUseCase: PublishPageUseCase.Interface) {} + + async execute({ id }: PublishPage.Params): PublishPage.Return { + const result = await this.publishPageUseCase.execute({ + id + }); + if (result.isFail()) { + throw result.error; } + return result.value; + } } export default PublishPage.createImplementation({ - implementation: PublishPageImpl, - dependencies: [PublishPageUseCase] + implementation: PublishPageImpl, + dependencies: [PublishPageUseCase] }); ``` @@ -266,21 +267,21 @@ import { createAbstraction } from "webiny/api"; import type { UnpublishPageUseCase } from "webiny/api/website-builder/page"; export interface IUnpublishPageParams { - id: string; + id: string; } export type IUnpublishPageReturn = Promise; export interface IUnpublishPage { - execute(params: IUnpublishPageParams): IUnpublishPageReturn; + execute(params: IUnpublishPageParams): IUnpublishPageReturn; } export const UnpublishPage = createAbstraction("UnpublishPage"); export namespace UnpublishPage { - export type Interface = IUnpublishPage; - export type Params = IUnpublishPageParams; - export type Return = IUnpublishPageReturn; + export type Interface = IUnpublishPage; + export type Params = IUnpublishPageParams; + export type Return = IUnpublishPageReturn; } ``` @@ -289,22 +290,22 @@ import { UnpublishPageUseCase } from "webiny/api/website-builder/page"; import { UnpublishPage } from "./abstractions.js"; class UnpublishPageImpl implements UnpublishPage.Interface { - constructor(private unpublishPageUseCase: UnpublishPageUseCase.Interface) {} - - async execute({ id }: UnpublishPage.Params): UnpublishPage.Return { - const result = await this.unpublishPageUseCase.execute({ - id - }); - if (result.isFail()) { - throw result.error; - } - return result.value; + constructor(private unpublishPageUseCase: UnpublishPageUseCase.Interface) {} + + async execute({ id }: UnpublishPage.Params): UnpublishPage.Return { + const result = await this.unpublishPageUseCase.execute({ + id + }); + if (result.isFail()) { + throw result.error; } + return result.value; + } } export default UnpublishPage.createImplementation({ - implementation: UnpublishPageImpl, - dependencies: [UnpublishPageUseCase] + implementation: UnpublishPageImpl, + dependencies: [UnpublishPageUseCase] }); ``` @@ -315,21 +316,21 @@ import { createAbstraction } from "webiny/api"; import type { DuplicatePageUseCase } from "webiny/api/website-builder/page"; export interface IDuplicatePageParams { - id: string; + id: string; } export type IDuplicatePageReturn = Promise; export interface IDuplicatePage { - execute(params: IDuplicatePageParams): IDuplicatePageReturn; + execute(params: IDuplicatePageParams): IDuplicatePageReturn; } export const DuplicatePage = createAbstraction("DuplicatePage"); export namespace DuplicatePage { - export type Interface = IDuplicatePage; - export type Params = IDuplicatePageParams; - export type Return = IDuplicatePageReturn; + export type Interface = IDuplicatePage; + export type Params = IDuplicatePageParams; + export type Return = IDuplicatePageReturn; } ``` @@ -338,22 +339,22 @@ import { DuplicatePageUseCase } from "webiny/api/website-builder/page"; import { DuplicatePage } from "./abstractions.js"; class DuplicatePageImpl implements DuplicatePage.Interface { - constructor(private duplicatePageUseCase: DuplicatePageUseCase.Interface) {} - - async execute({ id }: DuplicatePage.Params): DuplicatePage.Return { - const result = await this.duplicatePageUseCase.execute({ - id - }); - if (result.isFail()) { - throw result.error; - } - return result.value; + constructor(private duplicatePageUseCase: DuplicatePageUseCase.Interface) {} + + async execute({ id }: DuplicatePage.Params): DuplicatePage.Return { + const result = await this.duplicatePageUseCase.execute({ + id + }); + if (result.isFail()) { + throw result.error; } + return result.value; + } } export default DuplicatePage.createImplementation({ - implementation: DuplicatePageImpl, - dependencies: [DuplicatePageUseCase] + implementation: DuplicatePageImpl, + dependencies: [DuplicatePageUseCase] }); ``` @@ -364,22 +365,22 @@ import { createAbstraction } from "webiny/api"; import type { MovePageUseCase } from "webiny/api/website-builder/page"; export interface IMovePageParams { - id: string; - folderId: string; + id: string; + folderId: string; } export type IMovePageReturn = Promise; export interface IMovePage { - execute(params: IMovePageParams): IMovePageReturn; + execute(params: IMovePageParams): IMovePageReturn; } export const MovePage = createAbstraction("MovePage"); export namespace MovePage { - export type Interface = IMovePage; - export type Params = IMovePageParams; - export type Return = IMovePageReturn; + export type Interface = IMovePage; + export type Params = IMovePageParams; + export type Return = IMovePageReturn; } ``` @@ -388,23 +389,23 @@ import { MovePageUseCase } from "webiny/api/website-builder/page"; import { MovePage } from "./abstractions.js"; class MovePageImpl implements MovePage.Interface { - constructor(private movePageUseCase: MovePageUseCase.Interface) {} - - async execute({ id, folderId }: MovePage.Params): MovePage.Return { - const result = await this.movePageUseCase.execute({ - id, - folderId - }); - if (result.isFail()) { - throw result.error; - } - return result.value; + constructor(private movePageUseCase: MovePageUseCase.Interface) {} + + async execute({ id, folderId }: MovePage.Params): MovePage.Return { + const result = await this.movePageUseCase.execute({ + id, + folderId + }); + if (result.isFail()) { + throw result.error; } + return result.value; + } } export default MovePage.createImplementation({ - implementation: MovePageImpl, - dependencies: [MovePageUseCase] + implementation: MovePageImpl, + dependencies: [MovePageUseCase] }); ``` @@ -415,21 +416,21 @@ import { createAbstraction } from "webiny/api"; import type { GetPageByIdUseCase } from "webiny/api/website-builder/page"; export interface IGetPageByIdParams { - id: string; + id: string; } export type IGetPageByIdReturn = Promise; export interface IGetPageById { - execute(params: IGetPageByIdParams): IGetPageByIdReturn; + execute(params: IGetPageByIdParams): IGetPageByIdReturn; } export const GetPageById = createAbstraction("GetPageById"); export namespace GetPageById { - export type Interface = IGetPageById; - export type Params = IGetPageByIdParams; - export type Return = IGetPageByIdReturn; + export type Interface = IGetPageById; + export type Params = IGetPageByIdParams; + export type Return = IGetPageByIdReturn; } ``` @@ -438,20 +439,20 @@ import { GetPageByIdUseCase } from "webiny/api/website-builder/page"; import { GetPageById } from "./abstractions.js"; class GetPageByIdImpl implements GetPageById.Interface { - constructor(private getPageByIdUseCase: GetPageByIdUseCase.Interface) {} - - async execute({ id }: GetPageById.Params): GetPageById.Return { - const result = await this.getPageByIdUseCase.execute(id); - if (result.isFail()) { - return null; - } - return result.value; + constructor(private getPageByIdUseCase: GetPageByIdUseCase.Interface) {} + + async execute({ id }: GetPageById.Params): GetPageById.Return { + const result = await this.getPageByIdUseCase.execute(id); + if (result.isFail()) { + return null; } + return result.value; + } } export default GetPageById.createImplementation({ - implementation: GetPageByIdImpl, - dependencies: [GetPageByIdUseCase] + implementation: GetPageByIdImpl, + dependencies: [GetPageByIdUseCase] }); ``` @@ -462,21 +463,21 @@ import { createAbstraction } from "webiny/api"; import type { GetPageByPathUseCase } from "webiny/api/website-builder/page"; export interface IGetPageByPathParams { - path: string; + path: string; } export type IGetPageByPathReturn = Promise; export interface IGetPageByPath { - execute(params: IGetPageByPathParams): IGetPageByPathReturn; + execute(params: IGetPageByPathParams): IGetPageByPathReturn; } export const GetPageByPath = createAbstraction("GetPageByPath"); export namespace GetPageByPath { - export type Interface = IGetPageByPath; - export type Params = IGetPageByPathParams; - export type Return = IGetPageByPathReturn; + export type Interface = IGetPageByPath; + export type Params = IGetPageByPathParams; + export type Return = IGetPageByPathReturn; } ``` @@ -485,20 +486,20 @@ import { GetPageByPathUseCase } from "webiny/api/website-builder/page"; import { GetPageByPath } from "./abstractions.js"; class GetPageByPathImpl implements GetPageByPath.Interface { - constructor(private getPageByPathUseCase: GetPageByPathUseCase.Interface) {} - - async execute({ path }: GetPageByPath.Params): GetPageByPath.Return { - const result = await this.getPageByPathUseCase.execute(path); - if (result.isFail()) { - return null; - } - return result.value; + constructor(private getPageByPathUseCase: GetPageByPathUseCase.Interface) {} + + async execute({ path }: GetPageByPath.Params): GetPageByPath.Return { + const result = await this.getPageByPathUseCase.execute(path); + if (result.isFail()) { + return null; } + return result.value; + } } export default GetPageByPath.createImplementation({ - implementation: GetPageByPathImpl, - dependencies: [GetPageByPathUseCase] + implementation: GetPageByPathImpl, + dependencies: [GetPageByPathUseCase] }); ``` @@ -509,21 +510,21 @@ import { createAbstraction } from "webiny/api"; import type { GetPageRevisionsUseCase } from "webiny/api/website-builder/page"; export interface IGetPageRevisionsParams { - id: string; + id: string; } export type IGetPageRevisionsReturn = Promise; export interface IGetPageRevisions { - execute(params: IGetPageRevisionsParams): IGetPageRevisionsReturn; + execute(params: IGetPageRevisionsParams): IGetPageRevisionsReturn; } export const GetPageRevisions = createAbstraction("GetPageRevisions"); export namespace GetPageRevisions { - export type Interface = IGetPageRevisions; - export type Params = IGetPageRevisionsParams; - export type Return = IGetPageRevisionsReturn; + export type Interface = IGetPageRevisions; + export type Params = IGetPageRevisionsParams; + export type Return = IGetPageRevisionsReturn; } ``` @@ -532,20 +533,20 @@ import { GetPageRevisionsUseCase } from "webiny/api/website-builder/page"; import { GetPageRevisions } from "./abstractions.js"; class GetPageRevisionsImpl implements GetPageRevisions.Interface { - constructor(private getPageRevisionsUseCase: GetPageRevisionsUseCase.Interface) {} - - async execute({ id }: GetPageRevisions.Params): GetPageRevisions.Return { - const result = await this.getPageRevisionsUseCase.execute(id); - if (result.isFail()) { - return []; - } - return result.value; + constructor(private getPageRevisionsUseCase: GetPageRevisionsUseCase.Interface) {} + + async execute({ id }: GetPageRevisions.Params): GetPageRevisions.Return { + const result = await this.getPageRevisionsUseCase.execute(id); + if (result.isFail()) { + return []; } + return result.value; + } } export default GetPageRevisions.createImplementation({ - implementation: GetPageRevisionsImpl, - dependencies: [GetPageRevisionsUseCase] + implementation: GetPageRevisionsImpl, + dependencies: [GetPageRevisionsUseCase] }); ``` @@ -556,21 +557,21 @@ import { createAbstraction, Result } from "webiny/api"; import type { ListPagesUseCase } from "webiny/api/website-builder/page"; export interface IListPagesParams { - params: ListPagesUseCase.Params; + params: ListPagesUseCase.Params; } export type IListPagesReturn = Promise>; export interface IListPages { - execute(params: IListPagesParams): IListPagesReturn; + execute(params: IListPagesParams): IListPagesReturn; } export const ListPages = createAbstraction("ListPages"); export namespace ListPages { - export type Interface = IListPages; - export type Params = IListPagesParams; - export type Return = IListPagesReturn; + export type Interface = IListPages; + export type Params = IListPagesParams; + export type Return = IListPagesReturn; } ``` @@ -579,21 +580,21 @@ import { ListPagesUseCase } from "webiny/api/website-builder/page"; import { ListPages } from "./abstractions.js"; class ListPagesImpl implements ListPages.Interface { - constructor(private listPagesUseCase: ListPagesUseCase.Interface) {} - - async execute({ params }: ListPages.Params): ListPages.Return { - const result = await this.listPagesUseCase.execute({ - ...params - }); - if (result.isFail()) { - throw result.error; - } - return result.value; + constructor(private listPagesUseCase: ListPagesUseCase.Interface) {} + + async execute({ params }: ListPages.Params): ListPages.Return { + const result = await this.listPagesUseCase.execute({ + ...params + }); + if (result.isFail()) { + throw result.error; } + return result.value; + } } export default ListPages.createImplementation({ - implementation: ListPagesImpl, - dependencies: [ListPagesUseCase] + implementation: ListPagesImpl, + dependencies: [ListPagesUseCase] }); ``` diff --git a/docs/developer-docs/6.x/website-builder/use-case/redirects.mdx b/docs/developer-docs/6.x/website-builder/use-case/redirects.mdx index 8f51fa7e3..b15829e49 100644 --- a/docs/developer-docs/6.x/website-builder/use-case/redirects.mdx +++ b/docs/developer-docs/6.x/website-builder/use-case/redirects.mdx @@ -4,7 +4,7 @@ title: Redirect Use Cases description: Use cases for redirects in the Website Builder. --- -import {Alert} from "@/components/Alert"; +import { Alert } from "@/components/Alert"; diff --git a/docs/handbook/company/deciding-what-features-to-build.mdx b/docs/handbook/company/deciding-what-features-to-build.mdx index d45f47969..43b86dd24 100644 --- a/docs/handbook/company/deciding-what-features-to-build.mdx +++ b/docs/handbook/company/deciding-what-features-to-build.mdx @@ -7,15 +7,15 @@ slug: deciding-what-features-to-build Webiny is a platform and it's made up of multiple applications, where each has several features. At Webiny we also consider our development framework a part of that platform and we treat it in the same way as any other part of the product. Treating the development framework as a product ensures our focus and commitment to developer experience is always at the core of everything we do. -The process described below is how we go about building our roadmap and prioritizing features, regardless if we're talking about a new application feature or a new DX feature inside the development framework. +The process described below is how we go about building our roadmap and prioritizing features, regardless if we're talking about a new application feature or a new DX feature inside the development framework. ## The process -We run a structured process every quarter around how we go about building our roadmap. It all starts with customer interviews where we gather their feedback about the current state of things, the good and the bad (there's no such thing as a perfect product). We then expand the conversation by learning about their upcoming projects and the potential challenges they might have in delivering them. Finally, we ask what other features the customers would like to see in Webiny. +We run a structured process every quarter around how we go about building our roadmap. It all starts with customer interviews where we gather their feedback about the current state of things, the good and the bad (there's no such thing as a perfect product). We then expand the conversation by learning about their upcoming projects and the potential challenges they might have in delivering them. Finally, we ask what other features the customers would like to see in Webiny. From a typical conversation, we can extract a lot of information and translate those into potential features. By doing this across multiple of our customers we can start to see patterns and identify the most important features that we should build next. Note that we mainly deal with enterprise customers so we're not talking about thousands of interviews, and it's something that we're able to scale as we grow. -Once we've identified certain patterns are can shape them into features, we might go back to those customers and again revalidate that the feature definition is correct, and we also ask them how important this feature is to them. For that, we use the [MoSCoW method](https://en.wikipedia.org/wiki/MoSCoW_method), where we ask the customer to rate the feature as Must have, Should have, Could have, or Won't have. +Once we've identified certain patterns are can shape them into features, we might go back to those customers and again revalidate that the feature definition is correct, and we also ask them how important this feature is to them. For that, we use the [MoSCoW method](https://en.wikipedia.org/wiki/MoSCoW_method), where we ask the customer to rate the feature as Must have, Should have, Could have, or Won't have. Each of these ratings is then given a score. If multiple customers say that a certain feature is a Must Have, it will quickly climb up to the top of our priority list. Eventually, we'll just sort all of the features by their score and this then becomes our priority list. @@ -23,7 +23,7 @@ But of course, the list can sometimes have a lot more items than what we're able ## Customer-driven roadmap -Things like agile, sprints, and how we plan things, it's not the main point we want to demonstrate here. The main point is that we're building a customer-driven roadmap. We're not building features that we think are cool, we're building features that our customers need. Those features help our customers deliver their projects faster and with less effort as part of that effort is already taken care of by our side. +Things like agile, sprints, and how we plan things, it's not the main point we want to demonstrate here. The main point is that we're building a customer-driven roadmap. We're not building features that we think are cool, we're building features that our customers need. Those features help our customers deliver their projects faster and with less effort as part of that effort is already taken care of by our side. Such an approach brings us very close to our customers. So close in fact that several customers now invite us to their planning sessions, they share their long-term plans with us, they plan the delivery of their projects together with us, and we become an extension of their team. It's a very unique relationship and it's something that we're very proud of. diff --git a/docs/handbook/company/how-we-ensure-quality.mdx b/docs/handbook/company/how-we-ensure-quality.mdx index f1f89cb94..237bb236b 100644 --- a/docs/handbook/company/how-we-ensure-quality.mdx +++ b/docs/handbook/company/how-we-ensure-quality.mdx @@ -11,43 +11,39 @@ Because we primarily work with larger organizations, their needs around reliabil It's not enough to say "Our product is of high quality", without being able to demonstrate what that means. Quality is not a thing, it is a result of doing the right thing, the right way. And when it comes to what that means, to us it means knowing what our customers expect from us and delivering on that. It also means that the product meets high standards of reliability and security. It means the product works as expected, and it means it works all the time in a performant way. We talk about those as [our principles](/handbook/company/why-webiny-exists#rooted-in-the-first-principles), and we adhere to them. -Let's take a deeper dive and see what this means in practice. +Let's take a deeper dive and see what this means in practice. Firstly, [we work very closely with our customers](/handbook/company/deciding-what-features-to-build#customer-driven-roadmap) and this allows us to have a deep understanding of what their needs are from our product. This way we know exactly what new features to build and what existing features to improve. Our customers not only influence our roadmap but take an active role in helping us build the required features. It's one of our [core values](/handbook/company/our-values) to work together with our customers in a transparent and open way. We don't focus on just the big things. Because we're pretty much chatting with our customers and their engineering teams daily, we see the small things, the irks that annoy them, but not enough to call it a bug or to raise a feature request. We'll fix those things for them too. - ## Reliability & Performance -Reliability can often be a challenge with self-hosted products. This is mainly because it's the customer's internal team that needs to have a great understanding of the internals of the product to correctly size and configure the necessary infrastructure. Then there are the undetermined factors, such as peak traffic usage, often caused by flash crowds, attacks, etc. - -To ensure Webiny stays reliable at all times and that it accounts for all the above-mentioned factors we've taken several measures which are embedded at the core of our product. A big part of why we're able to achieve high levels of reliability and performance is the fact that we decided to build our platform on the foundation of serverless infrastructure. You can read in more detail about why we chose serverless, and the benefits we got from such a decision in the [Why We Choose Serverless Infrastructure](/handbook/company/why-we-chose-serverless) article. +Reliability can often be a challenge with self-hosted products. This is mainly because it's the customer's internal team that needs to have a great understanding of the internals of the product to correctly size and configure the necessary infrastructure. Then there are the undetermined factors, such as peak traffic usage, often caused by flash crowds, attacks, etc. +To ensure Webiny stays reliable at all times and that it accounts for all the above-mentioned factors we've taken several measures which are embedded at the core of our product. A big part of why we're able to achieve high levels of reliability and performance is the fact that we decided to build our platform on the foundation of serverless infrastructure. You can read in more detail about why we chose serverless, and the benefits we got from such a decision in the [Why We Choose Serverless Infrastructure](/handbook/company/why-we-chose-serverless) article. -## Security +## Security - -When it comes to security, there is just no compromise. We documented our security posture in a separate article [here](/overview/features/security). You're never done with security, it's a continuous process, and we're constantly improving. We have a conscious stance on security, and we have many measurements in place to ensure we're always on top of things. From acquiring SOC 2 compliance to using only hardened infrastructure and services to having tools that are constantly scanning our codebase for vulnerabilities, including also our 3rd party dependencies. +When it comes to security, there is just no compromise. We documented our security posture in a separate article [here](/overview/features/security). You're never done with security, it's a continuous process, and we're constantly improving. We have a conscious stance on security, and we have many measurements in place to ensure we're always on top of things. From acquiring SOC 2 compliance to using only hardened infrastructure and services to having tools that are constantly scanning our codebase for vulnerabilities, including also our 3rd party dependencies. Security posture for us has 2 sides. One is the security of the infrastructure our product runs on, and the other is the security of the product itself, including the security features that enable our users to configure the product to their needs. Infrastructure security is something we take very seriously. Many parts go into this, from how things are encrypted, to giving customers the ability to bring in their current security features, like private VPC's, Lambda authorizers, and more. If you're interested in learning more about this, please read our [Security](/overview/features/security) article. -When it comes to the security features of the application itself. We tick the regular boxes like having the ability to bring your own IdP, the ability to customize both the authentication and the authorization workflows, and more. We also support features like roles, API keys, and the ability to manage users inside teams. - -We go further than that. We allow users to structure their content, be that headless CMS entries, Page Builder pages, or assets, into folders and then assign on a folder level who can access the items inside that folder. Files inside our File Manager can be marked as "private", so even if you share a direct link with someone, they will not be able to access that file unless they are authorized to access it. +When it comes to the security features of the application itself. We tick the regular boxes like having the ability to bring your own IdP, the ability to customize both the authentication and the authorization workflows, and more. We also support features like roles, API keys, and the ability to manage users inside teams. -Our security posture and features keep evolving as our users push the product further and share even more complex use cases with us. +We go further than that. We allow users to structure their content, be that headless CMS entries, Page Builder pages, or assets, into folders and then assign on a folder level who can access the items inside that folder. Files inside our File Manager can be marked as "private", so even if you share a direct link with someone, they will not be able to access that file unless they are authorized to access it. +Our security posture and features keep evolving as our users push the product further and share even more complex use cases with us. ## Framework as a product -Customization is at the core of our product, it is one of our most important values. We have built a product that is made to be extended and customized in a maintainable way. This means customers get the product they want, not a product that they are willing to have as a set of compromises along the road. +Customization is at the core of our product, it is one of our most important values. We have built a product that is made to be extended and customized in a maintainable way. This means customers get the product they want, not a product that they are willing to have as a set of compromises along the road. -Getting the balance right between what can be customized and to what extent is very hard. We have spent countless months designing and researching how to make this possible, for a product as powerful and complex as Webiny. We believe we have found that balance, but it's not perfect and we're constantly improving. +Getting the balance right between what can be customized and to what extent is very hard. We have spent countless months designing and researching how to make this possible, for a product as powerful and complex as Webiny. We believe we have found that balance, but it's not perfect and we're constantly improving. -However, without this ability to adapt and customize the product, we would not be able to truly and fully satisfy the needs of our customers. We would be like many other products on the market that are good, but not great. And this just isn't a compromise we are willing to make. +However, without this ability to adapt and customize the product, we would not be able to truly and fully satisfy the needs of our customers. We would be like many other products on the market that are good, but not great. And this just isn't a compromise we are willing to make. With Webiny you are guaranteed to be able to build a product that is truly yours, one that exactly fits your needs. We stand by that! Customization is not an afterthought for us, it's embedded into every single part of the product, and it is the foundation of the product itself. This, we believe, is a key aspect of quality. diff --git a/docs/handbook/company/how-we-got-here.mdx b/docs/handbook/company/how-we-got-here.mdx index 4d0e3850b..15b443aa5 100644 --- a/docs/handbook/company/how-we-got-here.mdx +++ b/docs/handbook/company/how-we-got-here.mdx @@ -9,31 +9,31 @@ slug: how-we-got-here Pavel and Sven were attending the uni together and that's where they met. Quickly they got involved in different side projects to earn some extra cash. PHP was the leading language of the time and both Pavel and Sven had way more hair on their heads than today. -A few years into the side gigs Sven decided to form a company and Pavel was the first employee. +A few years into the side gigs Sven decided to form a company and Pavel was the first employee. ## Web agency - Est. 2009 -Before Webiny became the product and company you know today, its beginning was quite different. In 2008 Webiny was born in London as a web agency whose core business was focusing on building websites and applications for customers around the world. +Before Webiny became the product and company you know today, its beginning was quite different. In 2008 Webiny was born in London as a web agency whose core business was focusing on building websites and applications for customers around the world. -Ten years and many projects later, Pavel and Sven gathered knowledge about different CMS options on the market, and what worked and what didn't. +Ten years and many projects later, Pavel and Sven gathered knowledge about different CMS options on the market, and what worked and what didn't. ## The product launch - 2018 -Around 2017/18 Sven started to play around with serverless technologies, which were still in very early stages. That vision of serverless, in how we saw it then, and how we still see it today remained the same - *serverless is the future of infrastructure*. +Around 2017/18 Sven started to play around with serverless technologies, which were still in very early stages. That vision of serverless, in how we saw it then, and how we still see it today remained the same - _serverless is the future of infrastructure_. -Many sleepless nights later, Webiny - Serverless CMS was born and launched on Product Hunt where it became the second most-voted product of the day, and a day later it ended up trending on hacker news. This was enough to open a new chapter. +Many sleepless nights later, Webiny - Serverless CMS was born and launched on Product Hunt where it became the second most-voted product of the day, and a day later it ended up trending on hacker news. This was enough to open a new chapter. ## Pre-seed - 2019 -With the initial traction, Webiny raised a pre-seed round from Episode 1, a London-based venture capital fund. With that round the team grew to 5 people and the product added lot more capabilities, especially around the framework which allowed us to build additional products like the Headless CMS and get even more traction. +With the initial traction, Webiny raised a pre-seed round from Episode 1, a London-based venture capital fund. With that round the team grew to 5 people and the product added lot more capabilities, especially around the framework which allowed us to build additional products like the Headless CMS and get even more traction. ## YCombinator / Seed round - 2021 -Webiny was selected to join the world's leading business accelerator. The same place where companies like Airbnb, Dropbox, Stripe and many others started. On the back of the new growth and the YC demo day, Webiny raised a $3.5M investment round led by M12 - Microsoft Ventures, with participation from many other investors, including Samsung Next. +Webiny was selected to join the world's leading business accelerator. The same place where companies like Airbnb, Dropbox, Stripe and many others started. On the back of the new growth and the YC demo day, Webiny raised a $3.5M investment round led by M12 - Microsoft Ventures, with participation from many other investors, including Samsung Next. ## Enterprise CMS - 2022 -In 2022 Webiny decided to focus on the CMS proposition targeting enterprise needs. We positioned our base value pillars focusing on our unique and defensible propositions like privacy, data ownership, open-source product customisability and more. This resonated extremely well with our target audience as on the other end they were forced into SaaS products that were taking away their data ownership and locking them into products that couldn't be adopted to fit their needs. +In 2022 Webiny decided to focus on the CMS proposition targeting enterprise needs. We positioned our base value pillars focusing on our unique and defensible propositions like privacy, data ownership, open-source product customisability and more. This resonated extremely well with our target audience as on the other end they were forced into SaaS products that were taking away their data ownership and locking them into products that couldn't be adopted to fit their needs. ## Serverless open-source DXP - 2024 diff --git a/docs/handbook/company/how-we-make-money.mdx b/docs/handbook/company/how-we-make-money.mdx index 42a9b39b2..60b9c0c34 100644 --- a/docs/handbook/company/how-we-make-money.mdx +++ b/docs/handbook/company/how-we-make-money.mdx @@ -8,11 +8,12 @@ slug: how-we-make-money The first thing to get out of the way is that open source doesn't mean "free" as in "I don't need to pay for it". It means "free" as in "I have the freedom to use it, study it, modify it, and redistribute it" (within the terms defined in its license). > When we call software “free,” we mean that it respects the users' essential freedoms: the freedom to run it, to study and change it, and to redistribute copies with or without changes. This is a matter of freedom, not price, so think of “free speech,” not “free beer.” + - by Richard Stallman ([Why Open Source Misses the Point of Free Software](https://www.gnu.org/philosophy/open-source-misses-the-point.en.html)) ## Our business model -We are a for-profit company. We make money by selling licenses to our software. We also offer professional services to help our customers with their projects and we offer support to make sure that if there are issues, we're there to help resolve them. +We are a for-profit company. We make money by selling licenses to our software. We also offer professional services to help our customers with their projects and we offer support to make sure that if there are issues, we're there to help resolve them. We have a dual-license model. This means that we offer our software under two different licenses: a commercial license and an open-source license. You can read more about our license model and pricing tiers [here](https://www.webiny.com/pricing). @@ -24,9 +25,9 @@ We love technical selling, so even if you start off talking business, we love to ## We are either the best solution for you, or we walk away -Our customers deeply care about at least four of the items from the list below: +Our customers deeply care about at least four of the items from the list below: -- ** Ability to control and customize the product ** - We are an open-source platform and we come with a development framework. This means that you have full control over the product and you can customize it to your needs. +- ** Ability to control and customize the product ** - We are an open-source platform and we come with a development framework. This means that you have full control over the product and you can customize it to your needs. - ** Data ownership and governance ** - We are purposely built to be a self-hosted solution. This means that you own your data and you can host it wherever you want. @@ -38,7 +39,7 @@ Our customers deeply care about at least four of the items from the list below: - ** Developer experience ** - We are a developer-first company. This means that we put a lot of effort into making sure that our product is easy to use and that it provides a great developer experience. Webiny is also AI-programmable — it ships with an MCP server and typed extension patterns that allow AI coding agents to generate correct, platform-native code. For teams using AI-assisted development, this dramatically accelerates customization work. -- ** Future-proof technology stack ** - We are built on top of [React](https://reactjs.org/), [GraphQL](https://graphql.org/), [Node.js](https://nodejs.org/en/), [Typescript](https://www.typescriptlang.org/) and [AWS Serverless](https://aws.amazon.com/serverless). This means that you are building your application on top of the most popular and future-proof technologies available today. +- ** Future-proof technology stack ** - We are built on top of [React](https://reactjs.org/), [GraphQL](https://graphql.org/), [Node.js](https://nodejs.org/en/), [Typescript](https://www.typescriptlang.org/) and [AWS Serverless](https://aws.amazon.com/serverless). This means that you are building your application on top of the most popular and future-proof technologies available today. - ** Environmentally friendly ** - Running your website on VMs is like having a car with its engine constantly running in an idle state. Just so you could drive it for the 2h of your daily commute. Webiny runs on serverless infrastructure. There's no idle state, your machines are only running when they have something to do. The rest of the time they don't idle, waste energy and pump out CO2. This means that you are not only saving money, but you are also helping the environment. @@ -46,7 +47,7 @@ In case you don't feel these things are resonating strongly with you, we are pro ## Don't let the price model get in the way -Our default pricing model for our enterprise tier is based on several factors, like how many users will be using the system to manage content, how many projects or websites will be built, which features are needed, etc. This works in most cases, but we also understand that some customers prefer a different pricing model. +Our default pricing model for our enterprise tier is based on several factors, like how many users will be using the system to manage content, how many projects or websites will be built, which features are needed, etc. This works in most cases, but we also understand that some customers prefer a different pricing model. Examples like customers having lots of external contributors or customers that deal with user-generated content, where the number of users is not known in advance, are cases where our default pricing model doesn't work. In these cases, we are happy to work with you to find a pricing model that works for both parties. diff --git a/docs/handbook/company/how-we-make-users-happy.mdx b/docs/handbook/company/how-we-make-users-happy.mdx index 8cc46ead2..30d2c2506 100644 --- a/docs/handbook/company/how-we-make-users-happy.mdx +++ b/docs/handbook/company/how-we-make-users-happy.mdx @@ -7,7 +7,7 @@ slug: how-we-make-users-happy ## Fanatical customer support -A product is not whole without fanatical customer support, and when we say fanatical, we truly mean it. It's been consistently the most praised thing from all of our customers. +A product is not whole without fanatical customer support, and when we say fanatical, we truly mean it. It's been consistently the most praised thing from all of our customers. Our support is provided by the core engineering team that built the product, so they know it inside out. The time to resolve a query and get you unblocked couldn't be any shorter. But also by providing that feedback to the team that built the product and not some support engineer, the perception of the feedback is different, it's more personal, and it is taken more seriously, and thus is acted upon faster. @@ -25,18 +25,18 @@ This makes our customers faster. Teams that used to spend days scaffolding a cus ## Strategic alignment on the roadmap -The customers that work with us are in for the long run. They are looking for a partner that will help them grow their business, and they are looking for a partner that will listen to their needs and act on them. We talk to our customers regularly, and we listen to their needs. We then align those needs with our product roadmap and make sure that we are building the right things for them. On average 90% of our roadmap items are direct requests from our customers. +The customers that work with us are in for the long run. They are looking for a partner that will help them grow their business, and they are looking for a partner that will listen to their needs and act on them. We talk to our customers regularly, and we listen to their needs. We then align those needs with our product roadmap and make sure that we are building the right things for them. On average 90% of our roadmap items are direct requests from our customers. **Does that mean you'll build custom features just for me?** Not necessarily. We will only commit items to the roadmap that we believe will benefit the majority of our customers, we don't do one-offs. But we will always make sure that we understand your needs and that we are building the right things for you. -In case we don't commit your item to the roadmap, we will always provide you with a solution that will get you unblocked. Typically this means adding new extension points, hooks and lifecycle events, followed by a set of tutorials that will enable your team to implement the feature themselves. We'll always have your back! +In case we don't commit your item to the roadmap, we will always provide you with a solution that will get you unblocked. Typically this means adding new extension points, hooks and lifecycle events, followed by a set of tutorials that will enable your team to implement the feature themselves. We'll always have your back! ## A truly composable product -There are many products on the market that use and practically abuse the word "composable" from the marketing side. You've got the legacy DXP platforms calling themselves composable, but the only composing you can do is within their product portfolio. +There are many products on the market that use and practically abuse the word "composable" from the marketing side. You've got the legacy DXP platforms calling themselves composable, but the only composing you can do is within their product portfolio. -On the other end, you've got the newer SaaS options which are also trying to take the piece of the pie, while they are a single-purpose solution, and you have zero ability to extend it. You are limited to only a few options they provide to integrate with other tools. +On the other end, you've got the newer SaaS options which are also trying to take the piece of the pie, while they are a single-purpose solution, and you have zero ability to extend it. You are limited to only a few options they provide to integrate with other tools. Users don't want that anymore. They want to be able to pick and choose the best tools for their needs, and they want to be able to integrate them in a way that makes sense for them. We have built our product with that in mind. We've built it to be truly composable. We've built it to be the glue that holds all your tools together. diff --git a/docs/handbook/company/our-values.mdx b/docs/handbook/company/our-values.mdx index ab7217377..62d92d4b2 100644 --- a/docs/handbook/company/our-values.mdx +++ b/docs/handbook/company/our-values.mdx @@ -12,23 +12,23 @@ Simply put, [our purpose](/handbook/company/why-webiny-exists) is the end destin ## Our values - **Innovate, iterate and improve** - - We are never satisfied with the status quo. - - We are always looking for a better way of doing things. - - We are not afraid to change. - - Question everything, including best practices. + - We are never satisfied with the status quo. + - We are always looking for a better way of doing things. + - We are not afraid to change. + - Question everything, including best practices. -This is about striving for innovation, not settling for the status quo. If there is a better way of doing something, let's try it out. If we are not happy with something, let's change it. It's not just innovation on the technical side, we're bringing innovation to how we do sales, pricing, how we operate as a team, and how we support our customers and partners. Our move to become AI-programmable is a prime example — rather than treating AI as a bolt-on feature, we redesigned the developer experience around it, shipping an MCP server and structured skills that let AI agents work with Webiny as naturally as a human developer would. All to do better and constantly evolve and grow. +This is about striving for innovation, not settling for the status quo. If there is a better way of doing something, let's try it out. If we are not happy with something, let's change it. It's not just innovation on the technical side, we're bringing innovation to how we do sales, pricing, how we operate as a team, and how we support our customers and partners. Our move to become AI-programmable is a prime example — rather than treating AI as a bolt-on feature, we redesigned the developer experience around it, shipping an MCP server and structured skills that let AI agents work with Webiny as naturally as a human developer would. All to do better and constantly evolve and grow. - **We care** - - About the individual, the customer, the employee, the partner, the user, everyone has their own and different needs. - - About enabling individual experiences and unlocking creativity. - - About our customer's business and what truly matters to them. We adapt our practices to make each customer successful. + - About the individual, the customer, the employee, the partner, the user, everyone has their own and different needs. + - About enabling individual experiences and unlocking creativity. + - About our customer's business and what truly matters to them. We adapt our practices to make each customer successful. -To care for others, we need to care for them as individuals. Every one of our customers is different, in terms of how they do their business, to how they wish to use our product. We care about this individually as it's what makes them who they are. We respect it, and so should our product. This is why we created a product that's so highly customizable, as this is the only way for customers to truly make it feel their own. We also go beyond the product, we care about how they conduct their business, what value their business creates for their end customers, and what their customers care about. All in the service to truly know our customers and to help them succeed. +To care for others, we need to care for them as individuals. Every one of our customers is different, in terms of how they do their business, to how they wish to use our product. We care about this individually as it's what makes them who they are. We respect it, and so should our product. This is why we created a product that's so highly customizable, as this is the only way for customers to truly make it feel their own. We also go beyond the product, we care about how they conduct their business, what value their business creates for their end customers, and what their customers care about. All in the service to truly know our customers and to help them succeed. - **Building the future together** - - Co-creating a better Internet with customers, community and partners. - - Collaboration is key, we can't do it alone. - - Knowledge sharing how we grow, both as individuals and as a company. + - Co-creating a better Internet with customers, community and partners. + - Collaboration is key, we can't do it alone. + - Knowledge sharing how we grow, both as individuals and as a company. To fulfill our mission as a company, we can't do it alone. Our customers, community and partners are a big part of it, we can only succeed by doing it together. We collaborate on our roadmap, feature design and feedback. We make sure everyone has a voice and that it's respected. diff --git a/docs/handbook/company/who-are-we-building-for.mdx b/docs/handbook/company/who-are-we-building-for.mdx index ed50f7b29..757450bf8 100644 --- a/docs/handbook/company/who-are-we-building-for.mdx +++ b/docs/handbook/company/who-are-we-building-for.mdx @@ -15,12 +15,11 @@ As an organization, they are a tech-forward innovator and prioritize staying at It's also important to note that there are clear differences between the types of personas we're serving within the ICP. We've got the buyer persona, the user persona and the developer persona. Let's dive into each of these. - ## Buyer persona The buyer persona is the person who is responsible for deciding to purchase Webiny. This is usually a CTO, a VP of Engineering, or a similar role. This person is usually not a developer, but they are technical enough to understand the value that Webiny brings to the table. -For us, that persona is an important one as we're usually working closely with them to understand their long-term plans and goals, and how Webiny can help them achieve those goals. This in turn helps us make better decisions when it comes to prioritizing features and planning our roadmap. +For us, that persona is an important one as we're usually working closely with them to understand their long-term plans and goals, and how Webiny can help them achieve those goals. This in turn helps us make better decisions when it comes to prioritizing features and planning our roadmap. The buyer persona cares about things like the total cost of ownership, driving the innovation within the business, the return on investment, and the long-term value that Webiny brings to their organization. They also care about things like the security and reliability of the platform, as well as the quality of the support they get from us. diff --git a/docs/handbook/company/why-we-chose-serverless.mdx b/docs/handbook/company/why-we-chose-serverless.mdx index 9d0c91524..377e52492 100644 --- a/docs/handbook/company/why-we-chose-serverless.mdx +++ b/docs/handbook/company/why-we-chose-serverless.mdx @@ -5,7 +5,7 @@ description: What are our reasons for using serverless infrastructure slug: why-we-chose-serverless --- -When we set out to build Webiny, one of our goals was to provide a great experience to the developers. To do that we knew we needed to control the product experience end-to-end, and with a self-hosted product like Webiny, that is not easily achievable. A big part of how a product performs is due to the infrastructure it runs on. How performant, scalable and secure that infrastructure is, is a part of it. But the other part is how costly is it to run and maintain on an ongoing basis. +When we set out to build Webiny, one of our goals was to provide a great experience to the developers. To do that we knew we needed to control the product experience end-to-end, and with a self-hosted product like Webiny, that is not easily achievable. A big part of how a product performs is due to the infrastructure it runs on. How performant, scalable and secure that infrastructure is, is a part of it. But the other part is how costly is it to run and maintain on an ongoing basis. We took a bet on serverless. With serverless we can use the best services in the world to build our product. Just imagine the engineering force you would need to have on your end if you wanted to build a fault-tolerant storage similar to AWS S3, or a computing unit that can scale from zero to a thousand instances in a few seconds, like AWS Lambda, or a database that can handle millions of queries per second, like DynamoDB. And did we mention that those services are fault-tolerant by design, that they are priced per usage and that they are maintained and secured by some of the best engineers in the world? @@ -21,7 +21,7 @@ This is a conscious tradeoff we decided to make. We choose the best infrastructu At the same time, with Webiny you are not locked into our platform, which we believe is the more important question to ask yourself. With Webiny you always have direct access to all of your data and can move it to any other system at any time. -With serverless you will save a significant amount of your engineering and DevOps effort which can be used to build your product and grow your business instead of managing servers. +With serverless you will save a significant amount of your engineering and DevOps effort which can be used to build your product and grow your business instead of managing servers. ### Is it just AWS, what about other cloud providers? @@ -33,4 +33,4 @@ With the right architecture, you will not face this problem. Just try Webiny for ### Where can I learn more about serverless? -Check out our [whitepaper](https://www.webiny.com/resources/benefits-of-a-serverless-cms). \ No newline at end of file +Check out our [whitepaper](https://www.webiny.com/resources/benefits-of-a-serverless-cms). diff --git a/docs/handbook/company/why-webiny-exists.mdx b/docs/handbook/company/why-webiny-exists.mdx index b6aaad75c..07e8cebc4 100644 --- a/docs/handbook/company/why-webiny-exists.mdx +++ b/docs/handbook/company/why-webiny-exists.mdx @@ -21,21 +21,18 @@ The purpose we aim to fulfill is one of allowing developers to focus on building With AI becoming a core part of how software is built, our purpose has taken on a new dimension. We want developers — and the AI agents working alongside them — to be able to customize the platform through well-defined, typed APIs that produce correct code on the first attempt. The less time spent wrestling with undocumented internals, the more time spent building things that matter. - -## Our Mission +## Our Mission **Provide an open, AI-programmable platform that empowers builders and creators to craft amazing online experiences. No matter the scale, always secure and reliable.** +To fulfill our purpose, our mission is an ambitious one. It's not enough for us to provide just another library, another framework, an infrastructure solution, or a ready-made application. It won't work. Many tried that approach before and failed. -To fulfill our purpose, our mission is an ambitious one. It's not enough for us to provide just another library, another framework, an infrastructure solution, or a ready-made application. It won't work. Many tried that approach before and failed. - -We need to provide a platform. A platform that is open, that is ultra-customizable, that is rooted in the first principles, and that respects the data ownership of the users. A platform that contains many ready-made applications, but that are composable. A platform where you can customize every single part of it in a maintainable way. Operating on an infrastructure type that is most reliable, scalable, secure, performs well under large workloads and comes at a low cost. +We need to provide a platform. A platform that is open, that is ultra-customizable, that is rooted in the first principles, and that respects the data ownership of the users. A platform that contains many ready-made applications, but that are composable. A platform where you can customize every single part of it in a maintainable way. Operating on an infrastructure type that is most reliable, scalable, secure, performs well under large workloads and comes at a low cost. In 2025 this mission evolved. With the rise of AI coding agents, we recognized that the same qualities that made Webiny great for developers — typed APIs, consistent extension patterns, code-as-configuration — also made it uniquely suited for AI-assisted development. Our mission now includes making Webiny the most AI-programmable content platform in the world: a system where an AI agent, given the right context, can generate correct, upgrade-safe extensions without guesswork. Our mission is timeless, things like user experience, performance, security and reliability are not going to change. They are the first principles of building great online experiences. Our approach to how we achieve this mission will probably evolve, but it will always stay relevant. - ## Our strategy ### Open-source as a foundation @@ -46,7 +43,6 @@ Open-source for us doesn't just mean a permissive license, but also a transparen By rooting our values around open source we give the developers back that sense of freedom to customize and use the product the way they want. - ### Developer experience as a priority / Ultra-customizable We believe that developer experience is the most important aspect of any software product. We want to build a product that developers love to use, and that's why we're building Webiny with developers in mind. @@ -65,16 +61,16 @@ This isn't about replacing developers. It's about removing the friction between ### Rooted in the first principles -Performance, security and reliability. These are the core principles we stand to uphold today and in the future. +Performance, security and reliability. These are the core principles we stand to uphold today and in the future. -It might sound obvious, but we're hearing every day from users how many of the leading solutions in the market are not meeting this basic bar of what is crucial for them to have a good experience. +It might sound obvious, but we're hearing every day from users how many of the leading solutions in the market are not meeting this basic bar of what is crucial for them to have a good experience. ### Data ownership as a right -Saying we care about customer's data, and doing something about it are very different things. +Saying we care about customer's data, and doing something about it are very different things. We differentiate by understanding the value of customer's data. SaaS vendors put the same value on the data across all their customers. In their view, there is no difference if a customer uses their solution to keep track of how many socks they have in their warehouse, vs a customer using their solution to store patients' medical information. -We believe there's a big difference between the two and that our customers are the ones who are best equipped to manage their data most responsibly. One can only care for their own with the detail and precision that it requires. +We believe there's a big difference between the two and that our customers are the ones who are best equipped to manage their data most responsibly. One can only care for their own with the detail and precision that it requires. Many of the SaaS solutions on the market are anti-freedom. They lock you in, they make it easy to check in, but you can't check out. They don't provide you with ways to export or move your content to another platform. Competition should be on who has a better product, not who can lock you in harder. If all products were forced to make it easy for you to migrate away from their platform, they would be forced to build a better product. With Webiny, you have direct access to all of your data. We want to compete on the quality of our product, not by locking you in. Our goal is to liberate your data from the dictators of the SaaS world. diff --git a/docs/messages/deployment-ddb-conditional-check-failed.mdx b/docs/messages/deployment-ddb-conditional-check-failed.mdx index 9ea74274a..81556a83e 100644 --- a/docs/messages/deployment-ddb-conditional-check-failed.mdx +++ b/docs/messages/deployment-ddb-conditional-check-failed.mdx @@ -19,16 +19,12 @@ Make sure to replace `` and `` with the appropr - To find the Amazon DynamoDB table name, run the `yarn webiny output core --env ` command. The table name will be listed in the output. - - - - +To find the Amazon DynamoDB table name, run the `yarn webiny output core --env ` command. The table name will be listed in the output. - Reach out to us in our [community Slack](https://www.webiny.com/slack) channel if you'd like to further investigate this issue. We might be able to identify the root cause and prevent it from happening in the future. - + +Reach out to us in our [community Slack](https://www.webiny.com/slack) channel if you'd like to further investigate this issue. We might be able to identify the root cause and prevent it from happening in the future. - + diff --git a/docs/messages/missing-stack-output.mdx b/docs/messages/missing-stack-output.mdx index 383fa2e2a..b441e113e 100644 --- a/docs/messages/missing-stack-output.mdx +++ b/docs/messages/missing-stack-output.mdx @@ -29,7 +29,7 @@ yarn webiny deploy api --env dev --deployment-logs - The `--deployment-logs` flag will output the deployment logs to the console, which can help you identify the issue if the deployment fails. +The `--deployment-logs` flag will output the deployment logs to the console, which can help you identify the issue if the deployment fails. diff --git a/docs/messages/webiny-package-versions-check.mdx b/docs/messages/webiny-package-versions-check.mdx index b3cbf1224..732e4af83 100644 --- a/docs/messages/webiny-package-versions-check.mdx +++ b/docs/messages/webiny-package-versions-check.mdx @@ -9,9 +9,9 @@ Each time you run the Webiny CLI, it verifies that all `@webiny/*` NPM packages In case you've encountered a version mismatch, the CLI will display a warning message similar to the one below: - + -To fix this, in your project, simply find all `package.json` files that include `@webiny/*` packages and ensure they all have the same version. +To fix this, in your project, simply find all `package.json` files that include `@webiny/*` packages and ensure they all have the same version. Another option is to run the [`yarn why`](https://yarnpkg.com/cli/why) command to check where the different package versions are coming from. For example: diff --git a/docs/release-notes/6.1.0/upgrade-guide.mdx b/docs/release-notes/6.1.0/upgrade-guide.mdx index 283ee929a..48b58a1af 100644 --- a/docs/release-notes/6.1.0/upgrade-guide.mdx +++ b/docs/release-notes/6.1.0/upgrade-guide.mdx @@ -41,8 +41,8 @@ Once the upgrade has finished, running the `yarn webiny --version` command in yo - Starting with 6.1.0, upgrade will be ran with `yarn webiny upgrade` command. - Current npx command is a temporary solution until the new command is available. +Starting with 6.1.0, upgrade will be ran with `yarn webiny upgrade` command. +Current npx command is a temporary solution until the new command is available. diff --git a/docs/release-notes/6.2.0/changelog.mdx b/docs/release-notes/6.2.0/changelog.mdx index 539c7fcec..a76fe47eb 100644 --- a/docs/release-notes/6.2.0/changelog.mdx +++ b/docs/release-notes/6.2.0/changelog.mdx @@ -201,11 +201,11 @@ You can now connect Webiny to an externally managed OpenSearch cluster that requ ```typescript ``` diff --git a/docs/release-notes/upgrade-webiny.mdx b/docs/release-notes/upgrade-webiny.mdx index 668f852b3..57d632aff 100644 --- a/docs/release-notes/upgrade-webiny.mdx +++ b/docs/release-notes/upgrade-webiny.mdx @@ -70,9 +70,7 @@ Each time a version upgrade is successfully applied, an entry is recorded in the ```json { "webiny": { - "history": [ - { "version": "6.1.0", "timestamp": "2026-03-15T10:30:00.000Z" } - ] + "history": [{ "version": "6.1.0", "timestamp": "2026-03-15T10:30:00.000Z" }] } } ``` @@ -86,14 +84,14 @@ This history serves two purposes: The upgrade command supports the following flags: -| Flag | Type | Default | Description | -| --- | --- | --- | --- | -| `--log-level` | `string` | `error` | Set log level for the upgrade process. Possible values are `debug`, `info`, `warning`, and `error`. | -| `--json` | `boolean` | `false` | Output logs as NDJSON (one JSON object per line) - only for npx command | -| `--force` | `boolean` | `false` | Force upgrade even if already ran. | -| `--package-manager` | `string` | auto-detected | Package manager to use: `yarn`, `pnpm`, or `npm`. Auto-detected from the lock file if omitted. | -| `--dry-run` | `boolean` | `false` | Do everything except actually performing the upgrade (for testing purposes). | -| `--registry` | `string` | `https://registry.npmjs.org` | npm registry URL. | +| Flag | Type | Default | Description | +| ------------------- | --------- | ---------------------------- | --------------------------------------------------------------------------------------------------- | +| `--log-level` | `string` | `error` | Set log level for the upgrade process. Possible values are `debug`, `info`, `warning`, and `error`. | +| `--json` | `boolean` | `false` | Output logs as NDJSON (one JSON object per line) - only for npx command | +| `--force` | `boolean` | `false` | Force upgrade even if already ran. | +| `--package-manager` | `string` | auto-detected | Package manager to use: `yarn`, `pnpm`, or `npm`. Auto-detected from the lock file if omitted. | +| `--dry-run` | `boolean` | `false` | Do everything except actually performing the upgrade (for testing purposes). | +| `--registry` | `string` | `https://registry.npmjs.org` | npm registry URL. | For example, to perform a dry run of the upgrade: diff --git a/docs/user-guides/5.x/apw/essentials/apw-user-guides-outline-users-creation.mdx b/docs/user-guides/5.x/apw/essentials/apw-user-guides-outline-users-creation.mdx index 74e03cd9b..bb7b75b82 100644 --- a/docs/user-guides/5.x/apw/essentials/apw-user-guides-outline-users-creation.mdx +++ b/docs/user-guides/5.x/apw/essentials/apw-user-guides-outline-users-creation.mdx @@ -20,58 +20,58 @@ Welcome to Advanced Publishing Workflow (APW) user guides! In this article, you To simplify this APW user guide, we will follow the example below and learn each aspect of APW one by one. We will have two users **John Author** and **Adam Reviewer**. + - **John Author** will create a blog. - And **Adam Reviewer** reviews it and suggests changes. Here are the steps to follow the APW User Guides: -| Steps | Description | -| :---------------------------------- | :----------------------------- | -| 1. **[Define a Workflow](/{version}/user-guides/apw/essentials/define-workflow)** | In this step, we will define a workflow to be followed in the publishing process. | -| 2. **[Submit Record for Review](/{version}/user-guides/apw/essentials/submit-record-for-review)** | **John Author** will create a **Blog** content and will submit it for review. In this step, John will also create a **Blog** content model and **Content** model group. | -| 3. **[Review Record](/{version}/user-guides/apw/essentials/review-record)** | **Adam Reviewer** will review the blog and request a change to update the title of the blog for better SEO. | -| 4. **[Submit Change](/{version}/user-guides/apw/essentials/submit-change)** | **John Author** will change the blog’s title as suggested by **Adam Reviewer** and will submit the changes. | -| 5. **[Provide Sign Off](/{version}/user-guides/apw/essentials/provide-sign-off)** | **Adam Reviewer** will review the update change request and provide sign-off to publish the content. | -| 6. **[Schedule Publishing](/{version}/user-guides/apw/essentials/scheduled-publishing)** | In this step, **John Author** will schedule a post for publishing. | - - +| Steps | Description | +| :------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1. **[Define a Workflow](/{version}/user-guides/apw/essentials/define-workflow)** | In this step, we will define a workflow to be followed in the publishing process. | +| 2. **[Submit Record for Review](/{version}/user-guides/apw/essentials/submit-record-for-review)** | **John Author** will create a **Blog** content and will submit it for review. In this step, John will also create a **Blog** content model and **Content** model group. | +| 3. **[Review Record](/{version}/user-guides/apw/essentials/review-record)** | **Adam Reviewer** will review the blog and request a change to update the title of the blog for better SEO. | +| 4. **[Submit Change](/{version}/user-guides/apw/essentials/submit-change)** | **John Author** will change the blog’s title as suggested by **Adam Reviewer** and will submit the changes. | +| 5. **[Provide Sign Off](/{version}/user-guides/apw/essentials/provide-sign-off)** | **Adam Reviewer** will review the update change request and provide sign-off to publish the content. | +| 6. **[Schedule Publishing](/{version}/user-guides/apw/essentials/scheduled-publishing)** | In this step, **John Author** will schedule a post for publishing. | ## Author & Reviewer Users Creation + As previously mentioned, to proceed with the guides, we require two users: **John Author** and **Adam Reviewer**. Now, let’s create these users first. In this section, we will create the two user accounts. If you are unfamiliar with how to create a user role (formerly called a "group") and a user account, please refer to the [User Creation](/{version}/user-guides/headless-cms/advanced/user-creation) tutorial. - - Webiny enables users to execute bulk actions, such as moving and deleting multiple files in bulk. In this tutorial, we will learn how to move multiple files from folder to another folder in the File Manager. ## Prerequisites To follow this tutorial, you need the **Men's Red Tshirt** and **Men's Black Tshirt** images uploaded in the file manager. If you don’t have these images in your file manager, please follow the [File Upload](/{version}/user-guides/file-manager/essentials/upload-file) and [Tagging Files](/{version}/user-guides/file-manager/essentials/tagging-files) tutorials to upload them. - - Webiny simplifies file access in the File Manager by allowing users to create path aliases. Each file can have one or more aliases, which proves useful, especially during file migrations from other systems, ensuring the preservation of URLs in existing content. In this tutorial, we will learn how to add a path alias to a file. We will do this in 2 steps: - Step 1: Add a path alias to a file @@ -23,23 +22,23 @@ Webiny simplifies file access in the File Manager by allowing users to create pa To follow this tutorial, you need the **Men's Red Tshirt** image uploaded in the file manager. If you don’t have the **Men's Red Tshirt** image in your file manager, please follow the [File Upload](/{version}/user-guides/file-manager/essentials/upload-file) tutorial to upload it. - - Webiny simplifies content organization by allowing users to create folders and sub-folders, making it easier to manage. In this tutorial, we will learn how to organize your files in folders and sub-folders in the File Manager. We will do this in 3 steps: - Step 1: Create a folders - Step 2: Create a sub-folder - Step 3: Upload an image - - - In the Open Source version, you'll have basic roles and permissions, where users either have full access or no access to a particular application. To access the advanced roles and permissions feature, you need a Webiny Enterprise or Webiny Business license. @@ -39,7 +37,7 @@ As an example, we will create a Role with the following access permissions: To follow this tutorial, you need to have a content model group named **E-commerce** in your Webiny instance. If you don't have it, please follow the [Create Content Model Group](/{version}/user-guides/headless-cms/essentials/create-content-model-group) tutorial to create it. - - -Webiny simplifies content organization by allowing users to create folders and sub-folders, making it easier to manage. In this tutorial, we will learn how to organize your files in folders and sub-folders in the File Manager. We will do this in a few steps: +Webiny simplifies content organization by allowing users to create folders and sub-folders, making it easier to manage. In this tutorial, we will learn how to organize your files in folders and sub-folders in the File Manager. We will do this in a few steps: - Step 1: Create a folder - Step 2: Create a sub-folder - Step 3: Move a folder into a parent folder - Step 4: Upload an image -