{"id":11475,"date":"2026-05-04T08:54:52","date_gmt":"2026-05-04T08:54:52","guid":{"rendered":"https:\/\/programmingfields.com\/?p=11475"},"modified":"2026-05-04T08:54:54","modified_gmt":"2026-05-04T08:54:54","slug":"laravel-13-json-api-resources","status":"publish","type":"post","link":"https:\/\/programmingfields.com\/laravel-13-json-api-resources\/","title":{"rendered":"Laravel 13 JSON:API Resources: Build Standard-Compliant APIs"},"content":{"rendered":"\n<p>Building APIs in Laravel has always been straightforward. However, getting your API responses to follow a proper specification has usually been a different story. In most cases, that meant either pulling in a third-party package <strong>or<\/strong> writing a lot of boilerplate code manually. As a result, things could quickly become messy and inconsistent. Fortunately, Laravel 13 changes that. Now, it ships with first-party Laravel JSON:API support through a new <code>JsonApiResource<\/code> class. <strong>Because of this<\/strong>, you can generate spec-compliant responses without any extra setup. <strong>Not only that<\/strong>, but it also includes the correct <code>Content-Type<\/code> headers, relationships, sparse fieldsets, and links <strong>by default<\/strong>.<\/p>\n\n\n\n<p><strong>In other words<\/strong>, everything just works right out of the box\u2014<strong>no hacks, no heavy packages, and no unnecessary complexity<\/strong>.<\/p>\n\n\n\n<p>Let&#8217;s build one step by step.<\/p>\n\n\n\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_82_2 counter-hierarchy ez-toc-counter ez-toc-light-blue ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/programmingfields.com\/laravel-13-json-api-resources\/#What_Is_JSON_API\" >What Is JSON:API?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/programmingfields.com\/laravel-13-json-api-resources\/#Step_1_%E2%80%94_Generate_the_JSON_API_Resource\" >Step 1 \u2014 Generate the JSON:API Resource<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/programmingfields.com\/laravel-13-json-api-resources\/#Step_2_%E2%80%94_Define_Attributes\" >Step 2 \u2014 Define Attributes<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/programmingfields.com\/laravel-13-json-api-resources\/#Step_3_%E2%80%94_Define_Relationships\" >Step 3 \u2014 Define Relationships<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/programmingfields.com\/laravel-13-json-api-resources\/#Step_4_%E2%80%94_Return_from_a_Route_or_Controller\" >Step 4 \u2014 Return from a Route or Controller<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/programmingfields.com\/laravel-13-json-api-resources\/#Step_5_%E2%80%94_Sparse_Fieldsets\" >Step 5 \u2014 Sparse Fieldsets<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/programmingfields.com\/laravel-13-json-api-resources\/#Step_6_%E2%80%94_Add_Links_and_Meta\" >Step 6 \u2014 Add Links and Meta<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/programmingfields.com\/laravel-13-json-api-resources\/#Real-World_Response_Example\" >Real-World Response Example<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/programmingfields.com\/laravel-13-json-api-resources\/#Final_Thoughts\" >Final Thoughts<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"What_Is_JSON_API\"><\/span><strong>What Is JSON:API?<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>The <a href=\"https:\/\/jsonapi.org\/\">JSON:API specification<\/a> is a widely adopted standard for structuring API responses. <strong>Essentially<\/strong>, it answers key questions like: Where should relationships go? How can clients request only specific fields? And what should the overall response envelope look like?<\/p>\n\n\n\n<p>In the past, achieving JSON:API compliance in Laravel wasn\u2019t always straightforward. Typically, developers had to rely on packages like Fractal <strong>or<\/strong> implement the specification manually. As a result, this often introduced extra complexity and inconsistency across projects.<\/p>\n\n\n\n<p>However, with Laravel 13, things have changed significantly.<\/p>\n\n\n\n<p>Now, JSON:API support is a native, first-party feature. Because of this, you no longer need external packages or custom implementations. Instead, you get a clean, standardized approach built directly into the framework\u2014making your APIs more consistent, maintainable, and easier to scale.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_1_%E2%80%94_Generate_the_JSON_API_Resource\"><\/span><strong>Step 1 \u2014 Generate the JSON:API Resource<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Run the <code>make:resource<\/code> command with the <code>--json-api<\/code> flag:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">php artisan make:resource PostResource --json-api<\/code><\/pre>\n\n\n\n<p>This generates a new file at <code>app\/Http\/Resources\/PostResource.php<\/code> that extends <code>JsonApiResource<\/code>:<\/p>\n\n\n\n<pre title=\"app\/Http\/Resources\/PostResource.php\" class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">&lt;?php\n\nnamespace App\\Http\\Resources;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Resources\\JsonApi\\JsonApiResource;\n\nclass PostResource extends JsonApiResource\n{\n    \/**\n     * The resource's attributes.\n     *\/\n    public $attributes = [\n        \/\/ define your attributes here\n    ];\n\n    \/**\n     * The resource's relationships.\n     *\/\n    public $relationships = [\n        \/\/ define your relationships here\n    ];\n}<\/code><\/pre>\n\n\n\n<p>That&#8217;s all the setup needed to start building <strong>Laravel JSON:API resources<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_2_%E2%80%94_Define_Attributes\"><\/span><strong>Step 2 \u2014 Define Attributes<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Add your model attributes to the <code>$attributes<\/code> array. Laravel reads them directly from the model:<\/p>\n\n\n\n<pre title=\"app\/Http\/Resources\/PostResource.php\" class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">public $attributes = [\n    'title',\n    'body',\n    'created_at',\n];<\/code><\/pre>\n\n\n\n<p>For more control, override the <code>toAttributes()<\/code> method instead. This is also where you can add computed values using closures \u2014 they are only evaluated when actually needed:<\/p>\n\n\n\n<pre title=\"app\/Http\/Resources\/PostResource.php\" class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">public function toAttributes(Request $request): array\n{\n    return [\n        'title'        => $this->title,\n        'body'         => $this->body,\n        'is_published' => fn () => $this->published_at !== null,\n        'created_at'   => $this->created_at,\n    ];\n}<\/code><\/pre>\n\n\n\n<p>Notice the closure on <code>is_published<\/code>. It is lazy, so it only runs if the client actually requests that field. This is one of the small but powerful optimizations built into <strong>JsonApiResource Laravel<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_3_%E2%80%94_Define_Relationships\"><\/span><strong>Step 3 \u2014 Define Relationships<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Relationships in <strong>Laravel JSON:API resources<\/strong> are only serialized when the client explicitly requests them via the <code>include<\/code> query parameter. By default, this means relationships are not included in the response. As a result, your API avoids unnecessary data transfer right from the start.<\/p>\n\n\n\n<p>In practice, this approach gives full control to the client. For example, if a client needs related data, it can simply request it using <code>?include=...<\/code>. Otherwise, the response remains clean and minimal.<\/p>\n\n\n\n<p>Because of this, you prevent over-fetching without adding extra logic on your end. At the same time, you still retain the flexibility to serve complex, relationship-rich responses when needed.<\/p>\n\n\n\n<p>In other words, instead of always sending everything\u2014or manually managing what to exclude\u2014you let the client decide what it needs. Consequently, your API becomes more efficient, predictable, and scalable, especially as your data and relationships grow.<\/p>\n\n\n\n<p>Add relationship names to the <code>$relationships<\/code> array:<\/p>\n\n\n\n<pre title=\"app\/Http\/Resources\/PostResource.php\" class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">use App\\Http\\Resources\\UserResource;\n\npublic $relationships = [\n    'author' => UserResource::class,\n    'comments',\n];<\/code><\/pre>\n\n\n\n<p>When a client sends <code>?include=author,comments<\/code>, the response automatically includes the full related resources in a top-level <code>included<\/code> array \u2014 exactly as the JSON:API spec requires.<\/p>\n\n\n\n<p>For conditional logic on relationships, use <code>toRelationships()<\/code> instead:<\/p>\n\n\n\n<pre title=\"app\/Http\/Resources\/PostResource.php\" class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">public function toRelationships(Request $request): array\n{\n    return [\n        'author'   => UserResource::class,\n        'comments' => fn () => CommentResource::collection(\n            $this->comments->where('is_public', true)\n        ),\n    ];\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_4_%E2%80%94_Return_from_a_Route_or_Controller\"><\/span><strong>Step 4 \u2014 Return from a Route or Controller<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p><strong>Laravel JSON:API resources<\/strong> work exactly like standard API resources. Return them directly from a route or controller:<\/p>\n\n\n\n<pre title=\"routes\/api.php\" class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">use App\\Http\\Resources\\PostResource;\nuse App\\Models\\Post;\n\nRoute::get('\/api\/posts\/{post}', function (Post $post) {\n    return new PostResource($post);\n});<\/code><\/pre>\n\n\n\n<p>Or use the <code>toResource()<\/code> shorthand on the model:<\/p>\n\n\n\n<pre title=\"routes\/api.php\" class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">Route::get('\/api\/posts\/{post}', function (Post $post) {\n    return $post->toResource();\n});<\/code><\/pre>\n\n\n\n<p>The <strong>Laravel JSON:API<\/strong> response automatically looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">{\n    \"data\": {\n        \"id\": \"1\",\n        \"type\": \"posts\",\n        \"attributes\": {\n            \"title\": \"Hello World\",\n            \"body\": \"This is my first post.\"\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p>The <code>Content-Type<\/code> header is automatically set to <code>application\/vnd.api+json<\/code>. No manual configuration needed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_5_%E2%80%94_Sparse_Fieldsets\"><\/span><strong>Step 5 \u2014 Sparse Fieldsets<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>One of the best features of <strong>Laravel JSON:API<\/strong> is sparse fieldsets. Clients can request only the specific fields they need \u2014 reducing payload size significantly.<\/p>\n\n\n\n<p>The client sends a request like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">GET \/api\/posts?fields[posts]=title,created_at<\/code><\/pre>\n\n\n\n<p>And <code>JsonApiResource<\/code> in Laravel handles the filtering automatically. In other words, you don\u2019t need to write any additional logic to control which fields are returned. By default, everything is streamlined for you.<\/p>\n\n\n\n<p>For instance, when a client requests specific fields using sparse fieldsets, Laravel takes care of the filtering behind the scenes. As a result, only the requested attributes\u2014like <code>title<\/code> and <code>created_at<\/code>\u2014will be included in the response.<\/p>\n\n\n\n<p>Because of this, you can avoid writing repetitive conditional code. At the same time, your API remains clean, predictable, and aligned with the <strong>JSON:API specification<\/strong>. Ultimately, this reduces boilerplate while still giving clients precise control over the data they receive.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_6_%E2%80%94_Add_Links_and_Meta\"><\/span><strong>Step 6 \u2014 Add Links and Meta<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>You can add links and meta to any <strong>Laravel JSON:API resource<\/strong> by overriding <code>toLinks()<\/code> and <code>toMeta()<\/code>:<\/p>\n\n\n\n<pre title=\"app\/Http\/Resources\/PostResource.php\" class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">public function toLinks(Request $request): array\n{\n    return [\n        'self' => route('api.posts.show', $this->resource),\n    ];\n}\n\npublic function toMeta(Request $request): array\n{\n    return [\n        'readable_created_at' => $this->created_at->diffForHumans(),\n    ];\n}<\/code><\/pre>\n\n\n\n<p>This adds <code>links<\/code> and <code>meta<\/code> keys to the resource object in the response \u2014 fully compliant with the JSON:API spec.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Real-World_Response_Example\"><\/span><strong>Real-World Response Example<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Here is what a full <strong>Laravel JSON:API<\/strong> response looks like when a client requests <code>?include=author<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">{\n    \"data\": {\n        \"id\": \"1\",\n        \"type\": \"posts\",\n        \"attributes\": {\n            \"title\": \"Hello World\",\n            \"body\": \"This is my first post.\"\n        },\n        \"relationships\": {\n            \"author\": {\n                \"data\": { \"id\": \"1\", \"type\": \"users\" }\n            }\n        },\n        \"links\": {\n            \"self\": \"https:\/\/example.com\/api\/posts\/1\"\n        }\n    },\n    \"included\": [\n        {\n            \"id\": \"1\",\n            \"type\": \"users\",\n            \"attributes\": {\n                \"name\": \"Umesh Rana\"\n            }\n        }\n    ]\n}<\/code><\/pre>\n\n\n\n<p>This is a fully spec-compliant <strong>Laravel JSON:API<\/strong> response. And it took less than 30 lines of PHP to produce it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Final_Thoughts\"><\/span><strong>Final Thoughts<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Laravel JSON:API resources remove one of the biggest friction points in API development\u2014inconsistent response formats. Traditionally, developers had to manually shape responses, which often led to differences across endpoints. As a result, maintaining consistency became a challenge over time. However, with <code>JsonApiResource<\/code>, that problem largely disappears. Now, your API speaks a standardized language that any client or frontend framework can understand right out of the box. Because of this, integration becomes smoother, development becomes faster, and your API stays predictable. In other words, instead of reinventing the response structure every time, you follow a clear and consistent standard from the start.<\/p>\n\n\n\n<p>Moreover, features like sparse fieldsets, lazy attribute evaluation, and automatic relationship inclusion make <strong>JsonApiResource Laravel<\/strong> not just spec-compliant \u2014 but genuinely production-ready.<\/p>\n\n\n\n<p>If you are building any kind of API in Laravel 13, this is the right way to do it.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Building APIs in Laravel has always been straightforward. However, getting your API responses to follow a proper specification has usually been a different story. In most cases, that meant either pulling in a third-party package or writing a lot of boilerplate code manually. As a result, things could quickly become messy and inconsistent. Fortunately, Laravel [&hellip;]<\/p>\n","protected":false},"author":5,"featured_media":11477,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_genesis_hide_title":false,"_genesis_hide_breadcrumbs":false,"_genesis_hide_singular_image":false,"_genesis_hide_footer_widgets":false,"_genesis_custom_body_class":"","_genesis_custom_post_class":"","_genesis_layout":"","jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[2564],"tags":[3156,3112,3159,3114,3158,3157,3154,3155],"yst_prominent_words":[652,456],"class_list":{"0":"post-11475","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-laravel","8":"tag-json-api-resource-in-laravel","9":"tag-laravel-13","10":"tag-laravel-13-api-resources","11":"tag-laravel-13-new-features","12":"tag-laravel-api-development-2026","13":"tag-laravel-api-resource","14":"tag-laravel-jsonapi","15":"tag-laravel-jsonapi-resource","16":"entry"},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/programmingfields.com\/wp-content\/uploads\/2026\/05\/JSON-API-Response.png?fit=1672%2C941&ssl=1","jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/programmingfields.com\/wp-json\/wp\/v2\/posts\/11475","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/programmingfields.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/programmingfields.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/programmingfields.com\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/programmingfields.com\/wp-json\/wp\/v2\/comments?post=11475"}],"version-history":[{"count":2,"href":"https:\/\/programmingfields.com\/wp-json\/wp\/v2\/posts\/11475\/revisions"}],"predecessor-version":[{"id":11478,"href":"https:\/\/programmingfields.com\/wp-json\/wp\/v2\/posts\/11475\/revisions\/11478"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/programmingfields.com\/wp-json\/wp\/v2\/media\/11477"}],"wp:attachment":[{"href":"https:\/\/programmingfields.com\/wp-json\/wp\/v2\/media?parent=11475"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/programmingfields.com\/wp-json\/wp\/v2\/categories?post=11475"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/programmingfields.com\/wp-json\/wp\/v2\/tags?post=11475"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/programmingfields.com\/wp-json\/wp\/v2\/yst_prominent_words?post=11475"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}