pstoute / laravel-domain-registrar
A unified domain registrar abstraction layer for Laravel, providing a single API to interact with multiple domain registrars
Package info
github.com/pstoute/laravel-domain-registrar
pkg:composer/pstoute/laravel-domain-registrar
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.0
- illuminate/contracts: ^10.0|^11.0|^12.0
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
Suggests
- aws/aws-sdk-php: Required for Route53 registrar support (^3.0)
README
A unified domain registrar abstraction layer for Laravel, providing a single API to interact with multiple domain registrars.
Features
- Unified API: Single interface for all registrar operations
- Multiple Registrars: Support for NameCheap, GoDaddy, Enom, OpenSRS, and Route53
- Domain Management: Register, renew, transfer, and manage domains
- DNS Management: Create, update, and delete DNS records
- WHOIS: Lookup and privacy management
- Immutable DTOs: Type-safe data transfer objects
- Caching: Built-in caching for API responses
- Rate Limiting: Respect registrar API limits
- Testing: Includes
FakeRegistrarfor application testing
Requirements
- PHP 8.2+
- Laravel 10, 11, or 12
Installation
composer require pstoute/laravel-domain-registrar
Publish the configuration file:
php artisan vendor:publish --tag=domains-config
Configuration
Configure your registrars in config/domains.php or via environment variables:
DOMAIN_REGISTRAR=namecheap # NameCheap NAMECHEAP_API_USER=your_api_user NAMECHEAP_API_KEY=your_api_key NAMECHEAP_USERNAME=your_username NAMECHEAP_CLIENT_IP=your_whitelisted_ip NAMECHEAP_SANDBOX=false # GoDaddy GODADDY_API_KEY=your_api_key GODADDY_API_SECRET=your_api_secret GODADDY_SANDBOX=false # Enom ENOM_UID=your_uid ENOM_PW=your_password ENOM_SANDBOX=false # OpenSRS OPENSRS_USERNAME=your_username OPENSRS_API_KEY=your_api_key OPENSRS_SANDBOX=false # Route53 (AWS) AWS_ACCESS_KEY_ID=your_access_key AWS_SECRET_ACCESS_KEY=your_secret_key AWS_DEFAULT_REGION=us-east-1
Basic Usage
Using the Facade
use Pstoute\LaravelDomains\Facades\Domains; // Use the default registrar $available = Domains::checkAvailability('example.com'); // Use a specific registrar $domain = Domains::driver('godaddy')->getDomain('example.com');
Using Dependency Injection
use Pstoute\LaravelDomains\DomainsManager; class DomainController extends Controller { public function __construct( private DomainsManager $domains ) {} public function check(string $domain) { return $this->domains->checkAvailability($domain); } }
Domain Operations
Check Availability
use Pstoute\LaravelDomains\Facades\Domains; // Single domain $result = Domains::checkAvailability('example.com'); if ($result->available) { echo "Domain is available!"; if ($result->premium) { echo "Premium price: {$result->price} {$result->currency}"; } } // Multiple domains $results = Domains::checkBulkAvailability([ 'example.com', 'example.net', 'example.org', ]); foreach ($results as $result) { echo "{$result->domain}: " . ($result->available ? 'Available' : 'Taken'); }
Register a Domain
use Pstoute\LaravelDomains\Facades\Domains; use Pstoute\LaravelDomains\Data\DomainContact; $contact = new DomainContact( firstName: 'John', lastName: 'Doe', email: 'john@example.com', phone: '+1.5551234567', address1: '123 Main St', city: 'New York', state: 'NY', postalCode: '10001', country: 'US', organization: 'ACME Corp', // optional ); $domain = Domains::register('example.com', $contact, years: 2); echo "Registered: {$domain->name}"; echo "Expires: {$domain->expiresAt->format('Y-m-d')}";
Renew a Domain
$domain = Domains::renew('example.com', years: 1); echo "New expiry: {$domain->expiresAt->format('Y-m-d')}";
Transfer a Domain
$domain = Domains::transfer( domain: 'example.com', authCode: 'AUTH-CODE-FROM-CURRENT-REGISTRAR', contact: $contact, years: 1 );
Get Domain Information
$domain = Domains::getDomain('example.com'); echo "Status: {$domain->status->value}"; echo "Expires: {$domain->expiresAt->format('Y-m-d')}"; echo "Auto-renew: " . ($domain->autoRenew ? 'Yes' : 'No'); echo "Locked: " . ($domain->locked ? 'Yes' : 'No'); // Check expiration if ($domain->isExpiringSoon(days: 30)) { echo "Warning: Domain expires in {$domain->daysUntilExpiry()} days!"; }
List All Domains
$domains = Domains::listDomains(page: 1, perPage: 100); foreach ($domains as $domain) { echo "{$domain->name} - {$domain->status->value}"; }
Manage Nameservers
// Get current nameservers $nameservers = Domains::getNameservers('example.com'); // Set new nameservers Domains::setNameservers('example.com', [ 'ns1.cloudflare.com', 'ns2.cloudflare.com', ]);
Lock/Unlock Domain
// Lock domain (prevent transfers) Domains::lock('example.com'); // Unlock domain (allow transfers) Domains::unlock('example.com'); // Get transfer auth code $authCode = Domains::getAuthCode('example.com');
DNS Management
Get DNS Records
$records = Domains::getDnsRecords('example.com'); foreach ($records as $record) { echo "{$record->type->value} {$record->name} -> {$record->value}"; }
Create DNS Records
use Pstoute\LaravelDomains\Data\DnsRecord; // A record $record = Domains::createDnsRecord( 'example.com', DnsRecord::a('www', '192.168.1.1') ); // AAAA record Domains::createDnsRecord( 'example.com', DnsRecord::aaaa('www', '2001:db8::1') ); // CNAME record Domains::createDnsRecord( 'example.com', DnsRecord::cname('blog', 'example.github.io.') ); // MX record with priority Domains::createDnsRecord( 'example.com', DnsRecord::mx('@', 'mail.example.com.', 10) ); // TXT record (SPF, DKIM, etc.) Domains::createDnsRecord( 'example.com', DnsRecord::txt('@', 'v=spf1 include:_spf.google.com ~all') ); // SRV record Domains::createDnsRecord( 'example.com', DnsRecord::srv('_sip._tcp', 'sipserver.example.com.', 10, 0, 5060) ); // Custom TTL Domains::createDnsRecord( 'example.com', DnsRecord::a('api', '192.168.1.100', 300) );
Update DNS Record
$record = new DnsRecord( type: 'A', name: 'www', content: '192.168.1.2', ttl: 3600, id: 'existing-record-id', ); Domains::updateDnsRecord('example.com', 'existing-record-id', $record);
Delete DNS Record
Domains::deleteDnsRecord('example.com', 'record-id');
WHOIS
Lookup
$whois = Domains::whois('example.com'); echo "Available: " . ($whois->available ? 'Yes' : 'No'); echo "Registrar: {$whois->registrar}"; echo "Created: {$whois->createdAt?->format('Y-m-d')}"; echo "Expires: {$whois->expiresAt?->format('Y-m-d')}";
WHOIS Privacy
// Enable WHOIS privacy Domains::enableWhoisPrivacy('example.com'); // Disable WHOIS privacy Domains::disableWhoisPrivacy('example.com');
Pricing
// Get pricing for a TLD $pricing = Domains::getPricing('com'); echo "Registration: {$pricing->registrationPrice} {$pricing->currency}"; echo "Renewal: {$pricing->renewalPrice} {$pricing->currency}"; echo "Transfer: {$pricing->transferPrice} {$pricing->currency}"; // Sync all pricing $allPricing = Domains::syncPricing();
Contact Management
// Get domain contacts $contacts = Domains::getContacts('example.com'); // Update contacts Domains::updateContacts('example.com', [ 'registrant' => $newContact, 'admin' => $adminContact, 'tech' => $techContact, ]);
Testing
The package includes a FakeRegistrar for testing your application without making real API calls:
use Pstoute\LaravelDomains\Tests\Fakes\FakeRegistrar; use Pstoute\LaravelDomains\DomainsManager; class YourTest extends TestCase { public function test_domain_registration() { $fake = new FakeRegistrar(); $fake->setAvailable('newdomain.com'); // Option 1: Bind to container $this->app->instance(DomainsManager::class, new class($fake) extends DomainsManager { public function __construct(private $fake) {} public function driver($driver = null) { return $this->fake; } }); // Option 2: Use directly $result = $fake->checkAvailability('newdomain.com'); $this->assertTrue($result->available); // Assert methods were called $fake->assertCalled('checkAvailability', ['newdomain.com']); } public function test_with_existing_domains() { $fake = new FakeRegistrar(); $fake->addDomain('existing.com'); $fake->addDomain('another.com', DomainStatus::Expired); $domains = $fake->listDomains(); $this->assertCount(2, $domains); } public function test_failure_scenarios() { $fake = new FakeRegistrar(); $fake->shouldFail('API Error'); $this->expectException(RuntimeException::class); $fake->testConnection(); } }
Events
The package dispatches events for domain lifecycle operations:
DomainRegistered- When a domain is registeredDomainRenewed- When a domain is renewedDomainTransferred- When a domain transfer is initiatedDnsRecordCreated- When a DNS record is createdDnsRecordUpdated- When a DNS record is updatedDnsRecordDeleted- When a DNS record is deleted
use Pstoute\LaravelDomains\Events\DomainRegistered; class DomainRegisteredListener { public function handle(DomainRegistered $event): void { Log::info("Domain registered: {$event->domain->name}"); Log::info("Registrar: {$event->registrar}"); Log::info("Years: {$event->years}"); } }
Supported Registrars
| Registrar | Registration | DNS | WHOIS | Notes |
|---|---|---|---|---|
| NameCheap | ✅ | ✅ | ✅ | Full support |
| GoDaddy | ✅ | ✅ | ✅ | Full support |
| Enom | ✅ | ✅ | ✅ | 30 req/min rate limit |
| OpenSRS | ✅ | ✅ | ✅ | MD5 signature auth |
| Route53 | ❌ | ✅ | ❌ | DNS-only (requires aws/aws-sdk-php) |
Error Handling
The package throws specific exceptions for different error conditions:
use Pstoute\LaravelDomains\Exceptions\{ RegistrarException, AuthenticationException, RateLimitException, DomainNotFoundException, DomainUnavailableException, ConfigurationException, OperationNotSupportedException, }; try { $domain = Domains::register('example.com', $contact); } catch (DomainUnavailableException $e) { // Domain is already registered } catch (AuthenticationException $e) { // Invalid API credentials } catch (RateLimitException $e) { // Too many requests, retry after $e->retryAfter seconds } catch (RegistrarException $e) { // General registrar error Log::error("Registrar error: {$e->getMessage()}", [ 'registrar' => $e->registrar, 'context' => $e->context, ]); }
License
MIT License. See LICENSE for details.