phattarachai / laravel-cloudflare
Purge the Cloudflare edge cache, toggle development mode, register tunnel DNS records, and sync WAF custom rules from artisan — built for apps behind a shared Cloudflare zone.
Package info
github.com/phattarachai/laravel-cloudflare
pkg:composer/phattarachai/laravel-cloudflare
Requires
- php: ^8.3
- illuminate/console: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.10
- laravel/pint: ^1.24
- orchestra/testbench: ^10.8|^11.0
- pestphp/pest: ^3.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-11 06:02:45 UTC
README
Purge the Cloudflare edge cache, toggle Development Mode, register tunnel DNS records, and sync WAF
custom rules from artisan. Built for a fleet of apps sharing one Cloudflare zone behind a
cloudflared tunnel, where purge_everything would evict every other app and a WAF or DNS sync must
never touch what another app declared.
Requirements
- PHP 8.3+
- Laravel 12 or 13
- A Cloudflare API token and zone id (any plan, Free included)
Install
composer require phattarachai/laravel-cloudflare php artisan vendor:publish --tag=cloudflare-config
Set the zone id (a public identifier, safe to commit) and, if you register DNS, the tunnel id:
CLOUDFLARE_ZONE_ID=... CLOUDFLARE_TUNNEL_ID=...
The API token is resolved at runtime and falls back to the process env, so on a self-hosted runner
you inject CLOUDFLARE_API_TOKEN once (in the runner's .env) and no repo needs a secret. A
Zone → Cache Purge scope covers cloudflare:purge; add Zone → Zone Settings Edit to use
cloudflare:dev-mode, Zone → DNS Edit to use cloudflare:dns, and Zone → Zone WAF Edit to use
cloudflare:waf.
Purge
php artisan cloudflare:purge # document root + every built asset from the Vite manifest php artisan cloudflare:purge --url=https://…/sitemap.xml # plus explicit URLs (repeatable) php artisan cloudflare:purge --host # every cached URL on this app's host php artisan cloudflare:purge --everything --force # whole zone — nukes every app on a shared zone
By default it purges by exact files: https://<host>/ plus every built file (and its CSS) from
public/build/manifest.json. That covers only / and the Vite assets — any other cached HTML
page (/blog/…, /about) stays at the edge until it expires, unless you add it with --url or
use the host mode. Batches go out at 100 URLs per request, Cloudflare's cap on Free, Pro and
Business.
--host purges {"hosts": ["<host>"]} instead: every cached URL on this app's host and nothing
else on the shared zone. Cloudflare has offered purge by hostname, tag and prefix on every plan
since April 2025, but on Free it is limited to 5 requests a minute per account, shared by every app
on that account. To make it the default for an app, set CLOUDFLARE_PURGE_MODE=host (or
purge.mode in the config). The host comes from CLOUDFLARE_HOST, else the APP_URL host.
Make it the last deploy step:
- run: php artisan cloudflare:purge || true
With no token or zone id it exits 0 without calling the API, so dev, tests, and a not-yet-configured repo keep CI green. With no manifest it purges only the document root.
Development Mode
php artisan cloudflare:dev-mode # show current state php artisan cloudflare:dev-mode on # bypass the edge cache for ~3 hours php artisan cloudflare:dev-mode off
DNS
Upsert-only — it never deletes a record it did not declare, so it is safe against the shared zone.
A tunnel record (--tunnel, or 'tunnel' => true in config) needs CLOUDFLARE_TUNNEL_ID; without
it the command fails before writing anything.
php artisan cloudflare:dns --name=new-app.phattarachai.app --tunnel # proxied CNAME → <tunnel>.cfargotunnel.com php artisan cloudflare:dns --name=txt.example.com --type=TXT --content='v=spf1 -all' --dns-only php artisan cloudflare:dns --list php artisan cloudflare:dns --name=old.example.com --delete --force
Declare an app's own records in config/cloudflare.php and a bare php artisan cloudflare:dns
upserts them — handy as a one-off when spinning up a new subdomain:
'dns' => [ 'records' => [ ['name' => 'new-app.phattarachai.app', 'tunnel' => true], ], ],
WAF custom rules
Upsert-only, keyed by a tag embedded in each rule's description — it never touches a rule it did
not declare, so it is safe against the shared zone. It creates the zone's custom-rules entrypoint
only when Cloudflare answers 404 (the zone has none yet); if reading the rules fails for any other
reason — a 5xx, a timeout, a token without read access — it exits non-zero and writes nothing. The canonical use is a Managed Challenge on
/admin: bots and brute-force hit the edge challenge, real browsers pass near-invisibly, and the
API is left alone (a challenge needs a browser + cf_clearance cookie, so it would break XHR,
mobile, and webhooks). Needs a Zone → Zone WAF Edit token.
php artisan cloudflare:waf # sync every rule declared in config php artisan cloudflare:waf --list # show the zone's WAF custom rules php artisan cloudflare:waf --remove=admin-challenge --force
Declare the rules in config/cloudflare.php. Each carries a unique tag, an action (default
managed_challenge), and EITHER declarative paths (the command builds the expression and, absent
hosts, scopes it to this app's own host) OR a raw expression for full control:
'waf' => [ 'rules' => [ // declarative — scoped to this app's host automatically ['tag' => 'admin-challenge', 'paths' => ['/admin']], // raw — pin every env host so all deploys upsert the SAME rule and converge // ['tag' => 'admin-challenge', 'expression' => '(starts_with(http.request.uri.path, "/admin")) and (http.host in {"backend-prod.example.com" "backend-qas.example.com"})'], ], ],
Verify: a challenged request returns 403 with a cf-mitigated: challenge header (curl always lands
here — it can't solve the JS challenge); a passed request has no cf-mitigated header and holds a
cf_clearance cookie (HttpOnly — visible in DevTools → Application → Cookies).
Testing
composer test
ผู้พัฒนา
พัฒนาและดูแลโดย บริษัท ภัทรชัย อาร์ทิซาน จำกัด (Phattarachai Artisan) บริษัทที่ปรึกษาและพัฒนาเว็บ ที่เรียนรู้และแบ่งปันกับชุมชน Laravel แพ็กเกจนี้แบ่งปันให้ชุมชนนำไปใช้และต่อยอดได้อย่างอิสระ
ดูแพ็กเกจอื่นของเราได้ที่ phattarachai.dev/open-source และติดต่อเราได้ที่ phattarachai.dev