# Domain Capability Hooks

https://dev.wisecp.com/es/domain-capability-hooks

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.

$contextarrayref`service` 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 PHP

```php
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.

$contextarrayref`service` 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 PHP

```php
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 PHP

```php
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.

## Related Articles

- [Domain DNS Hooks](https://dev.wisecp.com/en/domain-dns-hooks)
- [Domain Contact Hooks](https://dev.wisecp.com/en/domain-contact-hooks)
- [Writing a Hook Listener](https://dev.wisecp.com/en/writing-a-hook-listener)
