$response = $client->moderation()->createQueue(new CreateQueueRequest(
name: 'Flagged images',
type: 'personal_view',
description: 'Unreviewed image content flagged by the AI image engine',
userID: 'moderator-1',
filters: (object)[
'entity_type' => 'stream:chat:v1:message',
'has_image' => true,
'category' => 'ai_image',
],
sort: [['field' => 'created_at', 'direction' => -1]],
));Custom Views
Overview
A custom view is a saved set of review queue filters and sort order, stored server-side under a name. Instead of re-applying the same filters at the start of every shift, a moderator opens the view and lands on exactly the slice of the queue they own.
Views come in two kinds:
| Type | Wire value | Dashboard label | Who can see it |
|---|---|---|---|
| Personal view | personal_view | Personal (only you) | Only the moderator who created it, plus users who can manage queues. |
| Operational queue | operational_queue | Shared (all moderators) | Every moderator in the app. |
Personal views serve one moderator's own workflow. A moderator who owns image review keeps a view filtered to has_image: true with category: ai_image, while someone working appeals keeps one on appeal_status: submitted. Neither has to rebuild those filters at the start of a shift.
Operational queues serve team structure. A Trust & Safety lead defines "User-reported harassment" (reporter_type: user, label: harassment) and "Automod removals to spot-check" (reporter_type: automod, recommended_action: remove) once, and the whole team works out of the same two surfaces instead of each moderator filtering by hand.
Custom views are in beta and require moderation v2 to be enabled on your app. The endpoints below are the same ones the Stream dashboard uses.
Managing Views in the Dashboard
Most teams never call these endpoints directly. Open the Stream Dashboard, go to Moderation > Content Queue, apply the filters you want, then save them as a view.
The save dialog asks for a name and a visibility: Personal (only you) or Shared (all moderators). The shared option is only selectable for users whose role can manage operational queues.
Saved views appear in the sidebar with an item count next to each one.
Create a View
Creating a view stores the filters, sort, name, and description. The type field decides whether the view is private to the caller or shared with the team.
POST /api/v2/moderation/queues
Request Parameters
| Key | Required | Type | Description |
|---|---|---|---|
| name | true | string | Display name for the view. Max 255 characters. |
| type | true | string | personal_view or operational_queue. |
| description | false | string | Free-text description shown alongside the view. Max 512 characters. |
| filters | false | object | Review queue filter conditions. Accepts the same keys as the filter object on Query Review Queue. |
| sort | false | array | Sort parameters. Supported fields: id, created_at, updated_at. |
| user_id | false | string | The acting moderator. Required on server-side requests, ignored on client-side requests. |
Response
| Key | Type | Description |
|---|---|---|
| queue | object | The created view, including its generated id. |
List Views
Returns every view visible to the calling moderator: their own personal views, plus all operational queues. Users whose role can manage operational queues additionally see other moderators' personal views, so views left behind by a departed moderator stay manageable.
GET /api/v2/moderation/queues
$response = $client->moderation()->listQueues('moderator-1');Request Parameters
| Key | Required | Type | Description |
|---|---|---|---|
| user_id | false | string | The acting moderator, passed as a query parameter. Required on server-side requests. |
Response
| Key | Type | Description |
|---|---|---|
| queues | array | Views visible to the calling moderator, each with its item_count. |
Get a View
GET /api/v2/moderation/queues/{id}
$response = $client->moderation()->getQueue('queue-id', 'moderator-1');Request Parameters
| Key | Required | Type | Description |
|---|---|---|---|
| id | true | string | The view ID, in the path. |
| user_id | false | string | The acting moderator, passed as a query parameter. |
Update a View
Updates are partial: omitted fields are left unchanged. The type field cannot be changed after creation. To move a personal view to a shared queue, create a new operational queue with the same filters and delete the old one.
PATCH /api/v2/moderation/queues/{id}
$response = $client->moderation()->updateQueue('queue-id', new UpdateQueueRequest(
name: 'Flagged images (EU)',
userID: 'moderator-1',
filters: (object)[
'entity_type' => 'stream:chat:v1:message',
'has_image' => true,
'category' => 'ai_image',
'label' => 'nudity',
],
));Request Parameters
| Key | Required | Type | Description |
|---|---|---|---|
| id | true | string | The view ID, in the path. |
| name | false | string | New display name. Max 255 characters. |
| description | false | string | New description. Max 512 characters. |
| filters | false | object | Replaces the stored filters entirely. This is not a per-key merge. |
| sort | false | array | Replaces the stored sort order entirely. |
| user_id | false | string | The acting moderator. Required on server-side requests. |
A personal view can only be updated by the moderator who created it, or by a user whose role can manage operational queues.
Delete a View
Deleting a view is a soft delete: the view stops appearing in listings. Review queue items are never touched, because a view is only a saved filter over them.
Note that delete is a POST, not an HTTP DELETE.
POST /api/v2/moderation/queues/{id}/delete
$response = $client->moderation()->deleteQueue('queue-id', new DeleteQueueRequest(
userID: 'moderator-1',
));Request Parameters
| Key | Required | Type | Description |
|---|---|---|---|
| id | true | string | The view ID, in the path. |
| user_id | false | string | The acting moderator. Required on server-side requests. |
As with updates, a personal view can only be deleted by its creator or by a user who can manage operational queues.
Filters
The filters object accepts the same keys as the filter object on Query Review Queue, including entity_type, entity_creator_id, category, label, recommended_action, reporter_type, has_image, and appeal_status. See that page for the full list.
Note the field name is filters here, plural, while Query Review Queue takes a singular filter.
Three keys are dropped when a view is saved, even if you send them:
| Key | Why it is dropped |
|---|---|
reviewed | Controlled at read time by the Inbox and Reviewed tabs, so it has no meaning as stored state. |
archived_at | Also a read-time toggle, for the same reason. |
date_range | A saved absolute date range goes stale immediately. Pick the range when you open the view instead. |
This is why a view saved from the Reviewed tab still opens on Inbox. The tab is not part of what gets stored.
Item Counts
Each view carries an item_count. Counts are computed by a background task and served from cache, so a freshly created view returns a count computed inline on first read, while an existing view's count can lag behind the live queue by a short interval.
Treat item_count as an indicator of queue depth, not as a precise number to reconcile against.
Limits and Permissions
| Constraint | Value |
|---|---|
| Views per app | 10 by default, counting personal views and operational queues together. Contact support to raise it. |
| Create or read a personal view | Requires the QueryModerationReviewQueue permission. |
| Create, update, delete a shared queue | Requires the ManageOperationalQueues permission. |
Creating a view past the per-app cap returns a 400 naming the limit. Attempting to modify someone else's personal view without permission to manage queues returns a 403.