---
title: "API resources — Comments for Laravel | Roundly"
description: "Return comments as JSON with CommentResource — nested replies, author and status, plus likes and attachments when loaded."
url: https://roundly-consulting.com/open-source/docs/comments-for-laravel/api-resources
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Comments for Laravel](https://roundly-consulting.com/open-source/docs/comments-for-laravel.md)

# API resources

Drop-in JSON for SPA and mobile backends:

```php
use RoundlyConsulting\Comments\Http\Resources\CommentResource;

return CommentResource::collection(
    $post->threadedComments()->with(['actor', 'likes', 'media'])->get(),
);
```

| Field | Content |
| --- | --- |
| `id` | The comment id. |
| `body` | The comment body. |
| `status` | pending, approved or hidden. |
| `visible` | The visibility flag. |
| `parent_id` | The parent comment id, or null. |
| `locked` | Whether the comment’s own thread lock is set. |
| `author` | { id, type } of the actor through comments.resources.author — type is the class basename (User); null when the author was deleted — only when actor is loaded. |
| `likes` | { count, viewer\_state, breakdown } — only when likes is loaded. |
| `attachments` | \[{ id, url }\] (media UUID + URL, signed for private attachments) — only when media is loaded. |
| `replies` | Nested CommentResource items — only when replies are loaded. |
| `created_at / updated_at` | ISO 8601 timestamps. |

threadedComments() is the public thread, so the JSON holds only visible + approved roots and replies. The resource renders whatever it is given — the replies key is any loaded replies relation — so on public endpoints feed it a public query: threadedComments(), or a visible() CommentQuery with withReplies().

The likes payload is rendered without a viewer, so viewer\_state is the guest state — for per-viewer data use withLikedState($viewer) on the query or likeState($viewer). Attachment URLs are resolved by each attachment’s visibility — signed and short-lived for private ones.

The resource leaves out meta, because metadata often holds internal flags. CommentResource is final — to expose meta, render comments through a resource of your own.

A comment loaded with actor and replies renders:

```json
{
  "id": 7,
  "body": "Great post!",
  "status": "approved",
  "visible": true,
  "parent_id": null,
  "locked": false,
  "author": { "id": 42, "type": "User" },
  "replies": [],
  "created_at": "2026-10-18T09:30:00+00:00",
  "updated_at": "2026-10-18T09:30:00+00:00"
}
```

author renders through CommentAuthorResource: the key and the class basename, never the stored actor\_type — without a morph map that is the full class name (App\\Models\\User), and no PHP class name reaches an API client. type is the basename with or without a morph map; an alias such as member isn’t rendered either.

## Your own author shape

Point comments.resources.author at a JsonResource subclass of your own. It receives the actor model, for top-level comments and nested replies alike — load replies.actor for the replies’ authors:

```php
// config/comments.php
'resources' => [
    'author' => App\Http\Resources\AuthorResource::class,
],

// app/Http/Resources/AuthorResource.php
final class AuthorResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->resource->getKey(),
            'name' => $this->resource->name,
            'avatar_url' => $this->resource->avatar_url,
        ];
    }
}
```

If more than one model type writes comments, branch on the model inside your resource. A blank or null value is the shipped CommentAuthorResource; anything that is not an instantiable JsonResource subclass throws an InvalidConfigurationException naming the key.

## Collections & pagination

CommentCollection collects CommentResource and works with paginated results too:

```php
use RoundlyConsulting\Comments\Facades\Comments;
use RoundlyConsulting\Comments\Http\Resources\CommentCollection;

return new CommentCollection(
    Comments::for($post)->visible()->rootsOnly()->withReplies()->paginate(20),
);
```

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/comments-for-laravel.md)
