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

Cloudflare Laravel Package

graham-campbell/cloudflare

A Laravel/PHP integration for the Cloudflare API that provides a service provider and facade for managing zones, DNS records, caching, and other Cloudflare resources from your application through a clean, expressive interface.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and wire up.

    composer require graham-campbell/cloudflare
    

    On Laravel 5.5+ the CloudflareServiceProvider and Cloudflare facade are auto-discovered. On older apps add them manually in config/app.php:

    GrahamCampbell\Cloudflare\CloudflareServiceProvider::class,
    // aliases
    'Cloudflare' => GrahamCampbell\Cloudflare\Facades\Cloudflare::class,
    
  2. Publish the config and add credentials.

    php artisan vendor:publish --provider="GrahamCampbell\Cloudflare\CloudflareServiceProvider"
    

    In .env, use either the legacy global key + email pair or a scoped API token:

    [email protected]
    CLOUDFLARE_KEY=your-global-api-key
    # OR, preferred:
    CLOUDFLARE_TOKEN=your-scoped-api-token
    
  3. Look here first. Open config/cloudflare.php — that's the whole public surface of the package. The facade resolves to the underlying cloudflare/sdk adapter, so all real documentation lives in the cloudflare/sdk README and the Cloudflare\API\Endpoints\* classes, not in this wrapper. When in doubt, read the endpoint class you're calling.

  4. First useful call (tinker). List zones, resolve one by name, then read its DNS records:

    use GrahamCampbell\Cloudflare\Facades\Cloudflare;
    
    Cloudflare::zones()->listZones('example.com');
    $zoneId = Cloudflare::zones()->getZoneID('example.com');
    Cloudflare::dns()->listRecords($zoneId);
    

That listZones → getZoneID → dns() sequence is the backbone of almost every real workflow.

Implementation Patterns

  • Prefer constructor injection over the facade in services. The container binds the SDK adapter as a singleton, so this is clean and mockable:

    use Cloudflare\API\Adapter\Adapter;
    
    class CloudflareDns
    {
        public function __construct(private Adapter $cloudflare) {}
    
        public function records(string $zone): array
        {
            $zoneId = $this->cloudflare->zones()->getZoneID($zone);
            return $this->cloudflare->dns()->listRecords($zoneId);
        }
    }
    

    Use the Cloudflare facade only in Artisan commands, tinker, and controllers where injection adds noise.

  • Cache zone IDs. getZoneID($name) is an extra API round-trip on every call. Resolve once, cache per zone name (config or cache store), and invalidate on zone changes. This alone removes a large chunk of your rate-limit pressure.

  • Wrap the SDK in your own thin domain service. The SDK methods are chatty — addRecord($zoneId, $type, $name, $content, $ttl, $proxied). A DnsRecord value object plus upsertRecord() / ensureRecord() helpers keeps call sites readable and gives you a single place to add logging and retries.

  • Reconcile, don't blind-write. For sync-style workflows (deploy pipelines, dynamic DNS, multi-tenant provisioning), list existing records, diff against the desired set, then addRecord / updateRecord / deleteRecord only what changed. Cloudflare is happy to accept duplicates — your code has to prevent them.

  • Deploy-time and scheduled use cases.

    • Artisan command (php artisan cloudflare:sync-dns) invoked in CI after a deploy.
    • Scheduled job for dynamic DNS ($schedule->command('cloudflare:ddns')->everyFiveMinutes()).
    • A queued job for bulk record changes so API latency doesn't block a request. Note the job must re-resolve the adapter or inject it — don't serialize the client.
  • Multi-account setups. The package wires one adapter from config. For a second account, construct a GuzzleAdapter yourself and bind it under a named key; don't fight the singleton.

  • Testing. Swap the binding:

    $this->mock(Adapter::class, function ($mock) {
        $mock->shouldReceive('zones->getZoneID')->andReturn('abc123');
    });
    

    Facade-level testing (Cloudflare::shouldReceive(...)) works too. If you want HTTP-level fakes, pass a Guzzle MockHandler through the options key in config/cloudflare.php — Http::fake() does not intercept this client, since the SDK owns its own Guzzle instance.

Gotchas and Tips

  • The facade is the adapter, not a manager. Cloudflare::dns(), Cloudflare::zones(), Cloudflare::user(), Cloudflare::zoneSettings() are endpoint factories on the SDK adapter. IDE autocompletion is only as good as the cloudflare/sdk version you installed; keep it in require-dev for stubs if needed.

  • Two auth modes, pick one. Global key + email and scoped API token behave differently (token permissions are per-resource). If a call returns 403 while others work, it's almost always a token-scope issue, not a package bug. Leaving both configured is a good way to get confusing, intermittent auth behavior — set only the pair you intend.

  • getZoneID() returns false/null on miss. Every downstream call will then fail with an opaque API error. Always guard:

    $zoneId = Cloudflare::zones()->getZoneID('example.com');
    throw_unless($zoneId, new \RuntimeException('Zone not found'));
    
  • Pagination is manual. listZones() and listRecords() cap results per page. If you're auditing a large zone, loop the page parameter (or pass a large per_page) and check the result_info returned. Silent truncation is the most common "why didn't my record get updated" cause.

  • TTL 1 means "automatic", not one second. And proxied: true only makes sense on A, AAAA, and CNAME records — the API rejects it elsewhere.

  • Rate limits are real. Cloudflare's v4 API is roughly 1,200 requests / 5 minutes per user. A naive reconcile loop across many zones will hit it. Batch, cache zone IDs, and back off on 429s (Cloudflare\API\Exceptions\APIException carries the response).

  • Config caching footgun. After changing .env, run php artisan config:cache (or config:clear) — the adapter singleton reads config once at boot.

  • Error handling. SDK exceptions (CloudflareException and subclasses) bubble straight through the package. Catch them at your service boundary, log the Cloudflare error code, and don't let a DNS sync failure crash a deploy without a clear message.

  • Extension points. To intercept requests, custom-timeout, or log, either add Guzzle options via the config options array or bind your own Adapter implementation in a service provider ahead of the package's binding. Subclassing GuzzleAdapter is the lowest-friction option.

  • Project health caveat. This wrapper is thin and low-activity. Pin the version, and if it lags behind Laravel releases, the escape hatch is easy: it's ~50 lines of service provider. You can drop it and instantiate GuzzleAdapter directly in your own provider with no changes to calling code that already injects Cloudflare\API\Adapter\Adapter.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource