About cloudflare-pages-ops
A CLI-first skill for operating Cloudflare Pages through Wrangler, with REST API calls for operations beyond the CLI's coverage. It is intended for agents performing project, deployment, domain, and status tasks on Pages.
Key features
- 01Create Pages projects and deploy build directories
- 02Manage projects, deployments, and domains
- 03Check deployment stage and status
- 04Resolve REST parameters from bundled OpenAPI data
- 05Use caller-injected credentials without logging in
Use cases
- 01Create a Direct Upload Pages project
- 02Deploy an already-built directory
- 03Register a custom domain or inspect deployment status
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ bm629/ agent-skills --skill cloudflare-pages-opsAfter installing, type/cloudflare-pages-opsin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
assets/cli-index.mdcloudflare-pages-openapi.jsonendpoint-index.mdreferences/credentials.mdforge-amendments.logrest-api.mdsources.mdwrangler.mdscripts/add-custom-domain.shadd-custom-domain.sh.validation.mdcreate-project.shcreate-project.sh.validation.mddeploy.shdeploy.sh.validation.mddeployment-status.shdeployment-status.sh.validation.mdendpoint.pyendpoint.py.validation.mdDetails
From the project READMEcloudflare-pages-ops
Overview
This skill lets an agent perform Cloudflare Pages operations CLI-first (the Wrangler CLI) with a REST fallback on Cloudflare's official API — no SDK. Wrangler is the ergonomic common path (create a project, deploy a build directory); the REST API is the comprehensive long tail (custom domains, deployment status, project/deployment/domain management), grounded on a bundled Pages OpenAPI slice via an endpoint index + a $ref-resolver so coverage never loads the spec into context. The agent consumes the caller-injected account_id + token, picks the Wrangler command or REST operation, runs it, and parses the result. It drives Cloudflare Pages' static/JAMstack + serverless surface — not full-stack container hosting.
When to activate
- ✅ Creating a Pages project, or deploying a build directory to one (Direct Upload).
- ✅ Adding/listing a custom domain, or reading deployment status (the stage machine).
- ✅ Listing/inspecting/deleting projects, deployments (retry/rollback), or domains.
Do NOT activate when:
- You need full-stack container/app hosting (Render, Fly.io, Railway, Vercel SSR-as-PaaS) — Cloudflare Pages here is a static/JAMstack + serverless host only.
- You only need credential setup — credentials are provided by the caller; this skill does not provision or resolve them, and never runs
wrangler login(seereferences/credentials.md).
Workflow
Step 1 — Receive the injected credentials
The caller has already resolved the account and injected what this operation needs — consume it; do not look for a record or log in. You receive from context:
account_id(a non-secret plain context value — exported asCLOUDFLARE_ACCOUNT_IDand interpolated into the REST path) and the capability (web-hosting).- The token, by an ordered load rule the context carries the variable NAME for: the project-level
.envvalue if that file exists and defines the var, else the environment variable of that name (project.envfirst). The token value is never in context prose — only its variable name. Perform no scope resolution or directory walk; it is project.env, not.envrc.
Bridge into CLOUDFLARE_API_TOKEN (Bearer) + CLOUDFLARE_ACCOUNT_ID. Never wrangler login. Full contract: references/credentials.md.
Step 2 — Pick the common path (Wrangler) or the long tail (REST)
- Wrangler (common path): scan
assets/cli-index.mdforpages project create,pages deploy,pages project list,pages deployment list/tail. Detail:references/wrangler.md. - REST (long tail): scan
assets/endpoint-index.md, thenpython3 scripts/endpoint.py <operationId>to resolve params + body + a curl skeleton. Detail:references/rest-api.md. Never load the bundled slice into context.
Step 3 — Run it
The four bundled scripts cover the priority ops:
create-project.sh <name> [branch]—wrangler pages project create … --production-branch …(Direct-Upload project).deploy.sh <project> <dir> [branch]—wrangler pages deploy … --commit-dirty true(uploads the built dir).add-custom-domain.sh <project> <domain>— RESTPOST …/domains(Wrangler has no domain command).deployment-status.sh <project> <deployment-id>— RESTget-deployment-info(the stage machine).
For anything else: Wrangler command from cli-index.md, or a curl against the resolved REST op.
Step 4 — Handle the response
- Get the deployment id first.
deploy.sh(Wrangler) prints only text, not the iddeployment-status.shneeds — capture it withwrangler pages deployment list --project-name <name> --json | jq -r '.[0].Id'(deployment list --jsonreturns capitalized table keys, so.Id, not.id). - Deploys are async + staged. A deployment moves through
latest_stage.name∈ queued/initialize/clone_repo/build/deploy withstatus∈ success/idle/active/failure/canceled. Polldeployment-status.shuntil thedeploystage reachesstatus: success(done) or any stage hitsfailure. - Check the envelope, not just HTTP. Every REST response is
{success, errors, messages, result}— a200can carrysuccess: false; readerrors[].code/message. - Custom domain ≠ DNS. Adding a domain needs it on a Cloudflare zone with DNS pointed at the project.
- Limits: 1,200 requests / 5 min per user → HTTP 429.
Rules
Hard rules (never violate):
- Never
wrangler login. Auth is the injectedCLOUDFLARE_API_TOKEN+CLOUDFLARE_ACCOUNT_ID; Wrangler runs headlessly. A missing token/account_id is a hard caller error, not a cue to log in. - Never read or print the token value. Reference it only as
$CLOUDFLARE_API_TOKEN(env /Bearer); the subprocess reads it from the environment. The token lives only in.env(gitignored) or the environment.account_idis non-secret context, not a secret. - Deploy a directory with Wrangler, not curl. The REST
create-deploymentbody ismultipart/form-data; never hand-roll a directory deploy overcurl. - Resolve before constructing a REST call. Build a body/params from the
$ref-resolved schema (endpoint.py) — never from a guessed field set. - Check
success, not just HTTP. A200withsuccess:falseis a failure — readerrors[]. - This skill never writes credentials. Credentials are provided by the caller.
Preferences (override-able):
- Pass
--jsontowrangler pages project list/deployment listfor machine output. --commit-dirty trueon a deploy from an uncommitted tree.- Use REST for custom domains + per-deployment status (Wrangler has no command for those).
Gotchas
- Don't log in. Reaching for
wrangler loginwhen the token "isn't working" violates the contract — a bad token/account_id is an error to surface, not a login to perform. CLOUDFLARE_ACCOUNT_IDis required headlessly. Wrangler needs it alongside the token; a missing account id is the most common CI failure.- Deploy is multipart. The REST deploy endpoint is
multipart/form-data(binary parts) — usewrangler pages deploy <dir>; don't JSON-POST a deploy. - Direct Upload vs Git-connected.
wrangler pages deployworks on Direct-Upload projects; a Git-connected project deploys ongit pushand Wrangler won't deploy it. (A Direct-Upload project can't later switch to Git.) - The envelope hides failures.
{success:false}can ride a200; always check it. - Custom domain needs DNS — the API won't create it.
add-custom-domain.shonly registers the domain (status: initializing); the RESTPOST /domainsdoes NOT create the DNS record, even on a same-account zone (only the dashboard auto-creates it). Validation stays pending ("CNAME record not set") until aCNAME <domain> -> <project>.pages.devexists on the zone — and a Pages-scoped token can't create that record (needs Zone DNS:Edit), so the caller adds it out-of-band. pages deploy <dir>uploads, doesn't build. Pass the already-built output dir (dist/build/…); Wrangler doesn't run your framework build.- Rate limit. 1,200 req/5 min per user, cumulative — back off on 429.
Anti-patterns
- Running
wrangler login. Forbidden — the contract injects the token; never resolve your own auth or prompt the user to log in. - Echoing the token. Never
echo $CLOUDFLARE_API_TOKEN, never paste it into a printed command, never write its value to a file. Reference the env var only. (account_idis non-secret and may appear in paths.) - Hand-rolling a directory deploy over REST. The deploy is multipart — use Wrangler.
- Guessing a REST body. Resolve
create-project/add-domainwithendpoint.py— don't hand-write a body from memory. - Trusting the HTTP status alone. A
200withsuccess:falseis a failure — readerrors[]. - Loading the whole spec. Don't
catthe bundled slice — scan the index, resolve one op. (And never fetch the 10 MB full Cloudflare OpenAPI.)
Output
This skill produces CLI/API side effects (the requested Cloudflare Pages operation) and returns the parsed result to the calling agent. It writes no files of its own (it only consumes the caller-injected credentials). For a deploy it reports the deployment id + URL + terminal stage status; for a project it reports the project name/id + URL; for reads it returns the requested result. The abstract consumer is the calling agent (or a sub-agent) that needs the web-hosting operation performed; secrets never enter that output.
Related
references/credentials.md— the credential contract (account_idfrom context + token-by-name →CLOUDFLARE_API_TOKEN, the ordered load rule, no-login).references/wrangler.md— the Wrangler Pages surface + what it doesn't cover.references/rest-api.md— the REST long tail, the envelope, the deploy stage machine, limits.- This is the CLI-first sibling of
github-cli-opsand the OpenAPI-grounded sibling ofatlassian-rest-ops/netlify-ops, under the per-provider service-skill pattern.
Progressive disclosure
Heavy content lives in subfolders, loaded only on demand:
references/credentials.md— caller-injectedaccount_id+ token, the ordered load rule, the Bearer bridge, no-login. Load in Step 1.references/wrangler.md— the Wrangler commands + the Direct-Upload-vs-Git rule + CLI gotchas. Load in Steps 2–3.references/rest-api.md— the REST pattern, the priority-op table, the envelope, the deploy stage machine, limits. Load for the REST tail.references/sources.md— provenance (the bundled Pages slice + official docs) + the "confirm live" items.assets/cli-index.md— the Wrangler quick command list (Step 2).assets/endpoint-index.md— one line per REST operation (Step 2).assets/cloudflare-pages-openapi.json— the bundled Pages OpenAPI slice (queried via the resolver, never loaded wholesale).scripts/endpoint.py+.validation.md— thepython3$ref-resolver (Step 2).scripts/{create-project,deploy,add-custom-domain,deployment-status}.sh+.validation.md— four validated priority-op scripts.
Standalone usage (optional, not required)
This is a convenience for a human running the skill by hand outside an agent-flow — it is not a dependency of the skill. The skill's normative contract is caller-injection (Step 1); this appendix is only the manual-operator bridge.
To run by hand, create a scoped token (My Profile → API Tokens → Create Token, Cloudflare Pages: Edit), store its value in a gitignored .env, install Wrangler (npm i -g wrangler), then populate the vars yourself:
set -a; source .env; set +a # loads $CLOUDFLARE_PAGES_TOKEN, never prints it
export CLOUDFLARE_ACCOUNT_ID="<account-id>"
export CLOUDFLARE_API_TOKEN="$CLOUDFLARE_PAGES_TOKEN"
bash scripts/create-project.sh my-site main
bash scripts/deploy.sh my-site ./dist
The token value is referenced by name only, never printed. Prefer a revocable, short-lived scoped token.
Body budget
description≤ 1,024 chars (agentskills.io cap).- Body ≤ ~500 lines / 5,000 tokens — kept in context every turn; Wrangler/REST detail lives in
references/. assets/cloudflare-pages-openapi.jsonis the Pages slice (queried on disk, never loaded into context).