Skip to content

JSON:API resources (timacdonald/json-api)

Activates automatically when timacdonald/json-api is installed. This is the package Laravel 13’s first-party JSON:API resources were upstreamed from, so if you’re on an earlier Laravel — or simply prefer the package — your resources are documented identically.

A resource extending TiMacDonald\JsonApi\JsonApiResource becomes a JSON:API document schema, read from the same methods the framework’s first-party resources expose:

Member Where it comes from
id, type Always emitted as strings — that’s the JSON:API contract, so they’re not read from your toId() / toType()
attributes Analyzed from toAttributes()
meta Analyzed from toMeta(), when you override it
links Emitted when you override toLinks(), as relation-keyed { href, meta? } link objects

Endpoints returning one of these resources (or a collection of them) also gain the JSON:API include and fields query parameters, and the response is served as the JSON:API media type application/vnd.api+json.

app/Http/Resources/CustomerResource.php
class CustomerResource extends JsonApiResource
{
public function toAttributes(Request $request): array
{
return [
'name' => $this->name,
'email' => $this->email,
];
}
}
// app/Http/Controllers/CustomerController.php
public function show(Customer $customer): CustomerResource
{
return new CustomerResource($customer);
}

Note what’s hoisted: the component is the resource object, not the { data: … } document. That’s what lets a collection reference the same object per item and wrap the envelope exactly once:

// A collection return — one `data` wrap around an array of resource objects
{
"type": "object",
"properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/CustomerResource" } } },
"required": ["data"]
}

fields is a single deep-object parameter (fields[TYPE]=field1,field2), not one parameter per type — the type keys are runtime data, so they’re modeled with additionalProperties.

toRelationships() and the compound-document included array are not emitted. Both packages express relationships as closures — 'author' => fn () => new AuthorResource($this->author) — and a static read of a closure can’t produce JSON:API’s { data: { type, id } } linkage object. Emitting a guess there would document a shape the resource never returns, so both members are left out until the linkage can be modeled from real relationship resolution.

The include parameter is still documented, because the endpoint really does accept it. Describe what the included array holds with a docblock or an #[Response] attribute if your consumers need it.

links is handled specially for the same reason: toLinks() returns Link objects keyed by relation, which a flat analysis can’t see through — so when you override it, Docuccino emits the link-object shape rather than trying to read your keys:

"links": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": { "href": { "type": "string" }, "meta": { "type": "object" } },
"required": ["href"]
}
}

If your app is on Laravel 13 with the first-party JSON:API resources, that support handles them; this integration handles the timacdonald/json-api base class. Both feed the same document builder, so you can migrate from the package to the framework resources without your documentation changing.

A timacdonald resource is also an Illuminate JsonResource, but the plain API Resources mapper deliberately declines it — you’ll never get a flat toArray() shape for a JSON:API resource by accident.

There’s nothing to configure. The only switch is the shared enabled opt-out, per document:

// config/docuccino.php → documents.default.integrations
'timacdonald_json_api' => ['enabled' => false],

With it off, nothing JSON:API-aware runs for these classes: no document schema, no include/fields parameters, and no application/vnd.api+json media type. Turn it off only if you’d rather document these endpoints by hand. See the integrations reference.