Skip to main content

Deploy a local folder to Cloudflare Pages via the Direct Upload API — no wrangler, no npm.

Project description

cf-publish

Deploy a local folder to Cloudflare Pages from Python — no wrangler, no npm, no Node.js. One pip install, one command.

pip install cf-publish
export CLOUDFLARE_API_TOKEN=...    # token with "Cloudflare Pages: Edit"
export CLOUDFLARE_ACCOUNT_ID=...   # shown on the dashboard overview page
cf-publish ./public --project my-site

That's it. The contents of ./public become the site. The project is created on first deploy if it doesn't exist.

日本語の説明は README.ja.md にあります。

Why

The only official way to do a Direct Upload deploy is wrangler, which drags in the whole Node.js toolchain. If your build pipeline is Python (or just a folder of files), that's a lot of machinery for one HTTP conversation. cf-publish implements the same upload protocol in ~300 lines of Python with two dependencies (httpx, blake3).

  • Content-addressed uploads — files are hashed the same way wrangler hashes them, so unchanged files are never re-uploaded (fast repeat deploys, and the cache is shared with wrangler).
  • Concurrent uploads with retry and exponential backoff on 429/5xx.
  • Pre-flight validation of the Pages limits (25 MiB/file, 20,000 files/deployment) before anything is sent.
  • Root-level _headers / _redirects are attached to the deployment the way wrangler does it, so Pages actually parses and applies the rules (uploading them as plain assets would serve them as static files instead).

Usage

cf-publish DIRECTORY --project NAME [options]

--branch BRANCH     'main' deploys to production, anything else gets a
                    preview URL (default: main)
--no-create         fail if the project doesn't exist instead of creating it
--exclude PATTERN   fnmatch pattern to skip, matched against the relative
                    path and the filename; repeatable (e.g. --exclude '*.map')
--dry-run           show what would be uploaded, deploy nothing
--quiet             print only the deployment URL
--json              print a JSON result (url, files, unique, uploaded,
                    duration, dry_run)

Progress goes to stderr, results to stdout, so both --quiet and --json compose cleanly with shell pipelines and CI.

Credentials

Environment variables win; otherwise ~/.config/cloudflare/pages.env is read (plain KEY=VALUE lines):

CLOUDFLARE_API_TOKEN=...
CLOUDFLARE_ACCOUNT_ID=...

Create the token at dash.cloudflare.com → My Profile → API Tokens with the Cloudflare Pages: Edit permission. Nothing else is needed.

As a library

from cf_publish import deploy, PagesError

result = deploy("./public", "my-site", on_progress=print)
print(result.url, result.uploaded, result.duration)

The core raises PagesError on expected failures and never calls sys.exit() or prints, so it embeds cleanly in build scripts and GUIs.

Notes and caveats

  • Unofficial. This project is not affiliated with Cloudflare. It speaks the same semi-official Direct Upload endpoints wrangler uses internally (upload-token / check-missing / upload / upsert-hashes). If Cloudflare changes them, fall back to wrangler or the Git integration — the hash algorithm is pinned by a fixed-value test so a breakage is caught loudly, not silently.
  • Hidden files and directories (names starting with .) are never uploaded.
  • Symlinks are followed and served as copies (Pages has no symlinks); cycles are detected and broken.

R2 sync

export R2_ACCESS_KEY_ID=...        # R2 S3-API token (dashboard -> R2 -> Manage API Tokens)
export R2_SECRET_ACCESS_KEY=...    # NOT the Pages token
export CLOUDFLARE_ACCOUNT_ID=...
cf-publish r2 sync ./data my-bucket/some/prefix [--delete] [--dry-run]

Diff-syncs a folder to an R2 bucket over the S3-compatible API — SigV4 is implemented with the standard library, so still just two dependencies. Unchanged files (remote ETag == local MD5) are skipped; --delete removes remote objects that no longer exist locally. Single-PUT only, so objects are capped at ~5 GB (no multipart yet). Pairs with the Pages command: site on Pages, data on R2 (free egress), one CLI.

Roadmap

  • Deployment list / rollback.
  • R2 multipart uploads (>5 GB objects).

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cf_publish-0.2.0.tar.gz (23.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cf_publish-0.2.0-py3-none-any.whl (15.5 kB view details)

Uploaded Python 3

File details

Details for the file cf_publish-0.2.0.tar.gz.

File metadata

  • Download URL: cf_publish-0.2.0.tar.gz
  • Upload date:
  • Size: 23.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for cf_publish-0.2.0.tar.gz
Algorithm Hash digest
SHA256 a9dc4efa2f2f8b89e25e373b9e30b23f77140a6bf665fccecbe3f6280b53c43a
MD5 de7a9b11840d42d43fe2fb05163c30a7
BLAKE2b-256 823030567b637cbc3899edbae58628c1aa13f4197656eecce451912deab89ee1

See more details on using hashes here.

Provenance

The following attestation bundles were made for cf_publish-0.2.0.tar.gz:

Publisher: publish.yml on aiseed-dev/cf-publish

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cf_publish-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: cf_publish-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 15.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for cf_publish-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b6fd8967b2f60a8f0cbc62dbe40b89fbb7290e03543ceb5cdced8c2a1f1a4265
MD5 3f9e484f7d4d643954ce184a2a8b7159
BLAKE2b-256 c7e0f09281abaf420291601669cf579da1b923b4f181a7e4b4653c0aa5fab7eb

See more details on using hashes here.

Provenance

The following attestation bundles were made for cf_publish-0.2.0-py3-none-any.whl:

Publisher: publish.yml on aiseed-dev/cf-publish

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page