Skip to content

Extension points

Surfaces external modules can rely on without forking the dns module. This page covers the contract; the Architecture and Writing a record type pages cover the rationale.

Plugin managers

plugin.manager.dns_record_type

Service id: plugin.manager.dns_record_typeRecordTypeManager.

  • Discovery: PHP-attribute-based via the #[Drupal\dns\Attribute\RecordType] attribute on classes under src/Plugin/RecordType/ of any installed module.
  • Contract: implementations satisfy RecordTypeInterface.
  • Plugin id: matches the DNS record-type token (A, MX, SRV, …). The ids surface in the entity's record_type field's allowed values via DnsRecord::recordTypeAllowedValues().

Add a new record type by dropping a class under MyModule\Plugin\RecordType\ with the attribute. No service or schema changes needed.

Hooks

hook_dns_record_type_info_alter(array &$definitions) runs after discovery and before the plugin manager caches the definition map. Use it to relabel, hide, or augment third-party plugins; the keys are the plugin ids. Don't add new plugins via the alter hook — register them as classes so they participate in dependency injection and the attribute discovery cache.

hook_ENTITY_TYPE_* for dns_zone and dns_record work as for any content entity — hook_dns_zone_predelete, hook_dns_record_presave, hook_dns_record_view, etc. The zone-deletion-blocked-by-records guard runs in DnsZone::preDelete(), so a predelete hook fires after that check passes.

Views integration

The dns_record_rdata_value Views field handler renders one rdata key per instance — admins (or other modules) configure the JSON key in the field options. External modules that ship custom views referencing rdata can include the field handler with no further setup; see the Views customization page for the user-facing side.

The dns_records view (configured at install) is plain config — modules that want a different default can replace it via standard Drupal config-import flows.

Form alters

DnsRecordForm is a regular ContentEntityForm, alterable with hook_form_dns_record_form_alter and hook_form_dns_record_<bundle>_form_alter — though dns_record currently has a single bundle, so the bundle-specific variant matches the generic one. The form attaches two libraries:

  • dns/target_preview — live BIND-resolution under the target widget.
  • dns/prefix_overlap_warning — confirm dialog if the prefix tail duplicates the zone label.

If your module wants to skip either enhancement, detach it in a form alter:

function mymodule_form_dns_record_form_alter(&$form, $form_state): void {
  $form['#attached']['library'] = array_filter(
    $form['#attached']['library'] ?? [],
    fn ($lib) => $lib !== 'dns/target_preview',
  );
}

Access policy: the dns_zone scope

Per-zone capabilities are modeled via Drupal core's Access Policy API. The module ships DnsZoneGrantPolicy in the dns_zone scope; each zone's id is an identifier within that scope. Calculated permissions for a user in (dns_zone, <zone_id>) carry whichever capabilities apply (owner implies all; explicit grants add more).

Third-party modules can extend the dns_zone scope with their own access policies. The standard pattern:

final class MyProviderConstraintPolicy extends AccessPolicyBase {

  public function applies(string $scope): bool {
    return $scope === 'dns_zone';
  }

  public function alterPermissions(
    AccountInterface $account,
    string $scope,
    RefinableCalculatedPermissionsInterface $calculated_permissions,
  ): void {
    // Revoke `edit zone` and `edit records` on every zone this
    // provider has imported as read-only — even owners shouldn't
    // mutate them locally.
    foreach ($calculated_permissions->getItems() as $item) {
      if (!$this->isReadOnly($item->getIdentifier())) {
        continue;
      }
      // Use removeItem / addItem to refine; see the API docs.
    }
  }

  public function getPersistentCacheContexts(): array {
    return ['user'];
  }
}

Tag the service with access_policy and the processor will pick it up automatically. The constant DnsZoneGrantPolicy::SCOPE is guaranteed stable for third-party use.

Provider-sync architecture (planned)

A plugin.manager.dns_provider is on the roadmap (Cloudflare, Route 53, BIND export, …). The interface and lifecycle aren't stable yet — no third-party module should depend on it before it lands. The Architecture roadmap table tracks the design sketch. When a provider plugin ships, it'll typically also register an access policy in the dns_zone scope (per the previous section) to enforce ops constraints — read-only zones, records-editable-but-zone-locked, and so on.