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 Api Laravel Package

graham-campbell/cloudflare-api

A Laravel/PHP client for the Cloudflare API, providing a simple, expressive wrapper for managing zones, DNS records, caching, and other Cloudflare resources through a clean, framework-friendly interface.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before line one: this is a retired package living under a GrahamDeprecated namespace, last shipped as 0.7.0-alpha whose headline change was "upgraded to Laravel 5.0." Day-to-day you will not be leveraging it so much as operating it inside an existing Laravel 5.0–5.4 app. Everything below assumes that constraint.

1. Pin, don't range. There are no patch releases coming, so a floating constraint is a liability:

// composer.json
"minimum-stability": "alpha",
"prefer-stable": true,
"require": {
    "graham-campbell/cloudflare-api": "0.7.0-alpha"
}

If swallowing minimum-stability: alpha for the whole graph is unacceptable, use a path or VCS repository pointing at a frozen commit instead.

2. Register manually. Laravel 5.0–5.4 has no package auto-discovery:

// config/app.php
'providers' => [
    GrahamCampbell\CloudFlareApi\CloudFlareApiServiceProvider::class,
],
'aliases' => [
    'CloudFlareApi' => GrahamCampbell\CloudFlareApi\Facades\CloudFlareApi::class,
],
php artisan vendor:publish --provider="GrahamCampbell\CloudFlareApi\CloudFlareApiServiceProvider"

3. Fill in the connection config (published as config/cloudflare-api.php): a default connection name plus a connections map of email + key. Multi-connection is the whole point of the underlying graham-campbell/manager — set up staging and production entries from day one rather than retrofitting later.

4. Look at three files first, in this order:

  • The facade — it exposes connection($name = null), which is the only surface you should touch.
  • The manager (CloudFlareApiManager) — resolves a name to a configured Cloudflare client. This is the seam.
  • The published config — tells you which auth scheme the SDK will actually send.

5. First real use case: a credential smoke test, not a DNS write. Run this in tinker against a scratch zone before wiring anything:

$manager = CloudFlareApi::connection();
$zones = new \Cloudflare\API\Endpoints\Zones($manager->getClient());

foreach ($zones->listZones()->result as $zone) {
    dump($zone->id, $zone->name);
}

If that returns zones, auth and connection resolution work. If it throws, you know it's credentials or connection name — and you haven't mutated anything. Turn this into an artisan cloudflare:ping command so it's repeatable in every environment.

Implementation Patterns

Never call the facade from a controller or a model. One adapter class per concern, and the SDK endpoint injected — this is the single seam you'll swap when you migrate to cloudflare/sdk:

class CloudflareDnsService
{
    public function __construct(private \Cloudflare\API\Endpoints\DNS $dns) {}

    public function upsert(string $zoneId, string $type, string $name, string $content, bool $proxied = true): void
    {
        $existing = collect($this->dns->listRecords($zoneId, $name, null, $type)->result);

        $payload = ['type' => $type, 'name' => $name, 'content' => $content, 'ttl' => 1, 'proxied' => $proxied];

        $existing->isNotEmpty()
            ? $this->dns->updateRecord($zoneId, $existing->first()->id, $payload)
            : $this->dns->addRecord($zoneId, $type, $name, $content, 1, $proxied);
    }
}

Bind the endpoint classes in your own service provider so nothing downstream knows the manager exists:

$this->app->bind(\Cloudflare\API\Endpoints\DNS::class, fn ($app) =>
    new \Cloudflare\API\Endpoints\DNS($app['cloudflare-api']->connection()->getClient())
);

Cache the zone IDs. Zone lookups are a network call you don't need on every request. Resolve domain → zone ID once, store it (config, DB column, or cache key), and refresh only on a miss. This removes most of your API traffic.

Queue cache purges. The bread-and-butter daily flow — save a page, purge its zone — must never be inline:

class PurgeCloudflareCache implements ShouldQueue
{
    public function __construct(private string $zoneId) {}

    public function handle(\Cloudflare\API\Endpoints\Zones $zones): void
    {
        $response = $zones->cachePurgeEverything($this->zoneId);

        if (! $response->success) {
            logger()->error('CF purge failed', ['errors' => $response->errors]);
            $this->release(60);
        }
    }
}

Fire it from an Eloquent observer (saved on your page/post model), not from the controller.

Custom-domain onboarding as an explicit state machine. This is the highest-value flow the package nominally serves, and also where its lack of maintenance bites hardest. Model it as states — pending → zone_created → nameservers_set → active → record_created → ssl_set — storing the current state on the tenant record. Each transition is a queued job that is safe to retry. Do not write it as a linear method; a partial failure at step 3 must be resumable, not restartable.

Multi-tenant connection switching. If each customer has their own Cloudflare account, resolve the connection name per tenant at runtime and pass it explicitly:

$client = CloudFlareApi::connection($tenant->cloudflare_connection)->getClient();

Keep a tenant→connection mapping table; don't rely on the config default.

Testing. Mock the manager and the endpoint bindings in the container and assert your adapter's payloads — never hit Cloudflare in the suite. Keep exactly one contract test behind @group cloudflare-live and an env flag, pointed at a scratch zone. That test is your only early warning that the abandoned wrapper has drifted from Cloudflare's response shapes.

Ops command. Add cloudflare:records {zone} and cloudflare:purge {zone} artisan commands early. When someone reports a stale page at 6pm, you want a one-liner, not a tinker session.

Gotchas and Tips

  • success: false with HTTP 200. Cloudflare wraps errors in the body. ->success, ->errors, ->messages must be checked on every call — the SDK will happily hand you a transport-level success that means "record rejected." Silent DNS failures during onboarding are the classic symptom.
  • Pagination is not automatic. listZones() / listRecords() return a page, not everything. Your upsert will create duplicates if the existing record was on page 2. Loop on result_info->page / total_pages, or pass an explicit per_page, before concluding a record is absent.
  • Zone activation is asynchronous. After creating a zone, status is pending until nameservers propagate — minutes to hours. Poll with backoff; the "create zone then immediately add a proxied A record and set SSL Full" sequence fails on brand-new zones and produces confusing errors.
  • Config caching eats env changes. In production, a rotated CLOUDFLARE_KEY won't take effect until php artisan config:cache runs again. Put it in the deploy script, or key rotation silently becomes auth failures at 2am.
  • The -alpha version string fails stable constraints. composer update in any sibling package can refuse the graph. Pin exactly; don't try to widen it.
  • Provider → repository rename is a breaking change with no shim. If you inherit a 0.6.x install, grep for Provider class references across app, config, and any published config before touching composer. Assume a full namespace rewrite and config republish.
  • Auth is legacy X-Auth-Email / X-Auth-Key. There is no evidence of scoped API Token (Authorization: Bearer) support. If your security posture requires scoped tokens, this is where you stop rather than where you patch — don't build a shim around abandoned auth plumbing.
  • Guzzle 7 conflicts are expected. The dependency chain targets the PHP 7 / Guzzle 6 era. If anything else in the graph pulls Guzzle 7, budget a day for an alias or a fork rebase, not an hour.
  • Debugging trick — log the actual auth header once. Dump a redacted $client->getConfig('headers') during staging setup. It instantly separates "wrong credentials" from "wrong connection resolved," which is otherwise ten minutes of staring at a 403.
  • ttl => 1 means "automatic," and proxied => true is what actually turns the orange cloud on. Both are easy to leave out of a payload and both are the difference between "we set up DNS" and "the site loads."
  • Rate limits are real. Cloudflare's global limit is ~1200 requests per five minutes per user. A foreach over 500 DNS records in an unthrottled job will start returning 429s that look like random failures. Batch, sleep, and respect the limit.
  • Extension point is the manager, not the client. Add coverage by binding more \Cloudflare\API\Endpoints\* classes. Do not subclass CloudFlareApiManager — nothing is merging upstream and you'll own the divergence forever.
  • Log redaction. The config holds email + global key. Ensure neither ends up in Laravel's exception context or your queue payloads.
  • Record an owner and a removal date. This package has no CVE process. The risk isn't the code quality, it's that the dependency silently stays in the request path for years. Put it in your dependency register with a review date on it.
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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment