A PHP SDK for the Cloudflare API, auto-generated from the official OpenAPI spec. Uses Guzzle for HTTP and Valinor for typed response deserialization.
- PHP 8.1+
- guzzlehttp/guzzle ^7.0
- cuyz/valinor ^2.0
composer require foundry-co/cloudflareThe package includes first-class Laravel support via a service provider and facade that are auto-discovered by Laravel's package auto-discovery.
composer require foundry-co/cloudflareAdd the following to your .env:
CLOUDFLARE_API_TOKEN=your-api-token
CLOUDFLARE_ACCOUNT_ID=your-account-id # optional
CLOUDFLARE_BASE_URL=https://api.cloudflare.com/client/v4 # optionalThat's it — the CloudflareServiceProvider is registered automatically and the Cloudflare facade is available immediately.
php artisan vendor:publish --tag=cloudflare-configThis copies config/cloudflare.php into your application's config directory.
use FoundryCo\Cloudflare\Laravel\Facades\Cloudflare;
// Via facade
$records = Cloudflare::zone('zone-id')->dnsRecords()->list();
// Via dependency injection
use FoundryCo\Cloudflare\CloudflareClient;
class MyController extends Controller
{
public function __construct(private CloudflareClient $cloudflare) {}
public function index()
{
return $this->cloudflare->zone('zone-id')->dnsRecords()->list();
}
}All requests authenticate with a Cloudflare API Token. Create tokens in the Cloudflare dashboard under My Profile > API Tokens.
use FoundryCo\Cloudflare\CloudflareClient;
$cf = new CloudflareClient(apiToken: 'your-api-token');Methods return typed PHP objects deserialized via Valinor. List endpoints that include pagination metadata return a PaginatedResponse:
use FoundryCo\Cloudflare\Support\PaginatedResponse;
$result = $cf->accounts()->list();
// PaginatedResponse
$result->items; // array of typed objects
$result->total; // total record count
$result->page; // current page
$result->perPage; // records per page
$result->totalPages; // total pages
$result->hasMorePages(); // bool
foreach ($result as $account) { ... }
// Single-item responses return typed objects directly
$account = $cf->accounts()->listGet(); // AccountsDetails object
$account->id;
$account->name;The client throws CloudflareException on any non-2xx response. It is thrown automatically — you do not need to check the response yourself.
use FoundryCo\Cloudflare\Exceptions\CloudflareException;
try {
$result = $cf->dnsRecords()->get('zone-id', 'record-id');
} catch (CloudflareException $e) {
$e->statusCode; // HTTP status
$e->errors; // array of ['code' => int, 'message' => string]
$e->getMessage(); // concatenated error messages
}Resources are available directly from the client or scoped to a zone via zone().
$cf->accounts()->list();
$cf->members()->list();
$cf->dnsRecords(); // zone ID must be baked in via zone()Pass a zone ID to zone() and then chain the resource:
$zone = $cf->zone('zone-id');
$zone->dnsRecords()->list();
$zone->dnsRecords()->get('record-id');
$zone->cacheSettings()->get();
$zone->rulesets()->list();$cf->accounts()->list();
$cf->accounts()->list(name: 'Acme', direction: AccountsDirection::Asc);
$cf->accounts()->listGet(); // single account details$cf->members()->list();$dns = $cf->zone('zone-id')->dnsRecords();
$dns->list();
$dns->list(type: DNSRecordsForAZoneType::A, name: 'www.example.com');
$dns->list(proxied: true, perPage: 100);
$dns->get('record-id');
$dns->create(); // pass a request object
$dns->update('record-id');
$dns->delete('record-id');
// Batch operations
$dns->batch($request);
// BIND zone file
$dns->export();
$dns->import();
$dns->scan();
$dns->review();$cf->zone('zone-id')->dnsSettings()->get();$cf->zone()->list(); // ZoneResource also exposes zone-level list/get$rulesets = $cf->zone('zone-id')->rulesets();
$rulesets->list();
$rulesets->get('ruleset-id');
$rulesets->create($request);
$rulesets->update('ruleset-id', $request);
$rulesets->delete('ruleset-id');$cf->zone('zone-id')->cacheSettings()->get();
$cf->zone('zone-id')->cacheSettings()->update($request);$cf->workerScript()->list();
$cf->workerScript()->get('script-name');
$cf->workers()->list();$cf->workersKVNamespace()->list();
$cf->workersKVNamespace()->create($request);
$cf->workersKVNamespace()->delete('namespace-id');
$cf->keys()->list();
$cf->values()->get('namespace-id', 'key');$cf->d1()->list();
$cf->d1()->create($request);
$cf->d1()->get('database-id');
$cf->d1()->delete('database-id');$cf->r2Bucket()->list();
$cf->r2Bucket()->create($request);
$cf->r2Bucket()->get('bucket-name');
$cf->r2Bucket()->delete('bucket-name');
$cf->r2Object()->get('bucket-name', 'object-key');$cf->pagesProject()->list();
$cf->pagesProject()->get('project-name');
$cf->pagesProject()->create($request);
$cf->pagesProject()->delete('project-name');
$cf->pagesDeployment()->list('project-name');
$cf->pagesDeployment()->delete('project-name', 'deployment-id');$cf->streamVideos()->list();
$cf->streamVideos()->get('video-id');
$cf->streamVideos()->delete('video-id');
$cf->streamLiveInputs()->list();
$cf->streamSigningKeys()->list();$cf->cloudflareImages()->list();
$cf->cloudflareImages()->get('image-id');
$cf->cloudflareImages()->delete('image-id');
$cf->cloudflareImagesVariants()->list();$cf->cloudflareTunnel()->list();
$cf->cloudflareTunnel()->create($request);
$cf->cloudflareTunnel()->delete('tunnel-id');
$cf->cloudflareTunnelConfiguration()->get('tunnel-id');
$cf->cloudflareTunnelConfiguration()->update('tunnel-id', $request);$cf->accessApplications()->list();
$cf->accessApplications()->get('app-id');
$cf->accessApplications()->create($request);
$cf->accessApplications()->update('app-id', $request);
$cf->accessApplications()->delete('app-id');
$cf->accessGroups()->list();
$cf->accessIdentityProviders()->list();
$cf->accessServiceTokens()->list();
$cf->accessReusablePolicies()->list();
$cf->zeroTrustOrganization()->get();
$cf->zeroTrustGatewayRules()->list();
$cf->zeroTrustLists()->list();$cf->loadBalancerPools()->list();
$cf->loadBalancerPools()->create($request);
$cf->loadBalancerMonitors()->list();
$cf->loadBalancerMonitors()->create($request);$cf->logpushJobs()->list();
$cf->logpushJobs()->create($request);
$cf->logpushJobs()->update('job-id', $request);
$cf->logpushJobs()->delete('job-id');$cf->vectorize()->list();
$cf->vectorize()->create($request);
$cf->vectorize()->get('index-name');
$cf->vectorize()->delete('index-name');$cf->aiGatewayGateways()->list();
$cf->aiGatewayLogs()->list('gateway-id');$cf->workersAI()->run($request);
$cf->workersAITextGeneration()->run($request);
$cf->workersAITextEmbeddings()->run($request);$cf->hyperdrive()->list();
$cf->hyperdrive()->create($request);
$cf->hyperdrive()->get('config-id');
$cf->hyperdrive()->delete('config-id');$cf->queue()->list();
$cf->queue()->create($request);
$cf->queue()->delete('queue-id');$cf->radarHTTP()->timeseries($request);
$cf->radarLayer7Attacks()->timeseries($request);
$cf->radarBGP()->timeseries($request);
$cf->radarDNS()->timeseries($request);$cf->notificationPolicies()->list();
$cf->notificationPolicies()->create($request);
$cf->notificationWebhooks()->list();
$cf->notificationAlertTypes()->list();$cf->registrarDomains()->list();
$cf->registrarDomains()->get('domain-name');
$cf->registrarDomains()->update('domain-name', $request);$cf->urlScanner()->scan($request);
$cf->urlScanner()->get('scan-id');$cf->ipIntelligence()->get('1.2.3.4');
$cf->domainIntelligence()->get('example.com');
$cf->urlIntelligence()->get('https://example.com');
$cf->whoisRecord()->get('example.com');
$cf->asnIntelligence()->get(13335);$cf->turnstile()->list();
$cf->turnstile()->create($request);
$cf->turnstile()->delete('widget-id');$cf->user()->get();
$cf->user()->update($request);
$cf->userAPITokens()->list();
$cf->usersAccountMemberships()->list();List endpoints that return result_info automatically return a PaginatedResponse. Iterate pages manually:
$page = 1;
$allRecords = [];
do {
$result = $cf->zone('zone-id')->dnsRecords()->list(page: $page, perPage: 100);
$allRecords = array_merge($allRecords, $result->items);
$page++;
} while ($result->hasMorePages());The CloudflareClient constructor accepts a custom HttpClient instance, which you can substitute in tests:
use FoundryCo\Cloudflare\CloudflareClient;
use FoundryCo\Cloudflare\Http\HttpClient;
$mockHttp = $this->createMock(HttpClient::class);
// configure expectations...
$cf = new CloudflareClient(apiToken: 'fake', http: $mockHttp);The SDK is auto-generated from the official Cloudflare OpenAPI spec. To regenerate:
php generator/bin/generateMIT