Skip to content

JSON:API resources (timacdonald/json-api)

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

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. A paginated collection is documented as a page, whose links list only the pages that exist, as the package sends them — an empty list [] on a cursor page with none either side.

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" } },
"included": { "type": "array", "items": { "$ref": "#/components/schemas/JsonApiResourceObject" } }
},
"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() is 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 the member is left out until the linkage can be modeled from real relationship resolution.

Every document does list the compound-document included array, since an endpoint that accepts include sends one whenever a related resource is loaded. Which resources land in it depends on the request, so each entry is published as a generic resource object — a shared JsonApiResourceObject component with a string id and type. Describe it more precisely with a docblock or an #[Response] attribute if your consumers need it.

When your app registers a server implementation at boot, every document gains a top-level jsonapi object:

app/Providers/AppServiceProvider.php
JsonApiResource::resolveServerImplementationUsing(
fn () => new ServerImplementation('1.1', ['copyright' => 'Acme Inc.']),
);

Docuccino publishes it as { version, meta? }. The callback runs per request and may return nothing, so the member is published as optional. Registering or removing the callback rebuilds the cached documentation on the next run.

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"]
}
}

Laravel’s first-party JSON:API resources (Laravel 12.45 and later) are handled by the framework support; this integration handles the timacdonald/json-api base class. Both feed the same document builder, so moving from the package to the framework resources leaves the documented resource objects unchanged. Two things differ, because the two send them differently: the package’s links are { href, meta? } link objects where the framework sends what toLinks() returns, and a page of the package’s resources leaves out the links there is no page for, where the framework sends them as null. Docuccino documents each as it is sent.

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.

Laravel 12.45 changed how a resource is sent: the response carries what the resource’s resolveResourceData() returns. The package declares that method, returning its resource object, from v1.0.0-beta.10. With an older release on Laravel 12.45 or later, your resources are sent as their attributes alone — no id, no type — so Docuccino documents each one, and each entry in included, as an object whose shape it doesn’t claim, and warns with timacdonald-json-api.resource-object-not-sent. Upgrading the package puts the resource objects back. A resource that overrides resolveResourceData() itself is documented the same way on any release, since the override decides what is sent.

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

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.