diff --git a/public/__redirects b/public/__redirects index 759b6b56687dfd8..f460dd588c6a78e 100644 --- a/public/__redirects +++ b/public/__redirects @@ -1964,6 +1964,7 @@ /workers/tutorials/hello-world-rust/ /workers/tutorials/ 301 /workers/tutorials/introduction-to-cloudflare-workers/ https://www.youtube.com/watch?v=H7Qe96fqg1M 301 /workers/configuration/bindings/about-service-bindings/ /workers/runtime-apis/bindings/service-bindings/ 301 +/workers/configuration/previews/ /workers/previews/ 301 /workers/tutorials/localize-a-website/ /pages/tutorials/localize-a-website/ 301 /workers/tutorials/manage-projects-with-lerna/ /pages/configuration/monorepos/#monorepo-management-tools 301 /workers/tutorials/create-sitemap-from-sanity-cms/ /resources/ 301 diff --git a/src/content/changelog/workers/2025-04-08-deploy-to-cloudflare-button.mdx b/src/content/changelog/workers/2025-04-08-deploy-to-cloudflare-button.mdx index d24d77883df382f..24164f0da5e4396 100644 --- a/src/content/changelog/workers/2025-04-08-deploy-to-cloudflare-button.mdx +++ b/src/content/changelog/workers/2025-04-08-deploy-to-cloudflare-button.mdx @@ -14,7 +14,7 @@ The Deploy to Cloudflare button: 1. **Creates a new Git repository on your GitHub/ GitLab account**: Cloudflare will automatically clone and create a new repository on your account, so you can continue developing. 2. **Automatically provisions resources the app needs**: If your repository requires Cloudflare primitives like a [Workers KV namespace](/kv/), a [D1 database](/d1/), or an [R2 bucket](/r2/), Cloudflare will automatically provision them on your account and bind them to your Worker upon deployment. 3. **Configures Workers Builds (CI/CD)**: Every new push to your production branch on your newly created repository will automatically build and deploy courtesy of [Workers Builds](/workers/ci-cd/builds/). -4. **Adds preview URLs to each pull request**: If you'd like to test your changes before deploying, you can push changes to a [non-production branch](/workers/ci-cd/builds/build-branches/#configure-non-production-branch-builds) and [preview URLs](/workers/configuration/previews/) will be generated and [posted back to GitHub as a comment](/workers/ci-cd/builds/git-integration/github-integration/#pull-request-comment). +4. **Adds preview URLs to each pull request**: If you'd like to test your changes before deploying, you can push changes to a [non-production branch](/workers/ci-cd/builds/build-branches/#configure-non-production-branch-builds) and [preview URLs](/workers/previews/) will be generated and [posted back to GitHub as a comment](/workers/ci-cd/builds/git-integration/github-integration/#pull-request-comment). ![Import repo or choose template](~/assets/images/workers/dtw-user-flow.png) diff --git a/src/content/changelog/workers/2025-08-08-support-long-branch-names-preview-aliases.mdx b/src/content/changelog/workers/2025-08-08-support-long-branch-names-preview-aliases.mdx index 4b869cc6a72861c..c15b306d0c657a0 100644 --- a/src/content/changelog/workers/2025-08-08-support-long-branch-names-preview-aliases.mdx +++ b/src/content/changelog/workers/2025-08-08-support-long-branch-names-preview-aliases.mdx @@ -6,7 +6,7 @@ tags: date: 2025-08-14T01:00:00Z --- -We've updated [preview URLs](/workers/configuration/previews/) for Cloudflare Workers to support long branch names. +We've updated [preview URLs](/workers/previews/) for Cloudflare Workers to support long branch names. Previously, branch and Worker names exceeding the 63-character DNS limit would cause alias generation to fail, leaving pull requests without aliased preview URLs. This particularly impacted teams relying on descriptive branch naming. diff --git a/src/content/changelog/workers/2025-09-17-update-preview-url-setting.mdx b/src/content/changelog/workers/2025-09-17-update-preview-url-setting.mdx index b43c774dfedb8db..8beea58d2e7bb71 100644 --- a/src/content/changelog/workers/2025-09-17-update-preview-url-setting.mdx +++ b/src/content/changelog/workers/2025-09-17-update-preview-url-setting.mdx @@ -5,7 +5,7 @@ date: 2025-09-17 --- import { WranglerConfig, Aside } from "~/components"; -To prevent the accidental exposure of applications, we've updated how [Worker preview URLs](/workers/configuration/previews/) (`-..workers.dev`) are handled. We made this change to ensure preview URLs are only active when intentionally configured, improving the default security posture of your Workers. +To prevent the accidental exposure of applications, we've updated how [Worker preview URLs](/workers/previews/) (`-..workers.dev`) are handled. We made this change to ensure preview URLs are only active when intentionally configured, improving the default security posture of your Workers. ## One-Time Update for Workers with workers.dev Disabled We performed a one-time update to disable preview URLs for existing Workers where the [workers.dev subdomain](/workers/configuration/routing/workers-dev/) was also disabled. @@ -16,7 +16,7 @@ If your Worker was affected, its preview URL (`-.-..workers.dev`. - -Preview URLs can be: - -- Integrated into CI/CD pipelines, allowing automatic generation of preview environments for every pull request. -- Used for collaboration between teams to test code changes in a live environment and verify updates. -- Used to test new API endpoints, validate data formats, and ensure backward compatibility with existing services. - -When testing zone level performance or security features for a version, we recommend using [version overrides](/workers/configuration/versions-and-deployments/gradual-deployments/#version-overrides) so that your zone's performance and security settings apply. - -:::note -Preview URLs are only available for Worker versions uploaded after 2024-09-25. -::: - -## Types of Preview URLs - -### Versioned Preview URLs - -Every time you create a new [version](/workers/configuration/versions-and-deployments/#versions) of your Worker, a unique static version preview URL is generated automatically. These URLs use a version prefix and follow the format `-..workers.dev`. - -New versions of a Worker are created when you run: - -- [`wrangler deploy`](/workers/wrangler/commands/#deploy) -- [`wrangler versions upload`](/workers/wrangler/commands/#versions-upload) -- Or when you make edits via the Cloudflare dashboard - -If Preview URLs have been enabled, they are public and available immediately after version creation. - -:::note -Minimum required Wrangler version: 3.74.0. Check your version by running `wrangler --version`. To update Wrangler, refer to [Install/Update Wrangler](/workers/wrangler/install-and-update/). -::: - -#### View versioned preview URLs using Wrangler - -The [`wrangler versions upload`](/workers/wrangler/commands/#versions-upload) command uploads a new [version](/workers/configuration/versions-and-deployments/#versions) of your Worker and returns a preview URL for each version uploaded. - -#### View versioned preview URLs on the Workers dashboard - -1. In the Cloudflare dashboard, go to the **Workers & Pages** page. - - - -2. Select your Worker. -3. Go to the **Deployments** tab, and find the version you would like to view. - -### Aliased preview URLs - -Aliased preview URLs let you assign a persistent, readable alias to a specific Worker version. These are useful for linking to stable previews across many versions (e.g. to share an upcoming but still actively being developed new feature). A common workflow would be to assign an alias for the branch that you're working on. These types of preview URLs follow the same pattern as other preview URLs: -`-..workers.dev` - -:::note -Minimum required Wrangler version: `4.21.0`. Check your version by running `wrangler --version`. To update Wrangler, refer to [Install/Update Wrangler](/workers/wrangler/install-and-update/). -::: - -#### Create an Alias - -Aliases may be created during `versions upload`, by providing the `--preview-alias` flag with a valid alias name: - -```bash -wrangler versions upload --preview-alias staging -``` - -The resulting alias would be associated with this version, and immediately available at: -`staging-..workers.dev` - -#### Rules and limitations - -- Aliases may only be created during version upload. -- Aliases must use only lowercase letters, numbers, and dashes. -- Aliases must begin with a lowercase letter. -- The alias and Worker name combined (with a dash) must not exceed 63 characters due to DNS label limits. -- Only the 1000 most recently deployed aliases are retained. When a new alias is created beyond this limit, the least recently deployed alias is deleted. - -## Manage access to Preview URLs - -When enabled, all preview URLs are available publicly. You can use [Cloudflare Access](/cloudflare-one/access-controls/policies/) to require visitors to authenticate before accessing preview URLs. You can limit access to yourself, your teammates, your organization, or anyone else you specify in your [access policy](/cloudflare-one/access-controls/policies/). - -To limit your preview URLs to authorized emails only: - -1. In the Cloudflare dashboard, go to the **Workers & Pages** page. - - - -2. In **Overview**, select your Worker. -3. Go to **Settings** > **Domains & Routes**. -4. For Preview URLs, click **Enable Cloudflare Access**. -5. Optionally, to configure the Access application, click **Manage Cloudflare Access**. There, you can change the email addresses you want to authorize. View [Access policies](/cloudflare-one/access-controls/policies/#selectors) to learn about configuring alternate rules. -6. [Validate the Access JWT](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/authorization-cookie/validating-json/#cloudflare-workers-example) in your Worker script using the audience (`aud`) tag and JWKs URL provided. - -## Toggle Preview URLs (Enable or Disable) - -Note: - -- Preview URLs are enabled by default when `workers_dev` is enabled. -- Preview URLs are disabled by default when `workers_dev` is disabled. -- Disabling Preview URLs will disable routing to both versioned and aliased preview URLs. - -### From the Dashboard - -To toggle Preview URLs for a Worker: - -1. In the Cloudflare dashboard, go to the **Workers & Pages** page. - - - -2. In **Overview**, select your Worker. -3. Go to **Settings** > **Domains & Routes**. -4. For Preview URLs, click **Enable** or **Disable**. -5. Confirm your action. - -### From the [Wrangler configuration file](/workers/wrangler/configuration/) - -:::note -Wrangler 3.91.0 or higher is required to use this feature. -::: - -:::note -Older Wrangler versions will default to Preview URLs being enabled. -::: - -To toggle Preview URLs for a Worker, include any of the following in your Worker's Wrangler file: - - - -```jsonc -{ - "preview_urls": true -} -``` - - - - - -```jsonc -{ - "preview_urls": false -} -``` - - - -If not given, `preview_urls = workers_dev` is the default. - -:::caution -If you enable or disable Preview URLs in the Cloudflare dashboard, but do not update your Worker's Wrangler file accordingly, the Preview URLs status will change the next time you deploy your Worker with Wrangler. -::: - -## Limitations - -- Preview URLs are not generated for Workers that implement a [Durable Object](/durable-objects/). -- Preview URLs are not currently generated for [Workers for Platforms](/cloudflare-for-platforms/workers-for-platforms/) [user Workers](/cloudflare-for-platforms/workers-for-platforms/how-workers-for-platforms-works/#user-workers). This is a temporary limitation, we are working to remove it. -- You cannot currently configure Preview URLs to run on a subdomain other than [`workers.dev`](/workers/configuration/routing/workers-dev/). -- You cannot view logs for Preview URLs today, this includes Workers Logs, Wrangler tail and Logpush. +This page has moved. For the latest documentation on Previews, refer to [Previews](/workers/previews/). diff --git a/src/content/docs/workers/configuration/routing/workers-dev.mdx b/src/content/docs/workers/configuration/routing/workers-dev.mdx index f105439e53ef4eb..bb1602563c194e0 100644 --- a/src/content/docs/workers/configuration/routing/workers-dev.mdx +++ b/src/content/docs/workers/configuration/routing/workers-dev.mdx @@ -66,7 +66,7 @@ To disable the `workers.dev` route for a Worker, include the following in your W -When you redeploy your Worker with this change, the `workers.dev` route will be disabled. Disabling your `workers.dev` route does not disable Preview URLs. Learn how to [disable Preview URLs](/workers/configuration/previews/#disabling-preview-urls). +When you redeploy your Worker with this change, the `workers.dev` route will be disabled. Disabling your `workers.dev` route does not disable Preview URLs. Learn how to [disable Preview URLs](/workers/previews/#enable-or-disable-preview-urls). If you do not specify `workers_dev = false` but add a [`routes` component](/workers/wrangler/configuration/#routes) to your [Wrangler configuration file](/workers/wrangler/configuration/), the value of `workers_dev` will be inferred as `false` on the next deploy. diff --git a/src/content/docs/workers/previews/api.mdx b/src/content/docs/workers/previews/api.mdx new file mode 100644 index 000000000000000..b149fdab60d6047 --- /dev/null +++ b/src/content/docs/workers/previews/api.mdx @@ -0,0 +1,633 @@ +--- +pcx_content_type: reference +title: Previews API +sidebar: + order: 2 + +description: Understand the Previews data model and use the API to manage Previews and Deployments programmatically. +--- + +import { Details } from "~/components"; + +This guide walks you through the Workers Previews API from the ground up. For a higher-level introduction to what Previews are and how to get started, refer to the [Previews overview](/workers/previews/). This page assumes you can make authenticated requests to the Cloudflare API and that you already have a production Worker deployed. + +Every endpoint below lives under a common base path: + +```txt +https://api.cloudflare.com/client/v4/accounts/{account_id}/workers +``` + +--- + +## Core concepts + +### Previews and Preview Deployments + +#### Preview + +A **Preview** is a fully functional Worker that Cloudflare creates as a child of your production Worker. It runs on the same infrastructure, supports the same [bindings](/workers/runtime-apis/bindings/), and handles traffic the same way — but it operates in complete isolation. Requests to a Preview never touch your production Worker's data or configuration. + +Every Preview belongs to exactly one parent Worker. You can create as many Previews as you need — one per feature branch, one per teammate, or one for every pull request. + +#### Preview Deployment + +A **Preview Deployment** is an immutable version of your Worker code, bindings, and configuration. It belongs to a single Preview and cannot be modified after creation. + +A Preview can have many Deployments, but it only serves traffic from one at a time. Creating a new Deployment automatically makes it the active one — 100% of the Preview's traffic switches to it immediately. There is no gradual rollout. + +Together, Previews and their Deployments form a hierarchy: + +```txt +Worker (your production Worker) +| ++-- Preview +| +-- Deployment +| +-- Deployment +| +-- ... +| ++-- Preview +| +-- Deployment +| +-- ... +... +``` + +### How you identify a Preview + +Every Preview has three identifiers: + +| Identifier | Description | Example | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- | +| **Name** | A human-readable label you provide at creation. Max 256 characters. Allowed: alphanumerics, hyphens, underscores, forward slashes, periods, plus signs, equals signs. Must start and end with an alphanumeric character. | `feature/my-branch` | +| **Slug** | URL-safe version of the name. Cloudflare generates this by lowercasing and replacing special characters with hyphens. Immutable after creation. Appears in Preview URLs. | `feature-my-branch` | +| **ID** | Immutable 32-character hex string assigned at creation. | `63bafce9179948688866bb22268eb1c6` | + +Anywhere the API accepts a `{preview_id}` path parameter, you can pass any of the three: the ID, the slug, or the URL-encoded name. + +--- + +### What a Preview contains vs. what a Deployment contains + +The Previews API splits configuration into two layers: the **Preview layer** and the **Deployment layer**. Understanding this split is key to working with the API. + +The **Preview layer** holds settings that apply across all of a Preview's Deployments: observability, log routing (`logpush`, `tail_consumers`), and organizational metadata (`tags`). These persist for the lifetime of the Preview regardless of how many Deployments you create. + +Each **Deployment** holds settings that are locked in at creation time and never change — your Worker code (`main_module`, `modules`), `bindings`, `compatibility_date`, `compatibility_flags`, `limits`, `placement`, `assets`, `migrations`, and `annotations`. + +This split matters because everything downstream — Preview Defaults, the merge behavior, and which endpoints accept which fields — follows from it. + +--- + +### Preview object + +The Preview object represents the Preview layer. Here is its full shape: + +```jsonc +{ + "id": "63bafce9179948688866bb22268eb1c6", // read-only, 32-char hex + "slug": "feature-my-branch", // read-only, immutable after creation + "name": "feature/my-branch", // max 256 chars + "tags": ["my-team", "my-preview"], // max 8 items, freeform labels + "observability": { + "enabled": true, // default false + "head_sampling_rate": 1, // 0 to 1, default 1 + "logs": { + "enabled": false, // default false + "head_sampling_rate": 1, // 0 to 1, default 1 + "invocation_logs": true, // default true + }, + }, + "logpush": false, // default false + "tail_consumers": [], // default [] + "urls": [ + // read-only + "https://feature-my-branch-my-worker.my-acct.workers.dev", + ], + "created_on": "2026-03-01T12:00:00Z", // read-only + "updated_on": "2026-03-01T12:30:00Z", // read-only + "deployed_on": "2026-03-01T12:35:00Z", // read-only, null if never deployed +} +``` + +| Field | Description | +| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | Immutable identifier assigned at creation. | +| `slug` | URL-safe version of `name`. Appears in Preview URLs. Immutable after creation. | +| `name` | Human-readable label you provide. Can contain slashes and other special characters that `slug` cannot. | +| `tags` | Up to 8 freeform strings for organizing Previews. | +| `observability` | Controls sampling rates and log collection. `head_sampling_rate` is a float from 0 to 1 (1 = 100%). The nested `logs` object has its own `enabled`, `head_sampling_rate`, and `invocation_logs` toggles. | +| `logpush` | Whether logs are pushed to your configured destination. | +| `tail_consumers` | Array of other Workers (by name) that receive this Preview's logs. | +| `urls` | Read-only. The routable URLs for this Preview, always pointing to its latest Deployment. | +| `created_on` | Read-only. When the Preview was created. | +| `updated_on` | Read-only. When the Preview was last modified. | +| `deployed_on` | Read-only. When the most recent Deployment was created. `null` if the Preview has never been deployed. | + +--- + +### Deployment object + +The Deployment object represents the Deployment layer. Here is its full shape: + +```jsonc +{ + "id": "11905d61-2e73-481e-b1b4-b4903974214a", // UUID, read-only + "number": 3, // sequential integer, read-only + "startup_time_ms": 10, // read-only + "main_module": "index.js", + "modules": [ + // only present if ?include=modules + { + "name": "index.js", + "content_type": "application/javascript+module", + "content_base64": "ZXhwb3J0IGRlZmF1bHQg...", + }, + ], + "bindings": [ + { + "type": "kv_namespace", + "name": "MY_KV", + "namespace_id": "staging-kv-id", + }, + ], + "compatibility_date": "2025-05-25", + "compatibility_flags": [], + "limits": { "cpu_ms": 50 }, + "placement": { "mode": "smart" }, + "usage_model": "standard", // deprecated + "assets": {}, + "migrations": {}, + "annotations": { + "workers/message": "Fixed bug.", // max 100 chars + "workers/tag": "v1.0.1", // max 25 chars + "workers/triggered_by": "create_preview_deployment_api", // read-only + }, + "urls": [ + // read-only + "https://9387e76d-my-worker.my-acct.workers.dev", + ], + "source": "wrangler", // read-only + "created_on": "2026-03-01T12:35:00Z", // read-only +} +``` + +| Field | Description | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `id` | UUID assigned at creation. | +| `number` | Sequential deployment number starting from 1. | +| `startup_time_ms` | Time in milliseconds the Worker took to start up. | +| `main_module` | Name of the entry-point module (the one that exports a `fetch` handler). | +| `modules` | Array of code and sourcemap content. Each entry has `name`, `content_type`, and `content_base64`. Only included in responses when you pass `?include=modules`. | +| `bindings` | [KV namespaces](/kv/), [D1 databases](/d1/), [secrets](/workers/configuration/secrets/), plain-text vars, [service bindings](/workers/runtime-apis/bindings/service-bindings/), and so on. | +| `compatibility_date` | The [compatibility date](/workers/configuration/compatibility-dates/) for runtime behavior. | +| `compatibility_flags` | Array of [compatibility flags](/workers/configuration/compatibility-dates/). | +| `limits` | Runtime resource limits, for example `{ "cpu_ms": 50 }`. | +| `placement` | [Smart placement](/workers/configuration/smart-placement/) configuration. | +| `usage_model` | Deprecated. One of `standard`, `bundled`, `unbound`. | +| `assets` | Static asset configuration (`html_handling`, `not_found_handling`, `run_worker_first`). | +| `migrations` | [Durable Object migrations](/durable-objects/reference/durable-objects-migrations/). Applied when the Deployment is created. | +| `annotations` | Metadata: `workers/message` (human-readable, max 100 chars), `workers/tag` (for example, a semver string, max 25 chars), `workers/triggered_by` (read-only, set by the system). | +| `urls` | Read-only. The routable URLs that always point to this exact Deployment. | +| `source` | Read-only. The client that created the Deployment (for example, `wrangler`, `api`). | +| `created_on` | Read-only. When the Deployment was created. | + +--- + +## Preview Defaults + +Your production Worker can store a property called **`preview_defaults`**. It is a template of starting values that Cloudflare applies whenever you create a new Preview or Deployment. It is not a standalone API resource — you read and write it through the existing Worker endpoints (`PUT` or `PATCH` on `/workers/workers/{worker_id}`). You can also configure these from the dashboard or your Wrangler configuration file — refer to [Set up Previews](/workers/previews/#set-up-previews) for a walkthrough. + +`preview_defaults` carries values for both layers: + +**Preview-layer defaults** (applied when creating or updating a Preview): + +- `observability` +- `logpush` +- `tail_consumers` + +**Deployment-layer defaults** (applied when creating a Deployment): + +- `bindings` +- `compatibility_date` +- `compatibility_flags` +- `limits` +- `placement` +- `usage_model` (deprecated) +- `assets.config` + +Configure these once on the parent Worker and every Preview and Deployment inherits them automatically, instead of repeating the same bindings and settings on every request. + +:::note +Changes to `preview_defaults` only affect Previews and Deployments created after the update. Existing ones stay unchanged. +::: + +### Secrets in Preview Defaults + +Secret bindings in `preview_defaults` have write-only fields. When you update `preview_defaults`, you can set secret values explicitly. If you omit them, the API inherits values in this order: + +1. From the existing `preview_defaults` secret with the same binding name (if one exists). +2. From the parent (production) Worker's secret binding. + +The API never exposes inherited secret values in responses. + +### How defaults merge + +When you create a Preview or a Deployment, the API merges your request body on top of the relevant layer's defaults: + +```txt +preview_defaults <-- base (stored on the parent Worker) + | + v +your request body <-- overrides + | + v +final created resource <-- what you get back +``` + +Your request body wins for any field it provides. Fields absent from your request body fall back to `preview_defaults`. Fields absent from both get schema defaults (for example, `logpush: false`) or are omitted. + +To skip the merge entirely and use only your request body, pass `?ignore_defaults=true`. This is supported on **Create Preview**, **Update Preview (PUT)**, and **Create Deployment**. + +
+ +Parent Worker has these `preview_defaults`: + +```json +{ + "bindings": [ + { "type": "kv_namespace", "name": "MY_KV", "namespace_id": "staging-kv-id" } + ], + "compatibility_date": "2025-01-15", + "observability": { "enabled": true, "head_sampling_rate": 0.1 } +} +``` + +You create a Preview with an observability override: + +```json +{ + "name": "feature/auth", + "observability": { "enabled": true, "head_sampling_rate": 1 } +} +``` + +**Result:** the Preview gets `head_sampling_rate: 1` (your override). `logpush`, `tail_consumers`, and other settings come from `preview_defaults` or their schema defaults. + +You then create a Deployment without specifying bindings: + +```json +{ + "main_module": "index.js", + "modules": [ + { + "name": "index.js", + "content_type": "application/javascript+module", + "content_base64": "..." + } + ], + "compatibility_date": "2025-05-25" +} +``` + +**Result:** the Deployment gets the `MY_KV` binding from `preview_defaults` even though you did not include it. Your `compatibility_date` of `2025-05-25` overrides the default of `2025-01-15`. + +
+ +--- + +## URL structure + +Previews and Deployments each receive automatically assigned URLs. The format depends on the zone type. + +### `workers.dev` + +| Type | Pattern | Example | +| ---------- | --------------------------------------------- | -------------------------------------------- | +| Production | `{worker}.{subdomain}.workers.dev` | `my-worker.my-acct.workers.dev` | +| Preview | `{slug}-{worker}.{subdomain}.workers.dev` | `feature-auth-my-worker.my-acct.workers.dev` | +| Deployment | `{short_id}-{worker}.{subdomain}.workers.dev` | `f498jo-my-worker.my-acct.workers.dev` | + +### `cloudflare.app` + +| Type | Pattern | Example | +| ---------- | ------------------------------------ | --------------------------------------- | +| Production | `{worker}.cloudflare.app` | `my-worker.cloudflare.app` | +| Preview | `{slug}.{worker}.cloudflare.app` | `feature-auth.my-worker.cloudflare.app` | +| Deployment | `{short_id}.{worker}.cloudflare.app` | `f498jo.my-worker.cloudflare.app` | + +A **Preview URL** always points to the latest Deployment. A **Deployment URL** always points to that exact immutable Deployment. For a quick summary of both URL types, refer to [Preview URLs and Deployment URLs](/workers/previews/#preview-urls-and-deployment-urls). + +These URLs appear in the read-only `urls` field on both the [Preview object](#preview-object) and [Deployment object](#deployment-object). + +### Custom domains + +You can route Previews through your own domain by enabling `previews_enabled` on a Worker custom domain. For Wrangler configuration and dashboard instructions, refer to [Use a custom domain for Previews](/workers/previews/#use-a-custom-domain-for-previews). + +```bash +curl -X PUT \ + "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/domains" \ + -H "Authorization: Bearer $API_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "zone_id": "593c9c94de529bbbfaac7c53ced0447d", + "hostname": "app.example.com", + "service": "my-worker", + "production_enabled": true, + "previews_enabled": true + }' +``` + +When `previews_enabled` is `true`, Cloudflare creates a wildcard DNS record (`*.app.example.com`) and adds a wildcard SAN to the edge certificate. + +| Type | Example | +| ---------- | ------------------------------------- | +| Production | `app.example.com` | +| Preview | `feature-auth.app.example.com` | +| Deployment | `f498jo-feature-auth.app.example.com` | + +Setting `previews_enabled: false` removes the wildcard DNS record and the wildcard SAN from the certificate. + +:::note +Both `production_enabled` and `previews_enabled` are optional and default to `true` and `false` respectively. If `production_enabled` is `false` and `previews_enabled` is `true`, only the wildcard DNS record is created (no bare-hostname record). +::: + +--- + +## API endpoints + +### Set Preview Defaults + +Use the existing Worker endpoints. Include `preview_defaults` in the body. Refer to [Preview Defaults](#preview-defaults) for how these values merge with individual Preview and Deployment requests. + +```txt +PUT /workers/workers/{worker_id} +PATCH /workers/workers/{worker_id} +``` + +Example (`PATCH` — only touches `preview_defaults`, leaves everything else unchanged): + +```bash +curl -X PATCH \ + "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/workers/$WORKER_ID" \ + -H "Authorization: Bearer $API_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "preview_defaults": { + "bindings": [ + { "type": "kv_namespace", "name": "MY_KV", "namespace_id": "staging-kv-id" }, + { "type": "plain_text", "name": "ENVIRONMENT", "text": "preview" } + ], + "compatibility_date": "2025-05-25", + "observability": { "enabled": true, "head_sampling_rate": 1 } + } + }' +``` + +### Preview endpoints + +All paths are relative to `/accounts/{account_id}/workers/workers/{worker_id}/previews`. + +#### List Previews + +```txt +GET / +``` + +| Parameter | In | Type | Default | +| ---------- | ----- | ------- | ------- | +| `page` | query | integer | `1` | +| `per_page` | query | integer | `10` | +| `order_by` | query | string | -- | +| `order` | query | string | -- | +| `slug` | query | string | -- | +| `name` | query | string | -- | + +Response: `200` with `result` as an array of Preview objects. + +#### Create Preview + +```txt +POST / +``` + +`name` is required in the request body. All other fields are optional and fall back to the Preview-layer values in `preview_defaults` (unless `?ignore_defaults=true`). + +| Parameter | In | Type | Description | +| ----------------- | ----- | ------- | ------------------------------------------------------------------- | +| `ignore_defaults` | query | boolean | If `true`, skip `preview_defaults`. Only your request body is used. | + +Response: `200` with the created Preview object, including the generated `id`, `slug`, and `urls`. + +#### Get Preview + +```txt +GET /{preview_id} +``` + +`{preview_id}` accepts an ID, slug, or URL-encoded name. + +#### Update Preview (full replacement) + +```txt +PUT /{preview_id} +``` + +Replaces the Preview entirely. Properties you omit are reset to defaults. `name` is required in the body. + +| Parameter | In | Type | +| ----------------- | ----- | ------- | +| `ignore_defaults` | query | boolean | + +#### Edit Preview (partial update) + +```txt +PATCH /{preview_id} +``` + +```txt +Content-Type: application/merge-patch+json +``` + +Only the properties you include are changed; everything else stays as-is. No fields are required. + +:::caution +Because this uses merge-patch ([RFC 7396](https://www.rfc-editor.org/rfc/rfc7396)), arrays are replaced wholesale, not merged element-by-element. If you want to add a tag, send the full `tags` array including existing tags. The same applies to `tail_consumers`. +::: + +#### Delete Preview + +```txt +DELETE /{preview_id} +``` + +Deletes the Preview and all of its Deployments. + +### Deployment endpoints + +All paths are relative to `/accounts/{account_id}/workers/workers/{worker_id}/previews/{preview_id}/deployments`. + +`{preview_id}` follows the same rules as above (ID, slug, or URL-encoded name). + +#### List Preview Deployments + +```txt +GET / +``` + +| Parameter | In | Type | +| ---------- | ----- | ------- | +| `page` | query | integer | +| `per_page` | query | integer | + +Response: `200` with `result` as an array of Deployment objects. + +#### Create Preview Deployment + +```txt +POST / +``` + +The API merges your request body on top of the Deployment-layer values in `preview_defaults`, unless you pass `?ignore_defaults=true`. + +| Parameter | In | Type | Description | +| ----------------- | ----- | ------- | ------------------------------------------------------------------- | +| `ignore_defaults` | query | boolean | If `true`, skip `preview_defaults`. Only your request body is used. | + +The request body can include: `main_module`, `modules`, `bindings`, `compatibility_date`, `compatibility_flags`, `limits`, `placement`, `usage_model`, `assets`, `migrations`, `annotations`. + +Response: `200` with the created Deployment object. + +#### Get Preview Deployment + +```txt +GET /{deployment_id} +``` + +`{deployment_id}` accepts a UUID or the literal string `latest`. + +| Parameter | In | Type | Description | +| --------- | ----- | ------ | -------------------------------------------------------------------------------------------------------------------- | +| `include` | query | string | Pass `modules` to include the `modules` array (code and sourcemaps) in the response. This can add several megabytes. | + +#### Delete Preview Deployment + +```txt +DELETE /{deployment_id} +``` + +`{deployment_id}` accepts a UUID or `latest`. + +--- + +## End-to-end walkthrough + +### 1. Set Preview Defaults on your Worker + +This is a one-time setup step. You are patching the parent Worker, not creating a new resource. You can also do this from the dashboard or Wrangler — refer to [Set up Previews](/workers/previews/#set-up-previews). + +```bash +curl -X PATCH \ + "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/workers/$WORKER_ID" \ + -H "Authorization: Bearer $API_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "preview_defaults": { + "bindings": [ + { "type": "kv_namespace", "name": "MY_KV", "namespace_id": "staging-kv-id" }, + { "type": "plain_text", "name": "ENVIRONMENT", "text": "preview" } + ], + "compatibility_date": "2025-05-25", + "observability": { "enabled": true, "head_sampling_rate": 1 } + } + }' +``` + +### 2. Create a Preview + +```bash +curl -X POST \ + "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/workers/$WORKER_ID/previews" \ + -H "Authorization: Bearer $API_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "feature/auth", + "tags": ["auth-team"] + }' +``` + +The response includes the Preview's `id`, `slug` (`feature-auth`), and `urls`. The `observability` setting comes from `preview_defaults` because you did not override it. Refer to [How defaults merge](#how-defaults-merge) for details on this behavior. + +### 3. Create a Preview Deployment + +```bash +curl -X POST \ + "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/workers/$WORKER_ID/previews/feature-auth/deployments" \ + -H "Authorization: Bearer $API_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "main_module": "index.js", + "modules": [ + { + "name": "index.js", + "content_type": "application/javascript+module", + "content_base64": "ZXhwb3J0IGRlZmF1bHQgewogIGFzeW5jIGZldGNoKHJlcXVlc3QsIGVudiwgY3R4KSB7CiAgICByZXR1cm4gbmV3IFJlc3BvbnNlKCdIZWxsbyBmcm9tIHByZXZpZXchJykKICB9Cn0=" + } + ], + "annotations": { + "workers/message": "Initial auth feature deployment", + "workers/tag": "v0.1.0" + } + }' +``` + +The `bindings` and `compatibility_date` come from `preview_defaults` automatically. The response includes the Deployment `id`, `number`, and `urls`. + +### 4. Visit the Preview + +- **Preview URL** (always points to the latest Deployment): the URL from the Preview's `urls` field, for example `https://feature-auth-my-worker.my-acct.workers.dev` +- **Deployment URL** (always points to this exact Deployment): the URL from the Deployment's `urls` field, for example `https://f498jo-my-worker.my-acct.workers.dev` + +### 5. Iterate + +Create more Deployments by repeating step 3 with updated code. Each new Deployment immediately receives 100% of the Preview's traffic. + +### 6. Clean up + +Delete a single Deployment (the most recent one): + +```bash +curl -X DELETE \ + "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/workers/$WORKER_ID/previews/feature-auth/deployments/latest" \ + -H "Authorization: Bearer $API_TOKEN" +``` + +Delete the entire Preview and all its Deployments: + +```bash +curl -X DELETE \ + "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/workers/$WORKER_ID/previews/feature-auth" \ + -H "Authorization: Bearer $API_TOKEN" +``` + +--- + +## Quick reference: all endpoints + +All paths are prefixed with `/accounts/{account_id}`. + +| Method | Path | +| --------------- | ------------------------------------------------------- | +| `PATCH` / `PUT` | `/workers/workers/{worker_id}` | +| `GET` | `/workers/workers/{worker_id}/previews` | +| `POST` | `/workers/workers/{worker_id}/previews` | +| `GET` | `/workers/workers/{worker_id}/previews/{preview_id}` | +| `PUT` | `/workers/workers/{worker_id}/previews/{preview_id}` | +| `PATCH` | `/workers/workers/{worker_id}/previews/{preview_id}` | +| `DELETE` | `/workers/workers/{worker_id}/previews/{preview_id}` | +| `GET` | `.../previews/{preview_id}/deployments` | +| `POST` | `.../previews/{preview_id}/deployments` | +| `GET` | `.../previews/{preview_id}/deployments/{deployment_id}` | +| `DELETE` | `.../previews/{preview_id}/deployments/{deployment_id}` | + +- `{worker_id}` accepts a Worker ID or name. +- `{preview_id}` accepts a Preview ID, slug, or URL-encoded name. +- `{deployment_id}` accepts a UUID or `latest`. diff --git a/src/content/docs/workers/previews/index.mdx b/src/content/docs/workers/previews/index.mdx new file mode 100644 index 000000000000000..78cb7ad1b3d0133 --- /dev/null +++ b/src/content/docs/workers/previews/index.mdx @@ -0,0 +1,233 @@ +--- +pcx_content_type: overview +title: Previews +sidebar: + order: 7 +description: Deploy isolated copies of your Worker to test changes before deploying to production. +--- + +import { + Render, + WranglerConfig, + Details, + PackageManagers, + Tabs, + TabItem, + InlineBadge, +} from "~/components"; + +A **Preview** is an isolated copy of your Worker that runs on its own URL, so you can test changes before deploying to production. A Worker can have many Previews running at the same time, each isolated from each other. Each Preview runs with its own [bindings](/workers/runtime-apis/bindings/), [environment variables](/workers/configuration/environment-variables/), and [secrets](/workers/configuration/secrets/), so your preview traffic never touches your production data. + +Previews are deployed automatically when you push to any branch other than your tracked branch (usually `main`), or manually with `wrangler preview`. Refer to [Deploy a Preview](#deploy-a-preview) for details. For a deeper look at how Previews and their Deployments are structured, refer to [Previews API](/workers/previews/api/). + +## Quick start + +1. Create a new project: + + + +2. Create a feature branch: + +```bash +git checkout -b new-feature +``` + +3. Deploy a Preview: + + + +Wrangler builds your code locally and deploys it as a Preview. It prints a **[Preview URL](#preview-urls-and-deployment-urls)** and a **[Deployment URL](#preview-urls-and-deployment-urls)** to your terminal. + +--- + +## Set up Previews + +Previews use their own [bindings](/workers/runtime-apis/bindings/), [environment variables](/workers/configuration/environment-variables/), and [secrets](/workers/configuration/secrets/) — separate from your production Worker settings. Before creating your first Preview, you need to configure what resources and environment variables Previews should use. These are called **[Preview Defaults](/workers/previews/api/#preview-defaults)** — a template of starting values that every new Preview inherits automatically. + +### From the dashboard + +Go to your Worker > **Settings**, and you will see a banner for setting up your default Preview settings. This walks you through the bindings, environment variables, and [observability](#observability-and-logging) settings you want every Preview to start with. This is the fastest way to get your whole team set up — anyone who creates a Preview (manually or by pushing a branch) gets the same configuration without needing to specify it themselves. + +### From code + +Add a `previews` block to your Wrangler configuration file to specify bindings and environment variables that apply when you run `wrangler preview`. This is useful when you want Preview configuration checked into Git alongside your Worker code, or when you are working through a coding agent. + + + +```toml +# Top-level configuration is treated as production configuration +name = "my-worker" +main = "src/index.ts" +compatibility_date = "$today" + +[[kv_namespaces]] +binding = "MY_KV" +id = "prod-kv-id" + +[previews] +vars = { ENVIRONMENT = "preview" } + +[[previews.kv_namespaces]] +binding = "MY_KV" +id = "preview-kv-id" +``` + + + +When you configure settings in both the dashboard and the Wrangler configuration file, values from your Wrangler file take precedence for that deployment. Settings configured in the dashboard still apply to anything not overridden — including Previews created by teammates or by automated builds triggered from Git pushes. + +--- + +## Deploy a Preview + +You can deploy a Preview automatically every time you push to a branch using [Workers Builds](/workers/ci-cd/builds/), or manually from the command line with `wrangler preview`. + +### Automate with Workers Builds + +Connect a GitHub or GitLab repository, and Cloudflare deploys a Preview for you every time you push to a branch. + +1. Go to **Workers & Pages** > your Worker > **Settings** > **Builds**. +2. Connect your repository. +3. Set your tracked branch (usually `main`). + +When you push to `main`, Cloudflare builds and deploys your Worker to production using your top-level Wrangler configuration — your production bindings, environment variables, and secrets. When you push to any other branch, Cloudflare deploys a Preview using your Preview settings instead. + +The first push to a branch creates a new Preview. Each subsequent push creates a new **[Preview Deployment](/workers/previews/api/#preview-deployment)** within that Preview — an immutable snapshot of your code at that point. This means you can always go back and inspect or compare any previous deployment, even after pushing new changes. + +A bot comments on your pull request with: + +- A link to the **[Preview URL](#preview-urls-and-deployment-urls)** (which always serves the latest deployment) +- A **[Deployment URL](#preview-urls-and-deployment-urls)** (which points to that exact snapshot) +- A link to the build logs + +### Deploy manually with Wrangler + +Run `wrangler preview` from your project directory to build your code locally and deploy it as a Preview: + + + +By default, Wrangler names the Preview after your current Git branch. You can also provide a name explicitly: + + + +If a Preview with that name does not exist yet, Wrangler creates it. If it already exists, Wrangler creates a new deployment within it. Each time you run `wrangler preview`, Wrangler prints the Preview URL and Deployment URL to your terminal. + +Use this for ad-hoc testing, external CI/CD pipelines, or when you are not using a Git-based workflow. + +--- + +## Preview URLs and Deployment URLs + +When you deploy a Preview, Cloudflare generates two URLs. For the full URL patterns across `workers.dev`, `cloudflare.app`, and custom domains, refer to [URL structure](/workers/previews/api/#url-structure). + +- **Preview URL** — always serves the latest deployment. When you push new code, the Preview URL points to the new deployment automatically. Share this with teammates when you want them to always see your latest changes. + + ```txt + https://my-feature-my-worker.my-account.workers.dev + ``` + +- **Deployment URL** — points to one specific deployment and never changes, even after you push newer code. Use this to pin to an exact version — for example, to compare two deployments side by side or to link to the exact build that a reviewer approved. + + ```txt + https://f498jo-my-worker.my-account.workers.dev + ``` + +Workers Builds posts both URLs as a comment on your pull request. `wrangler preview` prints both to your terminal. + +--- + +## Enable or disable Preview URLs + +You control whether Preview URLs are active through the `previews_enabled` setting on your Worker's subdomain configuration. By default, `previews_enabled` follows the state of your `workers.dev` subdomain — if you disable your `workers.dev` route, Preview URLs are also disabled unless you explicitly enable them. + +You can toggle this through the dashboard, the API, or your Wrangler configuration file: + + + +```toml +# Explicitly enable Preview URLs +preview_urls = true +``` + + + +--- + +## Use a custom domain for Previews + +You can serve Previews from your own **custom domain** instead of (or in addition to) `workers.dev`. When you enable Previews on a custom domain, Cloudflare creates a wildcard DNS record and adds a wildcard SAN to the domain's certificate to cover all Preview subdomains. For more details, refer to [Custom domains](/workers/previews/api/#custom-domains). + +| URL type | Example | +| ---------- | ----------------------------------- | +| Production | `app.example.com` | +| Preview | `my-feature.app.example.com` | +| Deployment | `f498jo-my-feature.app.example.com` | + +To enable Previews on a custom domain via the API, set `previews_enabled` to `true`: + +```bash +curl -X PUT \ + "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/domains" \ + -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "zone_id": "", + "hostname": "app.example.com", + "service": "my-worker", + "previews_enabled": true + }' +``` + +Or in your Wrangler configuration file: + + + +```toml +[[routes]] +pattern = "app.example.com" +custom_domain = true +previews_enabled = true +``` + + + +You can also disable production routing on a custom domain while keeping Previews active by setting `production_enabled` to `false` — this serves only Preview traffic on that domain. + +--- + +## Secure access to Previews + +To restrict who can access your Preview URLs, you can enable [Cloudflare Access](/cloudflare-one/access-controls/policies/) with a single click in your Worker's settings. This protects both Preview URLs and Deployment URLs. + +1. Go to **Workers & Pages** > your Worker > **Settings** > **Domains & Routes**. +2. For Preview URLs, select **Enable Cloudflare Access**. +3. Optionally, select **Manage Cloudflare Access** to configure which users, groups, or email addresses are authorized. + +For more information on configuring access policies, refer to [Access policies](/cloudflare-one/access-controls/policies/). + +--- + +## Observability and logging + +Each Preview runs its own observability and logging settings, independent of your production Worker. This means you can: + +- Set full sampling (`head_sampling_rate: 1`) on a Preview to capture every request during testing, without increasing log volume on your production Worker. +- Enable [Logpush](/workers/observability/logpush/) to send a Preview's logs to your own storage for debugging. +- Attach [Tail Workers](/workers/observability/logs/tail-workers/) to a Preview to pipe its logs into a custom analysis pipeline. + +Configure these in the dashboard under **Workers & Pages** > your Worker > **Settings** > **Preview Defaults**, and Cloudflare applies them to every Preview created for that Worker. For details on how observability settings are structured, refer to the [Preview object](/workers/previews/api/#preview-object). + +--- + +## Limits and lifecycle + +- Previews do not count against the account-level Worker limit (100 free / 500 paid). They have a separate limit: **1,000** for Free plans, **10,000** for Paid plans. +- If you hit the limit, creating a new Preview automatically deletes the least-recently-deployed existing Preview to make room. +- Previews are automatically deleted after a TTL since their last deployment. +- Previews cannot be addressed by user-managed triggers (routes, cron triggers, queues). They are reachable only through their auto-generated URLs and custom-domain wildcard URLs. +- [Service bindings](/workers/runtime-apis/bindings/service-bindings/) on a Preview always target the production instance of the bound Worker. You cannot point a service binding at another Preview. +- Previews cannot be gradually deployed. All other runtime features — [Durable Objects](/durable-objects/), observability, [placement](/workers/configuration/smart-placement/), [Tail Workers](/workers/observability/logs/tail-workers/) — are supported. diff --git a/src/content/docs/workers/static-assets/migration-guides/migrate-from-pages.mdx b/src/content/docs/workers/static-assets/migration-guides/migrate-from-pages.mdx index ad29e20a1c94762..ebf5c6828b55fb1 100644 --- a/src/content/docs/workers/static-assets/migration-guides/migrate-from-pages.mdx +++ b/src/content/docs/workers/static-assets/migration-guides/migrate-from-pages.mdx @@ -321,7 +321,7 @@ Pages automatically creates a preview environment for each project, and can be i To get a similar experience in Workers, you must: -1. Ensure [preview URLs](/workers/configuration/previews/) are enabled (they are on by default). +1. Ensure [preview URLs](/workers/previews/) are enabled (they are on by default). @@ -341,7 +341,7 @@ To get a similar experience in Workers, you must: 1. [Enable non-production branch builds](/workers/ci-cd/builds/build-branches/#configure-non-production-branch-builds) in Workers Builds. -Optionally, you can also [protect these preview URLs with Cloudflare Access](/workers/configuration/previews/#manage-access-to-preview-urls). +Optionally, you can also [protect these preview URLs with Cloudflare Access](/workers/previews/#manage-access-to-preview-urls). :::note @@ -412,7 +412,7 @@ This compatibility matrix compares the features of Workers and Pages. Unless oth | [Cloudflare Vite plugin](/workers/vite-plugin/) | ✅ | ❌ | | [Rollbacks](/workers/configuration/versions-and-deployments/rollbacks/) | ✅ | ✅ | | [Gradual Deployments](/workers/configuration/versions-and-deployments/) | ✅ | ❌ | -| [Preview URLs](/workers/configuration/previews) | ✅ | ✅ | +| [Preview URLs](/workers/previews/) | ✅ | ✅ | | [Testing tools](/workers/testing) | ✅ | ✅ | | [Local Development](/workers/development-testing/) | ✅ | ✅ | | [Remote Development (`--remote`)](/workers/wrangler/commands/) | ✅ | ❌ | diff --git a/src/content/docs/workers/wrangler/configuration.mdx b/src/content/docs/workers/wrangler/configuration.mdx index 02d8cb5ac7b9c9c..c2eda5a701e6206 100644 --- a/src/content/docs/workers/wrangler/configuration.mdx +++ b/src/content/docs/workers/wrangler/configuration.mdx @@ -171,7 +171,7 @@ The `main` key is optional for assets-only Workers. - Enables use of `*.workers.dev` subdomain to deploy your Worker. If you have a Worker that is only for `scheduled` events, you can set this to `false`. Defaults to `true`. Refer to [types of routes](#types-of-routes). - `preview_urls` - - Enables use of Preview URLs to test your Worker. Defaults to value of `workers_dev`. Refer to [Preview URLs](/workers/configuration/previews). + - Enables use of Preview URLs to test your Worker. Defaults to value of `workers_dev`. Refer to [Preview URLs](/workers/previews/). - `route` - A route that your Worker should be deployed to. Only one of `routes` or `route` is required. Refer to [types of routes](#types-of-routes). diff --git a/src/content/partials/workers/custom_headers.mdx b/src/content/partials/workers/custom_headers.mdx index b141e5868dc94b2..03cd713e8756993 100644 --- a/src/content/partials/workers/custom_headers.mdx +++ b/src/content/partials/workers/custom_headers.mdx @@ -104,7 +104,7 @@ To enable other domains to fetch every static asset from your {props.product === {props.product === "workers" ? ( <> -This applies the `Access-Control-Allow-Origin` header to any incoming URL. Note that the CORS specification only allows `*`, `null`, or an exact origin as valid `Access-Control-Allow-Origin` values — wildcard patterns within origins are not supported. To allow CORS from specific preview URLs, you will need to handle this dynamically in your Worker code rather than through the `_headers` file. +This applies the `Access-Control-Allow-Origin` header to any incoming URL. Note that the CORS specification only allows `*`, `null`, or an exact origin as valid `Access-Control-Allow-Origin` values — wildcard patterns within origins are not supported. To allow CORS from specific preview URLs, you will need to handle this dynamically in your Worker code rather than through the `_headers` file. ) : ( <>