sebdesign/artisan-cloudflare
A Laravel package that adds Artisan commands for managing Cloudflare, so you can purge cache, manage DNS records, and control zones from the CLI or scheduled tasks. It wraps the Cloudflare API in a simple, configurable integration for your app.
sebdesign/artisan-cloudflare is a thin Laravel wrapper around the Cloudflare API whose main job is to let you purge the Cloudflare edge cache from the CLI — so a deploy, a content publish, or a scheduled job can invalidate the CDN without anyone logging into the dashboard.
composer require sebdesign/artisan-cloudflare
php artisan vendor:publish --tag=cloudflare # publishes config/cloudflare.php
Add credentials to .env (never to the config file):
[email protected]
CLOUDFLARE_KEY=your-global-api-key # or CLOUDFLARE_API_TOKEN for scoped tokens
CLOUDFLARE_ZONE=your-zone-id
The first use case you should wire up is the one you'll use forever: purge everything at the end of a deploy.
php artisan cloudflare:purge
Verify it worked before building anything on top of it — run with -v to see the API response, then hard-refresh a static asset and confirm the cf-cache-status header flips from HIT to MISS. Run php artisan list cloudflare to see the exact command signatures and options for the installed version rather than trusting a blog post.
Version note (v2.9.0): this release adds Laravel 13.x support and drops PHP 7.x and Laravel 5.x. If any app is still on PHP 7.4 or Laravel 5.x, pin
"sebdesign/artisan-cloudflare": "^2.8"incomposer.jsonbefore runningcomposer update, otherwise the solver will fail or, worse, you'll only discover it during a deploy.
Deploy hook (the 90% case). Put the purge at the very end of your deploy script, after php artisan migrate and after caches are warmed, so you don't purge and then immediately re-warm stale data:
php artisan down --render="errors::503" || true
git pull && composer install --no-dev -o && php artisan migrate --force
php artisan config:cache && php artisan route:cache && php artisan view:cache
php artisan up
php artisan cloudflare:purge --quiet
Because purge-all is cheap to call but expensive for origin traffic, only run it on deploys that actually change publicly cacheable output — gate it on whether public/ or resources/ changed.
Selective URL purge. For content-driven sites, purging everything on every article edit is overkill. Prefer targeted purges driven from model events:
// app/Observers/ArticleObserver.php
public function saved(Article $article): void
{
PurgeArticleCache::dispatch($article->url(), "/blog/{$article->slug}");
}
// app/Jobs/PurgeArticleCache.php
public function handle(): void
{
Artisan::call('cloudflare:purge', [
'urls' => $this->urls, // confirm the argument name with `php artisan help cloudflare:purge`
]);
}
Always dispatch this on a queue — the Cloudflare call is an external HTTP round-trip and has no business blocking a save request. Use dispatchAfterResponse() for low-traffic sites where a queue worker is overkill.
Sitemap-driven sweeps. For a nightly "make sure the cache matches reality" job, iterate your sitemap and purge in batches. Cloudflare's single-URL purge endpoint accepts a limited number of files per call (30), so chunk:
collect($urls)->chunk(30)->each(fn ($chunk) => Artisan::call('cloudflare:purge', [
'urls' => $chunk->all(),
]));
Programmatic use. Rather than shelling out, resolve the package's client/service from the container (check config/cloudflare.php and the service provider for the exact binding name in your version) when you need the return value or want to handle errors granularly. Reserve Artisan::call() for scripts and one-off jobs where the command output is the point.
Environment separation. Staging and production should never share a zone. Drive CLOUDFLARE_ZONE from the environment and add a guard in your deploy script so a misconfigured staging box can't nuke production's cache:
[ "$APP_ENV" = "production" ] || { echo "skipping purge"; exit 0; }
example.com in CLOUDFLARE_ZONE. Cloudflare's purge endpoints want the 32-char zone ID from the dashboard overview page. Symptom: a 400/Invalid zone identifier from the API.403 almost always means missing permission, not a wrong key.Cache-Control: max-age=86400, users keep the stale copy. Pair purges with filename hashing (Mix/Vite already do this) or short browser TTLs.Artisan::call() output and -v verbosity can echo request context. Use --quiet in CI and keep secrets in the platform's secret store (Forge/Vapor/GitHub Actions secrets), never in config/cloudflare.php.CLOUDFLARE_*, run php artisan config:clear. A cached config from a previous deploy is the second most common "it says unauthorized but the key is right" cause.Sebdesign\ArtisanCloudflare\CloudflareServiceProvider manually.php artisan config:clear, (2) confirm env values with php artisan tinker --execute="dump(config('cloudflare'))", (3) hit the Cloudflare API directly with curl using the same token to isolate whether it's the credentials or the package, (4) re-run the command with -v.composer.lock in CI: a transitive cloudflare/sdk bump can change exception types your error handling catches. Add a smoke test that runs the purge command against a throwaway zone.How can I help you explore Laravel packages today?