Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Hetzner Dns Api Laravel Package

dutchcodingcompany/laravel-hetzner-dns-api

A Laravel wrapper for the Hetzner DNS API that lets you manage DNS zones and records from your application. Configure your API token and list, create, update, or delete records through a simple, Laravel-friendly interface. Ideal for automating domain and DNS workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a thin Laravel wrapper around Hetzner's DNS API, so onboarding follows the usual Laravel package ritual:

  1. Install via Composer: composer require dutchcodingcompany/laravel-hetzner-dns-api. Pin the version explicitly (:^1.0) — the package is archived and you don't want silent drift.
  2. Publish the config (php artisan vendor:publish --tag=hetzner-dns-config or the tag documented in the README) and inspect config/hetzner-dns.php. This is your first stop — it will show the config key name and how the token is resolved.
  3. Set the token: add HETZNER_DNS_API_TOKEN=... to .env. Generate it in the Hetzner DNS console. Use a separate token per environment — do not share staging and production tokens.
  4. Resolve the client: use the facade (typically HetznerDns::...) or inject the underlying client/service through the container. The facade makes quick scripts trivial; DI is preferable in application code.

First use case: pick a disposable zone (not your production domain) and do read-only operations first — list zones, list records for a zone. Confirm the token works, the response shape is what you expect, and exceptions fire correctly on a bad zone ID. Only then attempt a record create in a scratch zone.

Where to look first if the README is thin (this package has a small maintenance footprint):

  • src/ for the facade, service provider, and the request/client classes — these reveal the exact method names and return types.
  • config/hetzner-dns.php for the token key and any base URL override.
  • The Saloon request classes for the actual endpoints covered (zones vs. records vs. anything else).

Implementation Patterns

Wrap the package behind your own adapter. Do not call the facade directly from controllers, jobs, or Artisan commands. Create a small DnsService / HetznerDnsGateway interface with methods like upsertRecord(...), listRecords($zoneId), deleteRecord($id). The package is archived, so this boundary is your escape hatch to swap in a raw Http:: call or another provider later.

Deployment automation (the flagship use case). DNS-as-code for ephemeral preview environments:

  • On deploy: ensure app-{env}.example.com A/CNAME records point to the new host.
  • On rollback: revert records to the previous values.
  • On destroy: delete the ephemeral records. Always capture the pre-change state before mutating, so rollback has something to restore.

Idempotent upserts. DNS operations are not naturally idempotent. Before creating a record, list existing records for the zone and match by (name, type). If present, update; if absent, create. This protects against re-running a deploy step, retries, and partial failures in a multi-record change.

Configuration and environment scoping. Put the token in env(), never in code. Wrap it in config('hetzner-dns.token') (or whatever the published key is) so config:cache works. Re-read the config file after publishing to confirm the key names — this is the most common first-hour stumbling block.

Testing. Mock the underlying HTTP transport (Saloon's MockClient if it exposes one, or Http::fake() if it's on Laravel's client) rather than hitting Hetzner. Assert on the outbound request body for create/update — remember release 1.0.1 specifically fixed HasJsonBody/HasBody serialization, so request-body assertions are worth having as regression coverage.

Artisan commands. Good home for one-off operations: a dns:list-records {zone} command, a dns:sync {env} command for deployment, and a dns:health command that lists zones to verify connectivity. Keeps ad-hoc tinker calls from becoming production incidents.

Error handling. Wrap mutations in a try/catch that distinguishes:

  • Auth failures (401/403) — token is bad or rotated; alert, do not retry.
  • Not found (404) — zone/record ID is stale; reconcile.
  • Validation (422) — your payload is wrong; fix code, do not retry.
  • Rate limit / 5xx — retry with backoff. Log the request payload (minus the token) on failure so you can reproduce.

Dry-run mode. For any automation that mutates a live zone, add a --dry-run flag that prints the intended create/update/delete without calling the API. Non-negotiable if this ever touches a production domain.

Gotchas and Tips

  • The package is archived. Treat it as frozen: pin the version, monitor for Saloon/Laravel major bumps that would break it, and keep your adapter boundary thin so a fork or native Http:: replacement is a small change. Fork it into your org if you need fixes.

  • Saloon version compatibility is the top dependency risk. The wrapper is built on Saloon, not Laravel's HTTP client directly, despite marketing that way. Check the Saloon constraint in composer.json against your Laravel/PHP baseline. A Saloon major bump will likely break this package before Laravel does.

  • HasBody / HasJsonBody change in 1.0.1. This was a compatibility fix for request-body serialization. If you have custom request classes extending or decorating the package's requests, verify they still satisfy the HasBody contract. If you're on 1.0.0, upgrade — the fix matters for correctness, not just compatibility.

  • TTL and propagation are your problem, not the package's. Creating an A record via the API does not mean resolvers see it. If a deploy step depends on the new record resolving, you need a propagation wait/poll loop (query a public resolver for the record and wait for the expected value) — the package will not do this for you.

  • Bulk operations are not a first-class feature. The package exposes per-zone and per-record CRUD. Multi-record changes are a sequence of calls, not a transaction — a mid-sequence failure leaves the zone partially updated. Design your automation to be re-runnable and log every step with the record ID.

  • Rate limits and retries are your responsibility. Hetzner has API quotas; the wrapper is thin and unlikely to have sophisticated backoff. Add a retry policy (exponential backoff on 429/5xx) in your adapter, and avoid tight loops over records at deploy time.

  • The zone id is not the zone name. Hetzner identifies zones by an internal ID; you will need a name→ID lookup at the start of most workflows. Cache this mapping in your adapter — it's a read you don't want on every record operation during a deploy.

  • Record updates typically require the record ID, not just name+type. Your idempotent-upsert logic must first resolve the existing record (list records, match by name+type) to get the ID for the update call. Skipping this is the most common cause of "created a duplicate record" bugs.

  • Token rotation. If you rotate the Hetzner token, remember to clear config:cache on every node; env() reads inside a cached config will not pick up the new value. Keep the token out of logs, error messages, and any audit trail you write.

  • No approval workflow, no dry-run built in. Any production-mutating path must add these yourself, plus a rollback plan that captured the previous record values before the change.

  • Extension points to lean on: the service provider/container bindings (swap in a fake or your own adapter in tests), the config file (base URL override if Hetzner ever changes the endpoint), and the underlying request classes (subclass for custom endpoints the package doesn't cover). Everything else, you own.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource