You can automate a Nuxt, Vue, React, or Lovable deployment end to end using Cloudflare’s Workers Builds API: create a Worker, connect the GitHub repository, generate a build token, wire up a trigger, run the first build, and attach a custom domain, all through nine sequential API calls. Once that setup is done, every future git push to the connected branch triggers an automatic build and deployment with no manual step in between. This is the architecture behind a one-click “deploy this repo” platform, and it’s what we use internally when spinning up new client Worker deployments.
What You’re Building
The end state looks like this: a developer pushes to GitHub, Cloudflare Workers Builds picks up the push, installs dependencies, runs the build command, deploys the Worker, and the change is live on a custom domain, all without anyone touching a deploy button. Getting there requires a one-time setup sequence run through the Cloudflare API. After that sequence completes, the automation runs itself.
The setup sequence, in order:
1. Create the Worker
2. Get the Worker's tag (not the same as its name)
3. Connect the GitHub repository
4. Create a build token
5. Create a build trigger
6. Run the first build manually
7. Poll until the build finishes
8. Attach a custom domain
9. Push to GitHub from now on -- Cloudflare handles the rest
Before You Start
You’ll need a Cloudflare account with a domain already on it, a GitHub account with the repository you want to deploy, and a Cloudflare API token. Workers Builds currently supports GitHub and GitLab (not self-hosted instances of either), and it deploys automatically on every push to whichever branch you configure.
Step 1: Create a Cloudflare API Token
Everything in this guide runs through one API token. Create a custom token in the Cloudflare dashboard with:
| Resource | Permission | Why |
|---|---|---|
| Account | Workers Builds Configuration: Edit | Create build tokens, repo connections, and triggers (Steps 5–8) |
| Account | Workers Scripts: Edit | Create the Worker itself (Step 3) |
| Account | Workers Scripts: Read | Look up the Worker tag (Step 4) |
| Zone | Workers Routes: Edit | Attach the custom domain to a zone (Step 10) |
These four come from actually running the full flow end to end, not just from Cloudflare’s own Builds API documentation, which only documents the first two permissions and explicitly doesn’t cover what’s needed to create a Worker, connect a repository, or attach a custom domain. Skip Workers Scripts: Edit and Step 3 fails outright. Skip the zone-level Workers Routes: Edit and Step 10 fails the same way, with an error that won’t point you back at a missing permission. The Workers Builds API specifically requires a user-scoped token; account-scoped tokens return an “Invalid token” error here even if they’d work fine elsewhere.
Step 2: Connect Cloudflare’s GitHub App
This part can’t be done through the API and only needs doing once per GitHub account. In the Cloudflare dashboard, go to Workers & Pages, open any Worker’s Settings, go to Builds, select Connect, choose GitHub, and authorize the Cloudflare GitHub App. If the repository you want to deploy is private, make sure the app has access to it specifically, GitHub scopes app permissions per repository, not account-wide by default.
Once that authorization exists, every remaining step in this guide runs entirely through the API.
Step 3: Create the Worker
POST /accounts/{ACCOUNT_ID}/workers/workers
Authorization: Bearer {CLOUDFLARE_API_TOKEN}
Content-Type: application/json
{
"name": "my-awesome-site"
}
The name you give it here becomes the Worker’s identifier in the dashboard and, later, its default subdomain.
Step 4: Get the Worker Tag
This is the step that trips people up first. Cloudflare has two separate identifiers for the same Worker: the name you just chose, and a “tag,” a long hex string the Workers Builds API actually expects. The Builds API calls this value external_script_id in its own docs, and it’s immutable once assigned.
GET /accounts/{ACCOUNT_ID}/workers/scripts
Authorization: Bearer {CLOUDFLARE_API_TOKEN}
The response includes both values for every Worker on the account:
{
"result": [
{
"id": "my-awesome-site",
"tag": "0de640790653441682fad7b1aa212122"
}
]
}
Save the tag value. You’ll need it in Step 6, not the Worker’s name.
Step 5: Connect the GitHub Repository
PUT /accounts/{ACCOUNT_ID}/builds/repos/connections
Authorization: Bearer {CLOUDFLARE_API_TOKEN}
Content-Type: application/json
{
"provider_type": "github",
"provider_account_id": "12345678",
"provider_account_name": "your-github-username",
"repo_id": "98765432",
"repo_name": "your-repository-name"
}
This tells Cloudflare which specific repository this Worker should build from. The response returns a repo_connection_uuid, save it, Step 7 needs it to wire the repository to a build trigger.
Step 6: Create a Build Token
This is the confusing part of the whole flow, confusing enough that we wrote a separate, deeper guide on just these two fields after getting burned by them ourselves. The short version:
POST /accounts/{ACCOUNT_ID}/builds/tokens
Authorization: Bearer {CLOUDFLARE_API_TOKEN}
Content-Type: application/json
{
"build_token_name": "my-awesome-site Build Token",
"build_token_secret": "{CLOUDFLARE_API_TOKEN}",
"cloudflare_token_id": "{CLOUDFLARE_TOKEN_ID}"
}
build_token_secret is not a random string you generate. It’s the secret of the actual Cloudflare API token from Step 1, the exact value you’ve been sending as a Bearer token this whole time. cloudflare_token_id isn’t your Account ID either, it’s a separate identifier for that same token, retrieved by calling:
GET /user/tokens/verify
Authorization: Bearer {CLOUDFLARE_API_TOKEN}
which returns result.id, the value that goes into cloudflare_token_id. Get both of these wrong and the request either fails outright or creates a token disconnected from the one you meant to use, with no error message that points back at which field was the problem.
A successful call returns a build_token_uuid. Save it for Step 7.
Step 7: Create the Build Trigger
The trigger is what actually ties everything together: the Worker, the repository, the build token, and the commands that build and deploy your project.
POST /accounts/{ACCOUNT_ID}/builds/triggers
Authorization: Bearer {CLOUDFLARE_API_TOKEN}
Content-Type: application/json
{
"external_script_id": "{WORKER_TAG}",
"repo_connection_uuid": "{REPO_CONNECTION_UUID}",
"build_token_uuid": "{BUILD_TOKEN_UUID}",
"trigger_name": "my-awesome-site production",
"build_command": "npm run build",
"deploy_command": "npx wrangler deploy",
"root_directory": "/",
"branch_includes": ["main"],
"branch_excludes": [],
"path_includes": ["*"],
"path_excludes": []
}
What root_directory Actually Means
If your project’s package.json sits at the repository root, use /. If your actual app lives in a subfolder (a monorepo with the frontend under /website, for example), point root_directory at that subfolder instead. Cloudflare runs both the build and deploy commands from whatever path you set here, not from the repository root by default.
Picking the Build Command
Nuxt, Vue, and React projects all typically use npm run build as their build command, though a Nuxt project targeting a specific Nitro preset might need something like NITRO_PRESET=node-server npm run build instead. Lovable projects are generally React and Vite under the hood, so the same default usually applies. In every case, check the project’s actual package.json scripts before assuming, the right command depends on how that specific project is configured, not on which framework generated it.
For the deploy command, npx wrangler deploy publishes to production. Cloudflare also supports npx wrangler versions upload for preview deployments that don’t touch the live production version, useful if you want a staging URL for every pull request without affecting the site your visitors see.
A successful call here returns a trigger_uuid. That’s the last identifier you’ll need.
Step 8: Run the First Build
The trigger you just created will fire automatically on future pushes, but nothing has actually deployed yet. Kick off the first build manually:
POST /accounts/{ACCOUNT_ID}/builds/triggers/{TRIGGER_UUID}/builds
Authorization: Bearer {CLOUDFLARE_API_TOKEN}
Content-Type: application/json
{
"branch": "main"
}
This returns a build_uuid almost immediately, but the build itself runs asynchronously. A queued response doesn’t mean the site is live yet.
Step 9: Poll for Build Status
GET /accounts/{ACCOUNT_ID}/builds/builds/{BUILD_UUID}
Authorization: Bearer {CLOUDFLARE_API_TOKEN}
Poll this every 10 to 15 seconds until status reaches a final state. The build_outcome field tells you what actually happened: success, fail, skipped, cancelled, or terminated. Only success means the Worker actually has a live deployment behind it, which matters for the next step.
Step 10: Attach a Custom Domain
PUT /accounts/{ACCOUNT_ID}/workers/domains
Authorization: Bearer {CLOUDFLARE_API_TOKEN}
Content-Type: application/json
{
"hostname": "my-awesome-site.yourdomain.com",
"service": "my-awesome-site",
"zone_id": "{ZONE_ID}",
"zone_name": "yourdomain.com"
}
Cloudflare handles the DNS record and certificate automatically once this call succeeds. The one hard requirement: the Worker needs at least one successful deployment behind it first. Attach the domain before that and Cloudflare returns an error naming the Worker and saying it has no deployments, which is why Step 9’s polling step isn’t optional to skip.
What Happens After Setup
This is the part that makes the whole exercise worth it. Every step above runs exactly once per project. After that, the workflow for the actual developer is just:
git add .
git commit -m "Update homepage"
git push origin main
Cloudflare’s Git integration detects the push, and the connected trigger runs the build and deploy commands automatically, no API call, no dashboard click, no manual redeploy. The build shows up as a check run or commit status directly on GitHub, so a developer can see whether their push succeeded without leaving their normal workflow.
Building This Into a One-Click Deploy Platform
Once you’ve run this sequence by hand once, it’s straightforward to wrap it into an internal tool: a form where someone picks a GitHub repository, sets a subdomain, and clicks deploy, while your backend runs Steps 3 through 10 automatically. The database only needs to persist a handful of identifiers per project so future operations don’t have to rediscover them:
{
"worker_name": "my-awesome-site",
"worker_tag": "0de640790653441682fad7b1aa212122",
"repo_connection_uuid": "3a7b682b-df4d-44a6-89d6-3be36e788c03",
"build_token_uuid": "b2b9e944-95d6-44f0-8d1d-4b428b1e12a6",
"trigger_uuid": "5d08947d-0c0e-4b55-a226-0aceeb04aa89",
"zone_id": "023e105f3ecef8ad9ca31a8372d0c353",
"hostname": "my-awesome-site.yourdomain.com"
}
This is exactly the kind of internal tooling that comes up naturally in web application development work once a team is shipping more than a couple of Worker-based projects, and it applies just as well to platforms built around AI-generated apps like Lovable, where the deployment step is often the only part left that still needs a human.
Keep the API Token Off the Browser
The Cloudflare API token used throughout this guide has account-level write access. It should never reach client-side JavaScript, not even briefly, not even behind a login screen. If you’re building the one-click platform described above, the browser should only ever send lightweight, non-sensitive data like the chosen repository name, branch, and subdomain. Your backend holds the actual Cloudflare token and makes every API call described in this guide on the user’s behalf. The token itself never crosses the network boundary between your backend and a browser tab, and it shouldn’t appear in logs, error messages, or anywhere a frontend build tool might accidentally bundle it.
Frequently Asked Questions
What’s the difference between a Worker’s name and its Worker tag?
The name is the human-readable identifier you chose when creating the Worker. The tag is a separate, immutable hex string Cloudflare assigns to it, and it’s the tag (labeled external_script_id in the Builds API) that the trigger creation call actually expects, not the name.
Does this work with GitLab instead of GitHub?
Yes. Cloudflare Workers Builds supports both GitHub and GitLab through the same underlying API, cloud-hosted accounts on either platform, not self-hosted instances of them.
Can I set this up without ever touching the Cloudflare dashboard?
Almost. Every step is available through the API except installing and authorizing Cloudflare’s GitHub App, which is a one-time, dashboard-only action per GitHub account. Everything after that, including every project you deploy afterward, runs through the API alone.
What happens if I attach a custom domain before the first build finishes?
The request fails with an error stating the Worker has no deployments. Poll the build status until it reports a successful outcome before attaching the domain.




