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

jamesryanbell/cloudflare

A PHP wrapper for the Cloudflare API that lets you manage zones, DNS records, caching, and other Cloudflare resources from Laravel or plain PHP applications using simple, expressive methods.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire up

composer require jamesryanbell/cloudflare

On Laravel 5.5+ auto-discovery registers the service provider and the Cloudflare facade for you. On older versions add the provider to config/app.php and alias the facade manually (namespace is JamesRyanBell\Cloudflare\...).

Publish the config and fill in credentials:

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

config/cloudflare.php should hold your account email and API key (see Gotchas — this package predates scoped API tokens).

Where to look first

  1. config/cloudflare.php after publishing — confirm the key names match what the provider actually reads (email / api_key); if it doesn't work, the package's source config is the source of truth.
  2. The package's src/ directory — the resource classes (Zone, DNS, User, Api) are small; read the method signatures before guessing them. Because the package is archived, the code is the documentation.
  3. php artisan tinker — fastest way to see the actual response shape:
>>> Cloudflare::zones()->all()   // or ->list(); check the class

First real use case

Pull a zone, then read and create a DNS record:

use Cloudflare;

$zones = Cloudflare::zones()->all();
$zoneId = $zones[0]->id;              // confirm key name in tinker first

Cloudflare::dns()->all($zoneId);
Cloudflare::dns()->create($zoneId, 'A', 'app.example.com', '203.0.113.10', 1, false);

Get that loop working in tinker before you build a service class around it — response shapes and argument order vary between endpoint wrappers.

Implementation Patterns

Wrap the facade in your own service, not the other way around

Don't sprinkle Cloudflare:: calls through controllers. Create a thin CloudflareDnsService that takes the resource objects (or the facade) and exposes domain-meaningful methods:

class DnsService {
    public function pointSubdomainTo(string $zoneId, string $host, string $ip): void { ... }
    public function removeRecord(string $zoneId, string $recordId): void { ... }
}

This gives you one place to add logging, retries, and — critically for an archived package — one place to swap the implementation out later.

Store zone and record IDs

Cloudflare's API is ID-driven; every DNS operation needs the record id. Don't re-list on every request. Persist zone_id on your Site/Tenant model and cloudflare_record_id on whatever owns the record. A zones() lookup on every page render is both slow and rate-limit-hostile.

Provisioning pattern (multi-tenant SaaS)

Classic use case: new tenant signs up → create tenant.example.com:

DB::transaction(function () use ($tenant) {
    $record = $this->dns->create($zoneId, 'A', $tenant->slug . '.example.com', $ip);
    $tenant->update(['cf_record_id' => $record->id]);
});

Do the API call before committing, or record the ID and clean up on failure. Never leave an orphan record because a DB write threw afterward.

Dynamic DNS / scheduled updates

A scheduled command that checks the server's public IP and edits a single A record is the lowest-friction day-to-day use. Use edit with the stored record ID — not create — otherwise you accumulate duplicate records on every run.

Bulk operations belong in a queue

Creating 200 subdomains in a request will hit Cloudflare's rate limits (~1,200 requests / 5 minutes per user, and lower per-zone limits) or your PHP max_execution_time. Fan out to a job per record, add ->onQueue('dns')->delay(...) spacing, and make the job idempotent (look up by name before creating).

Testing

The facade is mockable, which is the main reason to keep using it rather than raw Guzzle:

Cloudflare::shouldReceive('dns->create')
    ->once()
    ->andReturn((object) ['id' => 'rec_123']);

Better: bind a DnsService interface in the container and swap in a fake for tests, so you never assert against this package's method names in your test suite.

Error handling

Responses are PSR-friendly, so catch transport exceptions at the service boundary and translate them:

try {
    $result = Cloudflare::dns()->create(...);
} catch (\Exception $e) {
    Log::error('Cloudflare DNS create failed', ['host' => $host, 'e' => $e->getMessage()]);
    throw new DnsProvisioningException($host, previous: $e);
}

Also inspect the body — Cloudflare returns HTTP 200 with success: false and an errors array for many validation failures. A no-throw does not mean success.

Gotchas and Tips

It is archived (last release 2017). Treat it as frozen risk.

Cloudflare's v4 API has grown considerably since 2017 (API tokens, newer record types, additional zone settings). The wrapper still speaks the old surface. Budget for a migration to the official cloudflare/sdk or a hand-rolled Guzzle client if you depend on anything beyond basic zone + DNS CRUD.

API token auth is almost certainly not supported.

The description mentions "API token / email-and-key authentication," but this package predates Cloudflare API tokens (2019). Expect it to require the global API key + account email, which is far more dangerous — it grants full account access. If your security posture forbids global keys, this package is not viable without a fork.

Config caching bites silently.

php artisan config:clear   # after editing .env
php artisan config:cache   # re-cache for prod

A stale cached config is the #1 cause of "it works in tinker but not in the app / queue worker."

Create is not idempotent.

Calling create twice makes two identical records. Always all($zoneId) and filter by name/type first, or store the record ID and use edit. Idempotency is your responsibility, not the wrapper's.

Pagination is manual.

Listing large zones returns a page at a time. Pass page/per-page parameters explicitly and loop until you've seen everything — don't assume all() returns the full zone.

Record field quirks.

  • TTL minimum is 120s; 1 means "automatic." Don't send 0.
  • proxied (the orange cloud) is a boolean per record — getting its position in the create() argument list wrong will silently produce an unproxied record. Verify in the dashboard the first time.
  • CNAME at the apex is not allowed; Cloudflare's flattening handles it, but the API will reject the raw attempt.
  • Deleting a zone deletes all its records — there is no undo. Guard zone-delete calls behind a confirmation or an admin-only command.

Debugging a failed call.

Log the response body (success, errors[].message) — Cloudflare's messages are usually precise ("record already exists", "invalid zone identifier"). Also capture the cf-ray header from the response; Cloudflare support and the dashboard's audit log key off it.

Guzzle version conflicts.

A 2017 package pins an old Guzzle. On Laravel 8+/PHP 8 you may hit dependency resolution or runtime incompatibilities. Composer --ignore-platform-reqs is not a fix; check composer why guzzlehttp/guzzle before blaming your code.

Extension points if you must keep it.

  • Swap the container binding: $this->app->bind('cloudflare', fn () => new YourAdapter()) — lets you keep the facade calls but back them with a modern client.
  • Fork and patch the Api class to accept a bearer token instead of email+key; the HTTP layer is thin enough that this is a small, contained change.
  • Keep all package contact behind one service class from day one. When you outgrow the package (you will), the migration is a single-file rewrite rather than a codebase-wide search-and-replace.
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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource