PHPackages                             porkbun-php/client - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. porkbun-php/client

ActiveLibrary[API Development](/categories/api)

porkbun-php/client
==================

PHP client for the Porkbun API v3 with domain-centric design, typed DTOs, and complete endpoint coverage

v1.1.0(1mo ago)02↓100%[4 PRs](https://github.com/porkbun-php/client/pulls)MITPHPPHP ^8.4CI passing

Since Mar 14Pushed 1mo agoCompare

[ Source](https://github.com/porkbun-php/client)[ Packagist](https://packagist.org/packages/porkbun-php/client)[ Docs](https://github.com/porkbun-php/client)[ RSS](/packages/porkbun-php-client/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (2)Dependencies (16)Versions (7)Used By (0)

 [![Porkbun PHP API Client](art/example.png)](art/example.png)

Porkbun PHP API Client
======================

[](#porkbun-php-api-client)

[![Tests](https://github.com/porkbun-php/client/actions/workflows/tests.yml/badge.svg)](https://github.com/porkbun-php/client/actions/workflows/tests.yml)[![Code Quality](https://github.com/porkbun-php/client/actions/workflows/code-quality.yml/badge.svg)](https://github.com/porkbun-php/client/actions/workflows/code-quality.yml)[![Latest Stable Version](https://camo.githubusercontent.com/32555aadd012caa2cfea1eb61c6fffcc9ab0291bc94275d60c995b0ef8143e5d/68747470733a2f2f706f7365722e707567782e6f72672f706f726b62756e2d7068702f636c69656e742f76)](https://packagist.org/packages/porkbun-php/client)[![License](https://camo.githubusercontent.com/e1dd086ad2cc7e394696105773a154d71113298a136751338f733f7aff3719dd/68747470733a2f2f706f7365722e707567782e6f72672f706f726b62756e2d7068702f636c69656e742f6c6963656e7365)](https://packagist.org/packages/porkbun-php/client)

A community-maintained PHP 8.4+ client for the [Porkbun API v3](https://porkbun.com/api/json/v3/documentation) with complete endpoint coverage, domain-centric design, and Laravel integration.

Features
--------

[](#features)

- **Complete API coverage** — all 27 Porkbun API v3 endpoints: DNS, DNSSEC, SSL, nameservers, URL forwarding, glue records, domains, pricing, and more
- **Domain-centric design** — fluent API: `$client->domain('example.com')->dns()->all()`
- **Typed everything** — immutable DTOs, backed enums, strict return types throughout
- **Fluent builders** — validated DNS record construction with convenience methods
- **Structured errors** — typed exception hierarchy with request/response context
- **Laravel integration** — service provider, facade, and config publishing out of the box
- **PSR-18 compatible** — works with any HTTP client (Guzzle, Symfony, curl, etc.)
- **Strict quality** — Pint, Pest, PHPStan, PHP 8.4+ with Rector

Requirements
------------

[](#requirements)

- PHP 8.4+
- A PSR-18 HTTP client (Guzzle, Symfony HttpClient, or any other)

Installation
------------

[](#installation)

```
composer require porkbun-php/client
```

If you don't already have a PSR-18 HTTP client installed, add one:

```
composer require guzzlehttp/guzzle
# or
composer require symfony/http-client nyholm/psr7
```

Tip

Most frameworks already ship with a PSR-18 client — Laravel includes Guzzle, Symfony includes its HttpClient. The package auto-discovers it, so `new Porkbun\Client()` just works.

Quick Start
-----------

[](#quick-start)

Generate your API key and secret on the [API Access](https://porkbun.com/account/api) page. To manage a specific domain via API, enable **API Access** for that domain in your Porkbun control panel.

```
$client = new Porkbun\Client();
$client->authenticate('pk1_your_api_key', 'sk1_your_secret_key');

// Test connectivity
$ping = $client->ping();
echo "Your IP: {$ping->resolvedIp}";

// Domain pricing (no auth required)
$pricing = $client->pricing()->all();
echo "COM: $" . $pricing->find('com')?->registrationPrice;

// Domain-centric operations
$domain = $client->domain('example.com');

// List DNS records
foreach ($domain->dns()->all() as $record) {
    echo "{$record->name} {$record->type->value} {$record->content}\n";
}

// Get SSL certificate
$cert = $domain->ssl();
echo $cert->certificateChain;
```

Advanced configuration```
// Custom PSR-18 HTTP client
$client = new Porkbun\Client($myPsr18Client);

// IPv4-only endpoint (useful for dynamic DNS)
$client->useIpv4Endpoint();
$client->useDefaultEndpoint(); // back to dual-stack

// Switch accounts at runtime
$client->authenticate($account2Key, $account2Secret);
$client->clearAuth(); // back to unauthenticated
```

API Reference
-------------

[](#api-reference)

Note

All endpoints require authentication except **Pricing** — you can query TLD pricing without API keys.

### Pricing (No Auth Required)

[](#pricing-no-auth-required)

```
$pricing = $client->pricing()->all();

$pricing->find('com')?->registrationPrice;  // float
$pricing->find('com')?->renewalPrice;       // float
$pricing->cheapest(10);                     // Top 10 cheapest TLDs
$pricing->tlds();                           // All available TLD keys
```

### Ping (Auth Test)

[](#ping-auth-test)

```
$ping = $client->ping();

$ping->resolvedIp;  // Your IP address (prefers forwarded IP)
$ping->forwardedIp; // Forwarded IP (from X-Forwarded-For header)
$ping->yourIp;      // Raw IP from API response
```

### Domains

[](#domains)

```
// List all domains (iterates all pages automatically)
foreach ($client->domains()->all() as $domain) {
    echo "{$domain->domain} expires {$domain->expireDate?->format('Y-m-d')}\n";
}

// Single page with pagination metadata
$page = $client->domains()->list();
$page->domains(); // DomainCollection (also available via iteration/count/json on $page itself)
$page->hasMore;    // bool — true if more pages exist
$page->nextStart;  // ?int — pass to list() for the next page
$page->start;      // int — current offset

// PaginatedResult is iterable, countable, and JSON-serializable:
count($page);              // number of domains on this page
json_encode($page);        // serializes with pagination metadata
foreach ($page as $domain) { /* ... */ }

// Paginate manually
$page = $client->domains()->list(start: 0, includeLabels: true);
while ($page->hasMore) {
    $page = $client->domains()->list(start: $page->nextStart);
}

// Find a specific domain
$domain = $client->domains()->find('example.com'); // Domain DTO or null

// Bulk auto-renewal management
$client->domains()->enableAutoRenew('example.com', 'other.com');
$client->domains()->disableAutoRenew('example.com');
```

### Domain Details

[](#domain-details)

```
$domain = $client->domain('example.com');
$info = $domain->details();      // Domain DTO (from your account)

$info->domain;                   // 'example.com'
$info->status;                   // 'ACTIVE'
$info->expireDate;               // ?DateTimeImmutable
$info->autoRenew;                // ?bool
$info->tld;                      // 'com'
```

### Domain Availability

[](#domain-availability)

```
$result = $client->domain('example.com')->check();

$result->isAvailable;           // bool
$result->price;                 // ?float (registration price)
$result->type;                  // string — 'standard', 'premium', etc.
$result->priceInCents;          // ?int (e.g., 999 for $9.99)
$result->effectivePrice;        // ?float (promo price if available, else regular)
```

### Domain Registration

[](#domain-registration)

```
$result = $client->domain('newdomain.com')->register(868);

$result->domain;                 // 'newdomain.com'
$result->orderId;                // int
$result->costInCents;            // int
$result->costInDollars;          // float (computed)
$result->balanceInCents;         // int
$result->balanceInDollars;       // float (computed)
```

### DNS Records

[](#dns-records)

Tip

Use the **builder** for validated record creation, or **direct methods** when you need to bypass client-side validation.

```
$dns = $client->domain('example.com')->dns();

// Retrieve
$dns->all();                          // DnsRecordCollection
$dns->find($recordId);               // DnsRecord or null
$dns->findByType('A');                // DnsRecordCollection
$dns->findByType('A', 'www');         // By type and subdomain
// Create (direct)
$result = $dns->create('A', 'www', '192.0.2.1', ttl: 3600);
echo "Created record: {$result->id}";

// Create (builder — recommended)
$result = $dns->createFromBuilder(
    $dns->record()
        ->a('192.0.2.2')
        ->name('api')
        ->ttl(3600)
        ->notes('API server')
);

// Builder convenience methods
$dns->record()->mx('mail.provider.com', priority: 10)->name('mail');
$dns->record()->txt('v=DMARC1; p=reject')->name('_dmarc');
$dns->record()->cname('blog.provider.com')->name('blog');
$dns->record()->aaaa('2001:db8::1')->name('app');

// Enum types are also accepted
use Porkbun\Enum\DnsRecordType;
$dns->findByType(DnsRecordType::A);
$dns->create(DnsRecordType::A, 'www', '192.0.2.1');

// Update (direct or builder)
$dns->update($recordId, 'A', 'www', '192.0.2.3');
$dns->updateFromBuilder($recordId, $dns->record()->a('192.0.2.3')->name('www'));
$dns->updateByType('A', 'www', '192.0.2.3');

// Delete
$dns->delete($recordId);
$dns->deleteByType('A', 'old-subdomain');

// Collection helpers (all collections support first(), last(), count())
$records = $dns->all();
$records->byType('MX');
$records->byName('www');
$records->rootRecords;
$records->byType('A')->first();
```

### DNSSEC Records

[](#dnssec-records)

```
$dnssec = $client->domain('example.com')->dnssec();

$dnssec->all();              // DnssecRecordCollection
$result = $dnssec->create(keyTag: 12345, algorithm: 13, digestType: 2, digest: 'abc123...');
$result->message;          // ?string
$dnssec->delete($keyTag);
```

### Batch DNS Operations

[](#batch-dns-operations)

```
$dns = $client->domain('example.com')->dns();

// Pre-wired batch builder — no need to pass $dns to execute()
$results = $dns->batch()
    ->addRecord('A', 'www', '192.0.2.1')
    ->addRecord('A', 'api', '192.0.2.2')
    ->add($dns->record()->mx('mail.example.com', priority: 10))  // builder-based add
    ->updateRecord($existingId, 'A', 'www', '192.0.2.3', ttl: 3600)
    ->deleteRecord($oldRecordId)
    ->deleteByType('TXT', 'old-subdomain')
    ->execute();

if ($results->hasFailures()) {
    echo "Some operations failed!\n";
}

foreach ($results as $result) {
    if ($result->success) {
        echo "OK: {$result->operation->value}\n";
    } else {
        echo "Failed: {$result->error}\n";
    }
}
```

### SSL Certificates

[](#ssl-certificates)

```
$cert = $client->domain('example.com')->ssl();

$cert->certificateChain;
$cert->privateKey;
$cert->publicKey;
$cert->fullChain;                   // Chain + intermediate
$cert->hasPrivateKey;               // bool
$cert->hasCertificate;              // bool
$cert->hasIntermediateCertificate;  // bool
```

### Nameservers

[](#nameservers)

```
$ns = $client->domain('example.com')->nameservers();

$ns->all();              // NameserverCollection: ['ns1.porkbun.com', 'ns2.porkbun.com']
$ns->update('ns1.custom.com', 'ns2.custom.com');
```

### URL Forwarding

[](#url-forwarding)

```
$forwards = $client->domain('example.com')->urlForwarding();

$forwards->all();              // UrlForwardCollection
$result = $forwards->create('https://destination.example.com', 'temporary', subdomain: 'go');
$result->message;              // ?string
$forwards->delete($recordId);
```

### Glue Records

[](#glue-records)

```
$glue = $client->domain('example.com')->glueRecords();

$glue->all();              // GlueRecordCollection
$result = $glue->create('ns1', '192.0.2.1', '192.0.2.2');
$result->message;          // ?string
$glue->update('ns1', '192.0.2.10');
$glue->delete('ns1');      // OperationResult
```

### Auto-Renewal

[](#auto-renewal)

```
$autoRenew = $client->domain('example.com')->autoRenew();

$result = $autoRenew->enable();   // AutoRenewResult
$result->success;                 // bool
$result->message;                 // ?string

$autoRenew->disable();
```

Error Handling
--------------

[](#error-handling)

All exceptions implement `Porkbun\Exception\ExceptionInterface` for unified catching:

```
use Porkbun\Exception\ApiException;
use Porkbun\Exception\AuthenticationException;
use Porkbun\Exception\InvalidArgumentException;
use Porkbun\Exception\NetworkException;
use Porkbun\Exception\ExceptionInterface;

try {
    $client->domains()->list();
} catch (AuthenticationException $e) {
    // Invalid or missing API credentials (403)
} catch (ApiException $e) {
    // API returned an error (4xx/5xx)
    $e->getStatusCode();
    $e->getRequest();
    $e->getResponse();
} catch (NetworkException $e) {
    // HTTP/connection failure
    $e->getRequest();
} catch (InvalidArgumentException $e) {
    // Invalid parameters (bad DNS type, empty domain list, etc.)
} catch (ExceptionInterface $e) {
    // Catch-all for any library exception
}
```

Tip

If the default endpoint is unreachable, fall back to `$client->useIpv4Endpoint()`. See [`08-error-handling.php`](examples/08-error-handling.php) for the full pattern.

Laravel Integration
-------------------

[](#laravel-integration)

The package auto-registers via Laravel's package discovery. The service provider is deferred — the client is only instantiated when you use it.

Add credentials to `.env`:

```
PORKBUN_API_KEY=pk1_your_key
PORKBUN_SECRET_KEY=sk1_your_secret
PORKBUN_ENDPOINT=default   # or 'ipv4' for IPv4-only
```

Optionally publish the config:

```
php artisan vendor:publish --tag=porkbun-config
```

### Facade

[](#facade)

```
use Porkbun\Laravel\Facades\Porkbun;

$domains = Porkbun::domains()->list();
$records = Porkbun::domain('example.com')->dns()->all();
```

### Dependency Injection

[](#dependency-injection)

```
use Porkbun\Client;

class DnsController
{
    public function index(Client $client)
    {
        return $client->domain('example.com')->dns()->all();
    }
}
```

Examples
--------

[](#examples)

See the [`examples/`](examples/) directory for runnable scripts:

- [`01-ping.php`](examples/01-ping.php) - Auth test and IPv4 endpoint switching
- [`02-pricing.php`](examples/02-pricing.php) - Public pricing API, cheapest TLDs, iteration
- [`03-domains.php`](examples/03-domains.php) - List domains, pagination, expiring soon, availability check
- [`04-dns.php`](examples/04-dns.php) - DNS CRUD with direct methods, collection helpers
- [`05-dns-builder.php`](examples/05-dns-builder.php) - Fluent builder, convenience methods, immutable templates
- [`06-dns-batch.php`](examples/06-dns-batch.php) - Batch operations, mixed create/edit/delete
- [`07-domain-services.php`](examples/07-domain-services.php) - Nameservers, URL forwarding, glue records, SSL, auto-renew
- [`08-error-handling.php`](examples/08-error-handling.php) - Exception hierarchy, endpoint fallback pattern
- [`09-dynamic-dns.php`](examples/09-dynamic-dns.php) - Real-world dynamic DNS updater recipe
- [`10-multi-account.php`](examples/10-multi-account.php) - Account switching, public/auth/clearAuth flow
- [`11-laravel.php`](examples/11-laravel.php) - Facade usage, dependency injection, Artisan commands

Development
-----------

[](#development)

```
composer install
composer run check    # code style + static analysis + tests
composer run fix      # auto-fix style issues
composer run test     # run test suite
```

License
-------

[](#license)

MIT License. See [LICENSE](LICENSE) for details.

###  Health Score

42

—

FairBetter than 89% of packages

Maintenance96

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity55

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

54d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/cda30b9e506cc0471aaff9890f9a144d8ef2cd2cb2d90ed090ce129f09c3e7db?d=identicon)[glebovdev](/maintainers/glebovdev)

---

Top Contributors

[![glebovdev](https://avatars.githubusercontent.com/u/2257771?v=4)](https://github.com/glebovdev "glebovdev (21 commits)")

---

Tags

api-clientdnsdomainlaravelphpporkbunpsr-18sslapidnssslapi clientdomainporkbundomain registration

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/porkbun-php-client/health.svg)

```
[![Health](https://phpackages.com/badges/porkbun-php-client/health.svg)](https://phpackages.com/packages/porkbun-php-client)
```

###  Alternatives

[openai-php/client

OpenAI PHP is a supercharged PHP API client that allows you to interact with the Open AI API

5.8k22.6M232](/packages/openai-php-client)[wordpress/php-ai-client

A provider agnostic PHP AI client SDK to communicate with any generative AI models of various capabilities using a uniform API.

26236.6k14](/packages/wordpress-php-ai-client)[getbrevo/brevo-php

Official Brevo provided RESTFul API V3 php library

963.1M35](/packages/getbrevo-brevo-php)[theodo-group/llphant

LLPhant is a library to help you build Generative AI applications.

1.5k311.5k5](/packages/theodo-group-llphant)[swisnl/json-api-client

A PHP package for mapping remote JSON:API resources to Eloquent like models and collections.

211473.2k12](/packages/swisnl-json-api-client)[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

35636.1k2](/packages/telnyx-telnyx-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
