
September 12, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
HATEOAS and Hypermedia APIs embed navigational links inside JSON responses so clients discover available actions at runtime. Most teams skip that step. They ship plain JSON and hard-code every URL in mobile apps, SPAs, and partner integrations. That works until you version routes, rename resources, or onboard clients you do not control. On production API development projects, I treat hypermedia as an architectural choice—not a REST purity test. This guide explains what HATEOAS actually means, how it maps to the Richardson Maturity Model, and when the extra payload complexity pays off in Laravel 13, Symfony 8.1, and public integration work.
_links, links, or relation arrays—that tell clients which actions are valid next. Clients follow those links instead of hard-coding URLs, which reduces coupling when routes, versions, or permissions change.What is HATEOAS and how does it relate to hypermedia APIs?
HATEOAS stands for Hypermedia As The Engine Of Application State. It is constraint six in Roy Fielding's REST definition. The server drives client state by returning hypermedia controls alongside data.
A hypermedia API response includes both the resource representation and the transitions the client may take. A booking record might expose self, cancel, and pay links. The client reads those links and chooses an action. It does not assemble URLs from a static route map.
Hypermedia is the broader idea: machine-readable links and forms inside representations. HATEOAS is the REST-specific application of that idea. In practice, engineers use the terms together when discussing REST API design at Richardson Level 3.
Core terms you will see in hypermedia responses
- Link relation (
rel): A semantic label such asself,next, orcancel. Defined in RFC 5988 Web Linking. - href: The target URI for that relation.
- method: Optional hint for POST, PATCH, or DELETE when GET alone is ambiguous.
- Curie: A compact namespace prefix for custom relations, common in HAL payloads.
Level 2 REST—correct verbs and status codes on resource URLs—is where most Laravel and Symfony APIs live today. That is fine for first-party SPAs where you own the client. HATEOAS earns its keep when third parties integrate over years and you need API versioning without breaking unknown consumers.
How do you implement HATEOAS in a Laravel API?
Laravel 13 does not ship a dedicated HATEOAS layer. You compose it from API Resources, conditional logic, and a consistent link envelope. I have used this pattern on booking portals and legal-tech client APIs where document state machines drive available actions.
Step 1: Define a link builder
Centralise URL generation so links stay consistent across controllers and tests. Never scatter route names inside twenty Resource classes without a shared helper.
<?php
namespace App\Http\Resources\Concerns;
trait BuildsHypermediaLinks
{
protected function link(string $rel, string $route, array $params = [], string $method = 'GET'): array
{
return [
'rel' => $rel,
'href' => route($route, $params, absolute: false),
'method' => $method,
];
}
}
Step 2: Attach links inside an API Resource
Return links based on model state and policy checks. A cancelled invoice should not expose a pay link. That is where hypermedia carries business rules—not just navigation.
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources\Concerns\BuildsHypermediaLinks;
class InvoiceResource extends JsonResource
{
use BuildsHypermediaLinks;
public function toArray(Request $request): array
{
$links = [
$this->link('self', 'invoices.show', ['invoice' => $this->id]),
];
if ($this->status === 'pending' && $request->user()?->can('pay', $this->resource)) {
$links[] = $this->link('pay', 'invoices.pay', ['invoice' => $this->id], 'POST');
}
if ($this->status === 'pending' && $request->user()?->can('cancel', $this->resource)) {
$links[] = $this->link('cancel', 'invoices.cancel', ['invoice' => $this->id], 'POST');
}
return [
'id' => $this->id,
'status' => $this->status,
'amount' => $this->amount,
'links' => $links,
];
}
}
This mirrors patterns from building RESTful APIs with Laravel. The difference is explicit: available transitions live in the payload, not in client code.
Step 3: Return collection-level navigation links
List endpoints need self, next, and prev relations. Pair hypermedia pagination with cursor or offset strategies from cursor versus offset pagination guidance. Wrap paginated data so clients always find the next page through links.
return response()->json([
'data' => InvoiceResource::collection($invoices),
'links' => [
['rel' => 'self', 'href' => $invoices->url($invoices->currentPage())],
['rel' => 'next', 'href' => $invoices->nextPageUrl()],
['rel' => 'prev', 'href' => $invoices->previousPageUrl()],
],
]);
Validate link output in feature tests. Assert that a pending invoice includes pay and that a paid invoice omits it. Hypermedia regressions are silent: clients do not crash—they just expose wrong buttons. Use the JSON formatter tool to inspect payloads during development.
When should you choose HATEOAS over plain JSON REST?
Not every endpoint needs hypermedia. I default to Level 2 REST for internal Vue or Livewire frontends on the same repo. I reach for HATEOAS when the API surface is public, long-lived, or state-heavy.
| Scenario | Plain JSON REST | HATEOAS / Hypermedia |
|---|---|---|
| First-party SPA on same codebase | Strong fit — shared types and routes | Usually unnecessary overhead |
| Public partner API with unknown clients | Works with heavy documentation | Strong fit — clients adapt to link changes |
| Mobile apps with slow upgrade cycles | Breaks when URLs shift | Strong fit — store rel names, not paths |
| State machines (bookings, legal workflows) | Clients duplicate server rules | Strong fit — server exposes valid actions |
| High-throughput read APIs | Strong fit — minimal payload | Weak fit — link objects add bytes |
| GraphQL or BFF aggregation layer | Client selects fields explicitly | Rare — different discovery model |
On a legal-tech client portal similar to Mijar Law Associates, document workflows change with case status. Exposing only valid upload or sign links prevents clients from calling endpoints that will return 403 errors. That is a UX win and a security boundary.
Payment integrations are the opposite story. Gateways like eSewa or Stripe expect fixed callback URLs. Hypermedia helps your internal order state machine. It does not replace gateway contract docs. Combine both: hypermedia for your domain, OpenAPI for external fixed endpoints. See idempotency key patterns for payment POST links that must be safe to retry.
What hypermedia formats should you use for API responses?
REST does not mandate a single hypermedia syntax. Teams pick a convention and enforce it across every endpoint. Mixing HAL on one route and JSON:API on another destroys client trust faster than skipping HATEOAS entirely.
Common format options
- Custom
linksarray: Lowest friction in Laravel. You define shape and validation. Best when you control all clients and publish an SDK. - HAL (Hypertext Application Language): Uses
_linksand optional_embedded. Mature tooling and clear nesting for related resources. - JSON:API: Standardised
links,relationships, and sparse fieldsets. Strong ecosystem; see the JSON:API specification. - Collection+JSON or Siren: Richer form descriptions for generic hypermedia clients. Rare in PHP business APIs but useful for exploratory admin tools.
Symfony 8.1 projects can normalise through the serializer component. Laravel teams often stay with API Resources unless they already standardise on JSON:API via a package. Either way, pick one envelope and document it beside your OpenAPI file.
{
"id": 42,
"status": "pending",
"_links": {
"self": { "href": "/api/v1/invoices/42" },
"pay": { "href": "/api/v1/invoices/42/pay", "method": "POST" }
}
}
That HAL-style snippet is readable in logs and diff-friendly in code review. For SDK design, expose helper methods like invoice.pay() that read rel keys internally. Client developers should not parse raw JSON by hand.
How do you test, secure, and document hypermedia APIs?
Hypermedia shifts complexity from client route maps to server link correctness. Your test suite and docs must catch missing or stale relations before release.
Testing link contracts
Write assertions on relation names, not full URLs. Paths change with prefix or version; semantics should not.
public function test_pending_invoice_exposes_pay_link(): void
{
$invoice = Invoice::factory()->pending()->create();
$response = $this->actingAs($this->admin)->getJson("/api/v1/invoices/{$invoice->id}");
$response->assertOk()
->assertJsonPath('status', 'pending')
->assertJsonFragment(['rel' => 'pay', 'method' => 'POST']);
}
Pair this with policy tests from Laravel API best practices. A link must never appear when authorization would deny the action.
Documentation strategy
OpenAPI describes static paths well. Hypermedia describes dynamic availability. Publish both: an OpenAPI spec for resource shapes and a short hypermedia guide listing every rel name, expected method, and state prerequisites. Tools like Scribe help for Laravel—see API documentation with Scribe and Redoc and Swagger UI for static docs.
Authenticate link generation with the same guards you use for routes. Sanctum and Passport patterns from Passport versus Sanctum apply. A hypermedia response is not a shortcut around API security review.
Gateway and aggregation layers
API gateways can strip or rewrite links if they terminate TLS on a different hostname. Configure your gateway to preserve link hrefs or rewrite them consistently. Patterns from Kong API gateway setup and API gateway patterns apply. On trekking booking systems like Adventure Third Pole Trek, BFF endpoints sometimes flatten hypermedia for a Livewire UI while the public mobile API keeps full links.
For greenfield platforms under enterprise application development, consider an API-first workflow. Define relation names in the contract before you write controllers. That prevents six months of ad hoc JSON shapes.
Key Takeaways
- HATEOAS and Hypermedia APIs put navigational links in responses so clients discover actions instead of hard-coding URLs.
- Most internal Laravel 13 and Symfony 8.1 apps stay at Richardson Level 2 unless third-party or mobile clients need runtime discovery.
- Implement links in API Resources with policy-aware conditionals—never expose transitions the server would reject.
- Pick one hypermedia format (custom links, HAL, or JSON:API) and test relation names, not full href strings.
- Combine hypermedia docs with OpenAPI for shapes and an SDK that wraps link following for integrators.
- Audit gateway and CDN layers so rewritten hostnames do not break href values in production.
People Also Ask
Is HATEOAS required for a REST API?
No. Fielding's REST architectural style includes HATEOAS as a constraint, but industry usage often means Level 2 HTTP APIs with good documentation. Many successful public APIs never adopted hypermedia. You need HATEOAS when unknown clients must survive URL and workflow changes without redeploying.
What is the difference between HATEOAS and OpenAPI?
OpenAPI describes your API surface statically—paths, schemas, and operations. HATEOAS describes what is available dynamically for a specific resource instance. OpenAPI says an invoice can be paid; hypermedia says this invoice exposes a pay link right now because status is pending and the caller is authorised.
Does GraphQL replace HATEOAS?
GraphQL uses a schema and client-written queries instead of link following. It solves a different discovery problem. Some teams run GraphQL at the edge and REST hypermedia for partner integrations. Choose based on client diversity, not fashion.
Can you use HATEOAS with Laravel Sanctum authentication?
Yes. Sanctum tokens authenticate the request; your Resource classes decide which links appear for that user. Links and policies must agree. Never emit a POST link for an action the same token cannot perform.
Ship APIs that clients can evolve with
HATEOAS and Hypermedia APIs are not academic REST trivia. They are a practical tool when your API outlives its first client app and business rules gate which actions exist at each state. Start with Level 2 REST, add links where coupling hurts, and document every relation name you publish. If you are planning a public integration layer on PHP 8.3+ or Laravel 13, contact us for architecture review—or explore custom software development for booking, legal, and eCommerce platforms that need state-aware APIs built correctly from day one.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

