Domain Capability Hooks

5 views Markdown

The two hooks that hand a single domain capability to a provider other than the registrar.

Overview

A domain service resolves to exactly one module: its registrar. So a provider that is not the registrar has no slot of its own. A DNS server, a forwarding service, a WHOIS privacy provider: none of them can register a domain, and none of them can be the module.

These two hooks open that door. One decides what the screens offer, the other decides who answers the call. The registrar keeps every capability you do not claim.

The capability names are shared: nameservers, child_ns, transfer_lock, auth_code, dns_records, dns_edit, dnssec, email_forwarding, domain_forwarding, whois, whois_privacy.

Use both. Only the matrix gives you a tab with nothing behind it. Only the provider gives you a capability nobody can reach.

Reference

Opening a capability

filterdomain.capabilities
Hook::runRefs by reference from four screens

Runs once the module probe is done and before the tabs are built. Four screens compute this matrix separately and every one of them calls the hook: the customer panel, the customer API, the management panel and the admin API.

Parameters 2
$capsarrayrefThe capability matrix, name => bool. Raw names on all four screens; the admin payload renames them to has_* afterwards.
$contextarrayrefservice the domain record, instance the registrar or null, surface one of website, client_api, admin_panel, admin_api.
Return 1
voidThe matrix changes by reference; the return is not read.
Listener
Hook::add('filter:domain.capabilities', 10, function (&$caps, $context = []) {
    $service = $context['service'] ?? [];
    if (!MyDns::handles((int) ($service['id'] ?? 0))) return;

    $caps['dns_records'] = true;
    $caps['dns_edit']    = true;
});

Answering the call

filterdomain.capability_provider
Services::instance_module() by reference

Runs when a screen asks for the module instance and names the capability it is about to use. A plain lookup never reaches this hook, so the service lifecycle is untouched.

Parameters 2
$providerobject|falserefArrives as false. Assign an object and the call goes to it; anything else is ignored.
$contextarrayrefservice the domain record, capability the name being asked for, instance the registrar or null.
Return 1
voidThe provider is handed over by reference; the return is not read.
Listener
Hook::add('filter:domain.capability_provider', 10, function (&$provider, $context = []) {
    if (!in_array((string) ($context['capability'] ?? ''), ['dns_records', 'dns_edit'], true)) return;

    $service = $context['service'] ?? [];
    if (!MyDns::handles((int) ($service['id'] ?? 0))) return;

    $provider = new MyDnsProvider($service);
});

What the provider must look like

Your provider carries the same method names the registrar would. Declare them as real methods.

Provider
class MyDnsProvider
{
    public string $error = '';

    public function set_service(int|array $service = []): void {}

    public function get_dns_records(): array|false
    {
        // ... read the zone
        return $records;
    }

    public function add_dns_record($type, $name, $value, $ttl, $priority): bool
    {
        // ... write one record
        return true;
    }
}

Pitfalls

A magic __call() will not work. Every screen asks method_exists() before it calls anything, and a method served by __call() is invisible to that check. Your provider is then never called and nothing reports an error. Declare each method for real.

One hook alone is not enough. Open the matrix without a provider and the customer opens an empty tab. Hand over a provider without opening the matrix and the tab never shows up.

Four screens, one answer. The matrix is computed separately on each screen. A capability opened in the panel but missed in the API reads as the tab being visible while the endpoint refuses the request.

The lifecycle is not yours. A capability handover only covers the capability. Creation, renewal, transfer and cancellation keep going to the registrar, which is what keeps this safe.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.