From b7f0e52ed4ebd979212832d1b5df818c940ba0d4 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Wed, 15 Apr 2026 13:09:24 +0200 Subject: [PATCH 1/4] Add `routes` option to `cloudflare.Worker` --- platform/src/components/cloudflare/worker.ts | 83 ++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/platform/src/components/cloudflare/worker.ts b/platform/src/components/cloudflare/worker.ts index 4b4aa3d8b1..cac2df4c11 100644 --- a/platform/src/components/cloudflare/worker.ts +++ b/platform/src/components/cloudflare/worker.ts @@ -3,6 +3,7 @@ import path from "path"; import crypto from "crypto"; import { ComponentResourceOptions, + Output, output, all, jsonStringify, @@ -66,6 +67,44 @@ export interface WorkerArgs { * ``` */ domain?: Input; + /** + * Attach the Worker to one or more [route patterns](https://developers.cloudflare.com/workers/configuration/routing/routes/) + * instead of a dedicated custom domain. + * + * Unlike `domain`, which gives the Worker a full hostname, routes are + * pattern-based. This lets multiple Workers (or a Pages project) share a + * hostname and fall through to the origin for unmatched paths. Use routes + * when you want to put a Worker in front of only part of a site, e.g. + * `example.com/api/*`. + * + * Each pattern must start with a concrete hostname that belongs to a + * Cloudflare zone on the same account. Wildcard hostnames like `*.example.com` + * are not supported here. + * + * A proxied DNS record for the hostname must already exist in the zone — + * routes do not auto-manage DNS, unlike custom domains. You can create + * one in the Cloudflare dashboard or alongside the Worker with a + * `cloudflare.DnsRecord` resource. + * + * Cannot be used together with `domain`. + * + * @example + * + * ```js + * { + * routes: ["example.com/api/*"] + * } + * ``` + * + * Multiple patterns on the same Worker: + * + * ```js + * { + * routes: ["example.com/api/*", "api.example.com/*"] + * } + * ``` + */ + routes?: Input[]>; /** * Configure how your function is bundled. * @@ -294,6 +333,7 @@ export class Worker extends Component implements Link.Linkable { private workerUrl: WorkerUrl; private workerPlacement?: WorkerPlacement; private workerDomain?: cf.WorkerDomain; + private workerRoutes?: Output; constructor(name: string, args: WorkerArgs, opts?: ComponentResourceOptions) { super(__pulumiType, name, args, opts); @@ -324,11 +364,13 @@ export class Worker extends Component implements Link.Linkable { const workerUrl = createWorkersUrl(); const workerPlacement = createWorkerPlacement(); const workerDomain = createWorkersDomain(); + const workerRoutes = createWorkersRoutes(); this.script = script; this.workerUrl = workerUrl; this.workerPlacement = workerPlacement; this.workerDomain = workerDomain; + this.workerRoutes = workerRoutes; all([dev, buildInput, script.scriptName]).apply( async ([dev, buildInput, scriptName]) => { @@ -619,6 +661,43 @@ export class Worker extends Component implements Link.Linkable { { parent }, ); } + + function createWorkersRoutes() { + if (!args.routes) return; + if (args.domain) + throw new VisibleError( + `Cannot set both "domain" and "routes" on the "${name}" Worker. Use "domain" for a dedicated hostname, or "routes" for pattern-based routing.`, + ); + + return output(args.routes).apply((patterns) => + patterns.map((pattern, i) => { + const hostname = pattern.split("/")[0]; + if (!hostname || hostname.includes("*")) + throw new VisibleError( + `Route pattern "${pattern}" on the "${name}" Worker must start with a concrete hostname (e.g. "example.com/*"). Wildcard hostnames are not supported.`, + ); + + const zone = new ZoneLookup( + `${name}Route${i}ZoneLookup`, + { + accountId: DEFAULT_ACCOUNT_ID, + domain: hostname, + }, + { parent }, + ); + + return new cf.WorkersRoute( + `${name}Route${i}`, + { + zoneId: zone.id, + pattern, + script: script.scriptName, + }, + { parent }, + ); + }), + ); + } } /** @@ -639,6 +718,10 @@ export class Worker extends Component implements Link.Linkable { * The Cloudflare Worker script. */ worker: this.script, + /** + * The Cloudflare Worker routes, if `routes` was set. + */ + routes: this.workerRoutes, }; } From 695da9571bfdc3ca5f45d97df56a8b149cdb0b4f Mon Sep 17 00:00:00 2001 From: Makisuo Date: Wed, 15 Apr 2026 13:32:24 +0200 Subject: [PATCH 2/4] Allow `WorkersRoute` in component physical-name validation --- platform/src/components/component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/platform/src/components/component.ts b/platform/src/components/component.ts index 919f023003..fe23a083c6 100644 --- a/platform/src/components/component.ts +++ b/platform/src/components/component.ts @@ -184,6 +184,7 @@ export class Component extends ComponentResource { "cloudflare:index/dnsRecord:DnsRecord", "cloudflare:index/workersCronTrigger:WorkersCronTrigger", "cloudflare:index/workersCustomDomain:WorkersCustomDomain", + "cloudflare:index/workersRoute:WorkersRoute", "cloudflare:index/queueConsumer:QueueConsumer", "docker-build:index:Image", "vercel:index/dnsRecord:DnsRecord", From dc57cff308ba1593912c224aa954e68501ad4283 Mon Sep 17 00:00:00 2001 From: Makisuo <31933546+Makisuo@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:47:20 +0200 Subject: [PATCH 3/4] Update platform/src/components/cloudflare/worker.ts Co-authored-by: Victor Navarro --- platform/src/components/cloudflare/worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/src/components/cloudflare/worker.ts b/platform/src/components/cloudflare/worker.ts index cac2df4c11..60ff22e9b5 100644 --- a/platform/src/components/cloudflare/worker.ts +++ b/platform/src/components/cloudflare/worker.ts @@ -104,7 +104,7 @@ export interface WorkerArgs { * } * ``` */ - routes?: Input[]>; + routes?: Input[]; /** * Configure how your function is bundled. * From fd84aea2a02a0a796a5c119d3b7a829f382da7a1 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Wed, 15 Apr 2026 17:04:24 +0200 Subject: [PATCH 4/4] Refactor `createWorkersRoutes` to avoid Pulumi resource-in-apply leak --- platform/src/components/cloudflare/worker.ts | 52 ++++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/platform/src/components/cloudflare/worker.ts b/platform/src/components/cloudflare/worker.ts index 60ff22e9b5..fc36d2396f 100644 --- a/platform/src/components/cloudflare/worker.ts +++ b/platform/src/components/cloudflare/worker.ts @@ -3,7 +3,6 @@ import path from "path"; import crypto from "crypto"; import { ComponentResourceOptions, - Output, output, all, jsonStringify, @@ -333,7 +332,7 @@ export class Worker extends Component implements Link.Linkable { private workerUrl: WorkerUrl; private workerPlacement?: WorkerPlacement; private workerDomain?: cf.WorkerDomain; - private workerRoutes?: Output; + private workerRoutes?: cf.WorkersRoute[]; constructor(name: string, args: WorkerArgs, opts?: ComponentResourceOptions) { super(__pulumiType, name, args, opts); @@ -669,34 +668,35 @@ export class Worker extends Component implements Link.Linkable { `Cannot set both "domain" and "routes" on the "${name}" Worker. Use "domain" for a dedicated hostname, or "routes" for pattern-based routing.`, ); - return output(args.routes).apply((patterns) => - patterns.map((pattern, i) => { - const hostname = pattern.split("/")[0]; - if (!hostname || hostname.includes("*")) + return args.routes.map((pattern, i) => { + const hostname = output(pattern).apply((p) => { + const h = p.split("/")[0]; + if (!h || h.includes("*")) throw new VisibleError( - `Route pattern "${pattern}" on the "${name}" Worker must start with a concrete hostname (e.g. "example.com/*"). Wildcard hostnames are not supported.`, + `Route pattern "${p}" on the "${name}" Worker must start with a concrete hostname (e.g. "example.com/*"). Wildcard hostnames are not supported.`, ); + return h; + }); - const zone = new ZoneLookup( - `${name}Route${i}ZoneLookup`, - { - accountId: DEFAULT_ACCOUNT_ID, - domain: hostname, - }, - { parent }, - ); + const zone = new ZoneLookup( + `${name}Route${i}ZoneLookup`, + { + accountId: DEFAULT_ACCOUNT_ID, + domain: hostname, + }, + { parent }, + ); - return new cf.WorkersRoute( - `${name}Route${i}`, - { - zoneId: zone.id, - pattern, - script: script.scriptName, - }, - { parent }, - ); - }), - ); + return new cf.WorkersRoute( + `${name}Route${i}`, + { + zoneId: zone.id, + pattern, + script: script.scriptName, + }, + { parent }, + ); + }); } }