A reusable GitHub Action for deploying preview and production environments to Cloudflare Workers with automatic PR comments and stable preview URLs.
Stable Preview URLs with Aliases
- Get predictable PR-specific URLs:
pr-123-{worker}.{subdomain}.workers.dev - Aliases persist across commits to the same PR
- Secrets and bindings are preserved (same worker, different version)
Version-Based Preview Strategy
- Uses
wrangler versions upload --preview-aliasfor previews - Creates isolated versions without affecting production traffic
- Uses
wrangler deployfor production deployments
Smart PR Comments
- Updates the same comment on subsequent pushes (no PR spam)
- Shows preview URL and version ID
Workflow Flexibility
- Integrate with larger workflows (run tests first, conditional deploys, etc.)
- Manual deployment triggers with
workflow_dispatch - Monorepo support with
working_directory
- Deploy to Cloudflare Workers with stable preview URLs
- Support for both preview and production deployments
- Stable preview aliases (
pr-123-{worker}.{subdomain}.workers.dev) - Smart PR comments that update instead of spam
- Support for Bun, npm, and pnpm
- Prebuild and predeploy scripts for custom workflows
- Monorepo support with
working_directory - GitHub Actions summary
name: Deploy Preview
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: w3dev/cloudflare-worker-deploy@main
with:
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}name: Deploy Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: w3dev/cloudflare-worker-deploy@main
with:
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
environment: production- uses: w3dev/cloudflare-worker-deploy@main
with:
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
working_directory: 'apps/api'
alias_prefix: 'api' # Creates api-pr-123-{worker}.{subdomain}.workers.dev- uses: w3dev/cloudflare-worker-deploy@main
with:
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
prebuild_script: |
npm run generate
npm run build- uses: w3dev/cloudflare-worker-deploy@main
with:
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
package_manager: 'pnpm'- uses: w3dev/cloudflare-worker-deploy@main
with:
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
environment: production
predeploy_script: |
echo "Running pre-deployment checks..."
npm run lint
npm run testYou can manually trigger a deployment for an existing PR using workflow_dispatch. This is useful when:
- You need to redeploy a PR without pushing a new commit
- You want to deploy from a different branch (e.g.,
main) to test against a PR's preview alias - CI failed for unrelated reasons and you want to retry
Add workflow_dispatch with a pr_number input to your workflow:
name: Deploy Preview
on:
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to deploy'
required: false
permissions:
contents: read
pull-requests: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: w3dev/cloudflare-worker-deploy@main
with:
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}When triggered manually with a PR number, the action will:
- Generate the preview alias (
pr-{number}) - Deploy a new version with that alias
- Post/update the comment on the specified PR
| Input | Description | Required | Default |
|---|---|---|---|
cloudflare_api_token |
Cloudflare API token with Workers edit permission | Yes | - |
cloudflare_account_id |
Cloudflare Account ID | Yes | - |
teardown |
Set to true to handle PR close cleanup |
No | false |
package_manager |
bun, npm, or pnpm |
No | bun |
node_version |
Node.js version | No | 22 |
working_directory |
Build directory | No | . |
alias_prefix |
Prefix for preview alias | No | - |
prebuild_script |
Script to run before deployment | No | - |
predeploy_script |
Script to run before deployment | No | - |
install_command |
Custom install command | No | - |
environment |
Deployment environment (preview or production) |
No | preview |
github_token |
Token for PR comments | No | github.token |
wrangler_version |
Wrangler CLI version (e.g., 4.42.0) |
No | latest |
pnpm_version |
pnpm version. Leave empty to use packageManager from package.json |
No | - |
| Output | Description |
|---|---|
deployment_url |
The Cloudflare Workers deployment URL |
preview_url |
The aliased preview URL (for preview deployments) |
version_url |
The version-specific preview URL (for preview deployments) |
version_id |
The uploaded version ID |
pr_number |
The PR number (if applicable) |
- Production:
https://{worker-name}.{subdomain}.workers.dev - Preview (aliased):
https://{alias}-{worker-name}.{subdomain}.workers.dev- Example:
https://pr-123-my-api.my-account.workers.dev - With prefix:
https://api-pr-123-my-api.my-account.workers.dev
- Example:
- A Cloudflare account with Workers enabled
- A
wrangler.tomlfile in your project (or working directory) - A Cloudflare API token with Workers edit permission
- Your Cloudflare Account ID
- Go to Cloudflare Dashboard
- Create a token with the following permissions:
- Account > Workers Scripts > Edit
- Account > Account Settings > Read
- Copy the token and add it as a GitHub secret (
CLOUDFLARE_API_TOKEN)
Your Account ID is visible in the Cloudflare dashboard URL when you're viewing your account:
https://dash.cloudflare.com/{account-id}/...
Or run wrangler whoami locally to see it.
name: API Preview
on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- 'apps/api/**'
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to deploy'
required: false
permissions:
contents: read
pull-requests: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: w3dev/cloudflare-worker-deploy@main
id: deploy
with:
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
package_manager: 'bun'
working_directory: 'apps/api'
alias_prefix: 'api'
- name: Use deployment URL
run: echo "Deployed to ${{ steps.deploy.outputs.deployment_url }}"From Cloudflare documentation:
- Durable Objects: Preview URLs are not generated for Workers with Durable Objects
- Logging: Cannot view logs for preview URLs (no
wrangler tail, Workers Logs, or Logpush) - Domain: Preview URLs only work on
workers.devsubdomain - Wrangler version: Requires Wrangler v4.21.0+ for aliased preview URLs
- Alias cleanup: Cloudflare auto-cleans older aliases (keeps last 1000)
Uses wrangler versions upload --preview-alias to:
- Create a new version without deploying to production
- Get a stable aliased preview URL
- Preserve secrets and bindings (same worker, different version)
Uses wrangler deploy to:
- Upload and immediately deploy to production
- Deploy to the main worker URL
MIT