Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Cloudflare Laravel Package

sumanion/laravel-cloudflare

A Laravel integration for the Cloudflare API that lets you manage zones, DNS records, and cache purging directly from your application. Configure credentials once and use a clean, fluent interface to automate common Cloudflare operations.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and configure in four steps.

composer require sumanion/laravel-cloudflare
php artisan vendor:publish --tag=cloudflare-config

Add credentials to .env — the package talks to the Cloudflare API v4, so you need an API token, not the legacy global API key, and you need your zone ID:

CLOUDFLARE_API_TOKEN=your-scoped-token
CLOUDFLARE_ZONE_ID=your-zone-id

Scope the token to only what you'll actually use: Zone.Zone:Read, Zone.DNS:Edit, and Zone.Cache Purge (Enterprise accounts also get Zone.Cache Purge by tag). A token that can purge cache cannot edit DNS if you scope it that way, and vice versa — create separate tokens per concern if you can.

Where to look first. Open config/cloudflare.php after publishing — it will show you the shape the package expects (token, zone, possibly per-resource defaults). Then hit a smoke test in tinker:

Cache::cloudflare()->purgeUrls(['https://example.com/']);
// or, depending on the facade alias:
Cloudflare::purgeUrls(['https://example.com/']);

If that returns without error and your Cloudflare dashboard shows a purge event, you're wired up correctly. If the container can't resolve the facade, check bootstrap/providers.php (Laravel 11+) or config/app.php (Laravel 10-) to confirm Sumanion\Cloudflare\CloudflareServiceProvider was auto-discovered.

First real use case. The highest-value, lowest-risk integration is purging cache on deploy. Wire it into your release script rather than the app runtime — no request path is affected, no user data flows through it, and you get immediate feedback that the integration works:

// In a deploy command or Envoy/Forge hook
Artisan::call('cloudflare:purge');          // if the package ships a command
// or your own:
app(\Sumanion\Cloudflare\Cloudflare::class)->purgeEverything();

Once that's green, add targeted URL purges to model observers (see next section). Do not start with DNS management — DNS mutations are irreversible from the app's perspective and you want the cache path proven first.

⚠️ Read the source before you build on this. The README/description advertises a full Cloudflare API wrapper (zones, DNS, cache). The package's public assessments describe it as an CF-Connecting-IP / CF-IPCountry header helper. Those are two different packages. Verify which one you actually installed by inspecting vendor/sumanion/laravel-cloudflare/src/ — the method names below assume the API-wrapper interpretation.

Implementation Patterns

Cache invalidation via model observers. The single most useful day-to-day pattern: purge the URL(s) that a model's public page maps to, whenever the model changes.

class ArticleObserver
{
    public function saved(Article $article): void
    {
        Cloudflare::purgeUrls([
            route('articles.show', $article),
            route('articles.index'),
            route('home'),
        ]);
    }
}

Register in AppServiceProvider::boot():

Article::observe(ArticleObserver::class);

Wrap the package in your own service. Because the package has zero adoption and a single maintainer, do not call Cloudflare:: directly from controllers or observers. Introduce a thin CachePurger contract in your app and provide a Cloudflare-backed implementation. When (not if) you need to fork, swap implementations, or add a fallback CDN, you change one class, not twenty callsites.

interface CachePurger { public function purgeUrls(array $urls): void; }

final class CloudflarePurger implements CachePurger
{
    public function purgeUrls(array $urls): void
    {
        rescue(fn () => Cloudflare::purgeUrls($urls), report: true);
    }
}

Note the rescue — cache purges are best-effort; a Cloudflare outage should never fail a user-facing save.

Deploy-time purge. Add a purge step to your deploy pipeline for HTML, and let tagged or asset-hashed content expire naturally. For static asset purges, prefer URL purges with specific paths over purgeEverything() — a full purge on a busy site can trigger a thundering-herd of origin requests.

Tag-based purge (Enterprise only). If your plan supports Cache-Tag, set the header on responses and purge by logical tag:

// Response middleware
$response->header('Cache-Tag', "tenant-{$tenant->id},locale-{$locale}");

Then purge on tenant config change with purgeTags(['tenant-42']) — this is dramatically more precise than URL lists and scales with content volume. On non-Enterprise plans the tags are silently ignored, so guard with a feature flag rather than trusting it.

Multi-zone / multi-site. If you operate several Cloudflare zones (e.g. regional marketing sites), don't rely on the single CLOUDFLARE_ZONE_ID in config. Build a small resolver that returns a client per zone, keyed by domain in a database or config map, and cache those clients. The whole package is effectively stateless, so this composes cleanly.

Testing. Never hit the real API in tests. Use Http::fake() around the package (it will be using Laravel's HTTP client under the hood) — this is the reason to prefer a Laravel-native wrapper over raw Guzzle in the first place:

Http::fake(['api.cloudflare.com/*' => Http::response(['success' => true, 'result' => []])]);

$this->post('/articles', [...])->assertOk();

Http::assertSent(fn ($req) => str_contains($req->url(), '/purge_cache'));

The assertSent check verifies your observer actually fires — that's the bug you'll ship otherwise.

DNS provisioning flows. If you do manage DNS, restrict it to create-on-provision for customer subdomains or verification TXT records (e.g. sending-domain setup). Never expose a generic "update DNS" surface to application code paths that a user can trigger — every DNS call is a potential production outage if parameters are wrong.

Gotchas and Tips

Token permission errors surface as opaque 403s. Cloudflare returns Authentication error for both a bad token and an insufficiently scoped token. When a purge fails, the first thing to check is that the token has Cache Purge on that specific zone, not just account-wide. Log the response body ($response->json('errors')) — Cloudflare's error payloads are usually precise about which permission is missing, but the wrapper may swallow them behind a generic exception.

purgeEverything() is a footgun. It's tempting, easy to call from a loop, and it removes every cached object for the zone. On a site with meaningful traffic the cache refill load can spike origin CPU. Reserve it for emergencies (bad deploy, malicious payload on cached pages) and log it loudly when called.

Rate limits. Cloudflare's API allows roughly 1,200 requests per five minutes per token. A model observer that purges on every save of a hot model will burn through that quickly under load. Batch purges: collect URLs in a queue job, deduplicate, and flush every few seconds. Also remember that purge calls are counted per URL group, not per URL, in some API paths — check the docs before assuming 100 URLs = 1 call.

Zone ID is not the account ID. A very common copy-paste error. The zone ID appears on the Overview page of the specific domain; the account ID appears in the URL of the dashboard. Passing the account ID yields Invalid zone identifier (or worse, a silent 400 if the wrapper doesn't throw).

Config caching breaks late-bound config. If you resolve config('cloudflare.zone_id') at boot (e.g. in a singleton constructor), php artisan config:cache will freeze whatever value was present at cache time. Read config lazily inside methods, or better, bind the client with $app->scoped() and resolve config at first use.

No origin verification — this is not an auth layer. If the installed package is in fact the header helper (see the warning above), CF-Connecting-IP and CF-IPCountry are only trustworthy when your origin is unreachable except through Cloudflare (IP allowlist, Authenticated Origin Pulls, or Tunnel). Anyone who can hit the origin directly can forge those headers. Verify with curl -H 'CF-Connecting-IP: 1.2.3.4' https://your-origin-ip/ from outside Cloudflare — if it returns anything other than a connection refused, you're spoofable and both geo logic and rate limiting are compromised.

CF-IPCountry returns XX for unknown. VPNs, some mobile carriers, and Tor produce XX. Any geo-conditional logic — pricing, feature flags, legal access — must have an explicit fallback. "Default to US" is a decision, and it should be written down, not an accidental side effect of ?? 'US'.

Country-varying content needs cache keys. If you personalize by country, you must either set Vary: CF-IPCountry at the edge, use Cloudflare cache rules keyed on the header, or bypass cache on those routes. Otherwise user A in Germany and user B in the US share a cached page. This is a correctness bug that only shows up in production and only after traffic, so test it deliberately with two simulated clients.

Interacts with TrustProxies. If the package rewrites Request::ip(), it may conflict with Laravel's native trusted-proxy handling, producing inconsistent IPs across middlewares depending on ordering. Decide on one mechanism, document it, and add a test asserting $request->ip() returns the expected value through the full middleware stack. Double-application of X-Forwarded-For chains is a classic source of "all users share one IP" bugs that break rate limiting.

Vendoring is the sane escape hatch. With zero stars, zero dependents, and a single maintainer, plan for the package to go stale. The API-wrapper surface you actually use is small (three or four HTTP calls). Keep a README note in your app explaining how to replace Cloudflare:: with a 60-line client wrapping Http::withToken(...)->purge(...) — you'll thank yourself at the next Laravel major upgrade.

Extension points. If the package exposes a configured HTTP client, reuse it (Cloudflare::http() or similar) rather than constructing your own — you inherit retry/timeout settings and get Http::fake() compatibility in tests. If it doesn't expose one, that's a reasonable reason to fork early: the package is glue, and glue that hides its client is harder to test than glue you wrote.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings