Deploy an internal drag-and-drop static site platform for your company using Workers for Platforms. Employees upload files and get a live URL -- every site is protected behind Cloudflare Access.
- Drag & drop deploy - Upload a folder or ZIP file and get a live URL instantly
- Protected by Access - Every site sits behind Cloudflare Access. Employees sign in with your company identity provider
- Subdomain routing - Each site gets its own subdomain:
site-name.yourcompany.com - Works on workers.dev - Preview HTML, CSS, and JavaScript immediately, no custom domain required
- Deployment tracking - Tracks who deployed what and when, stored in D1
- Admin dashboard - View all deployed sites and deployment history at
/admin
- Workers for Platforms - Each deployed site becomes an isolated Worker in a dispatch namespace. The platform routes requests to the correct site Worker
- D1 - Stores site metadata (name, slug, owner, timestamps) and deployment history
- Cloudflare Access - Enforces company login. The platform reads authenticated user identity via
ctx.access— no manual JWT verification or secrets required
- dispatcher (Workers for Platforms) - Routes requests to deployed site Workers
- DB (D1) - Stores site metadata and deployment history
The platform needs an API token to deploy Workers into the dispatch namespace.
- Go to API Tokens
- Click Create Token > Create Custom Token
- Set permissions: Account > Workers Scripts > Edit
- Scope it to your account only
- Copy the token — you will enter it when prompted during the Deploy to Cloudflare flow
Click the Deploy to Cloudflare button above and follow the prompts. Enter your API token when prompted.
After deployment completes:
- Go to Workers & Pages in the Cloudflare dashboard
- Click on your newly deployed Worker (named
internal-sites-templateby default) - Go to Settings > Domains & Routes
- Under Worker URL, click Enable and confirm — this enables your
workers.devURL
Protect your Worker with Cloudflare Access so only company employees can access it.
- Go to Workers & Pages and select your Worker
- Select the Access tab
- Select Protect this Worker behind Access
- Choose All traffic to keep this Worker and all sites deployed by employees private by default
- Under Authentication policy, select
Emails ending in→@yourcompany.comto restrict access to your company email domain - Optionally review the session duration
- Select Apply Access
Every request now requires company login. The platform Worker reads the authenticated user's identity via ctx.access — no manual JWT verification or secrets required. Access handles authentication at the edge before your Worker runs.
- Go back to Workers & Pages and select your Worker
- On the Overview tab, click the
workers.devlink to open the platform - Upload a folder or ZIP containing an
index.html - Click Deploy site
- Open the generated URL shown after deployment
Update SITE_DOMAIN in wrangler.jsonc to your domain. The platform switches to subdomain routing automatically.
a. Update config in wrangler.jsonc:
b. Add DNS records in your Cloudflare DNS settings:
| Type | Name | Content | Proxy |
|---|---|---|---|
| A | @ |
192.0.2.1 |
Proxied |
| A | * |
192.0.2.1 |
Proxied |
c. Redeploy:
npm run deploynpm run deploy provisions the dispatch namespace first and stops without deploying if provisioning fails.
┌─────────────────────────────────────────────────────────────┐
│ Platform Worker (this template) │
├─────────────────────────────────────────────────────────────┤
│ yourcompany.com/deploy → Drag & drop deploy UI │
├─────────────────────────────────────────────────────────────┤
│ Deployed Sites (Workers for Platforms) │
│ ├── docs.yourcompany.com → Employee's site │
│ ├── handbook.yourcompany.com → Employee's site │
│ └── ... │
├─────────────────────────────────────────────────────────────┤
│ Cloudflare Access │
│ └── All routes require company identity provider login │
└─────────────────────────────────────────────────────────────┘
On workers.dev (testing mode), sites use path-based routing instead:
your-worker.workers.dev/deploy → Deploy UI
your-worker.workers.dev/sites/docs/ → Deployed site
Sites previewed on workers.dev share a web address with the deploy and admin pages, so previews have extra browser restrictions to keep those management tools safe. Regular HTML, CSS, and JavaScript still work. If a site needs browser storage such as localStorage or needs to install a service worker, set up custom subdomains first.
npm testnpm test runs fully locally in Miniflare. It requires no Cloudflare login or account, makes no Cloudflare API calls, and creates no remote resources.
npm run devThis starts the platform Worker with a local D1 database at http://localhost:8787. Dispatch namespaces cannot run locally, so deploying or opening uploaded sites requires the remote setup below.
To deploy test sites from the local UI, you need a Cloudflare account with Workers for Platforms enabled.
What runs where:
npm run dev:remotestarts the platform Worker athttp://localhost:8787. The platform Worker and D1 run locally in Miniflare, and D1 data persists under.wrangler/state. Thedispatcherbinding keeps"remote": true, so the dispatch namespace and uploaded site Workers are real remote Cloudflare resources. Deploying a site from the local UI creates or updates a real Worker in your Cloudflare account.
1. Install dependencies
npm installThis runs the local-only build/type check and does not contact Cloudflare or create resources.
2. Sign in to Cloudflare
The remote dispatch binding requires a Cloudflare login and selection of the account that contains the dispatch namespace:
npm exec -- wrangler loginIf Wrangler prompts you to choose an account, select the account you will configure in the next step.
3. Set your Account ID
Find your Account ID on the Cloudflare dashboard (copy from the right sidebar of the account home page). Set it in wrangler.jsonc so the remote dispatch binding and deployment API use the intended account:
"vars": {
"ACCOUNT_ID": "your-account-id"
}4. Provision the dispatch namespace
This explicit setup command creates the Workers for Platforms namespace where deployed sites are stored. It exits with an error if provisioning fails:
npm run setup5. Create the D1 database
npm exec -- wrangler d1 create internal-sites-platformCopy the database_id from the output and paste it into wrangler.jsonc:
"d1_databases": [
{
"binding": "DB",
"database_name": "internal-sites-platform",
"database_id": "paste-your-database-id-here"
}
]6. Create an API token
The platform needs a token to deploy Workers into the dispatch namespace:
- Go to API Tokens
- Create Custom Token with permission: Account > Workers Scripts > Edit
- Copy the token and save it to
.dev.vars:
cp .dev.vars.example .dev.varsEdit .dev.vars and replace your-token-here with the real token.
7. Run locally with the remote dispatch namespace
npm run dev:remoteOpen http://localhost:8787/deploy to use the platform.
Local dev uses path-based routing automatically (/sites/site-name/). Cloudflare Access is simulated locally using the access.dev block in wrangler.jsonc, which provides a mock identity (local-dev@localhost) via ctx.access. To test as a different user, change the identity fields in the access.dev block.
| Problem | Solution |
|---|---|
| "Setup required: Enable Cloudflare Access" | Open Workers & Pages, select your Worker, and follow Require company login in the Setup section above |
| "Could not read your Access identity" | Sign in again. If the problem continues, confirm Access is enabled on this Worker |
| "Could not create asset upload session" | Check that DISPATCH_NAMESPACE_API_TOKEN is set with Workers Scripts Edit permission |
| "Dispatch namespace not found" | Enable Workers for Platforms and run npm run setup |
| 404 on deployed sites | Ensure uploaded files include index.html at the root |
| Database errors | Tables auto-create on first request. Check the D1 database in the Cloudflare dashboard |
| "Could not delete site from Cloudflare" | Check that DISPATCH_NAMESPACE_API_TOKEN is valid and has Workers Scripts Edit permission |
View logs:
npm exec -- wrangler tailThe admin page (/admin) shows all deployed sites and deployment history. Protect it with Cloudflare Access so only admins can reach it:
- Go to Zero Trust → Access → Applications
- Click Create new application → Continue with self-hosted and private
- Under Destinations > Public hostnames, configure your Worker's domain:
- Subdomain: the name of your Worker (e.g.
internal-sites-template) - Domain: select your
*.workers.devdomain from the dropdown, or your custom domain if you have one configured - Path:
admin*
- Subdomain: the name of your Worker (e.g.
- Add an Access policy and configure who can access it — for example, restrict to specific admin email addresses
- Save the application
This scopes the policy to /admin* only, so employees can still reach /deploy freely without an additional login step.
- Cloudflare Account with Workers for Platforms enabled
- Node.js 22+
Apache-2.0
{ "workers_dev": false, "vars": { "SITE_DOMAIN": "yourcompany.com", }, "routes": [ { "pattern": "yourcompany.com/*", "zone_name": "yourcompany.com" }, { "pattern": "*.yourcompany.com/*", "zone_name": "yourcompany.com" }, ], }