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:

TypeWire valueDashboard labelWho can see it
Personal viewpersonal_viewPersonal (only you)Only the moderator who created it, plus users who can manage queues.
Operational queueoperational_queueShared (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

$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]],
));

Request Parameters

KeyRequiredTypeDescription
nametruestringDisplay name for the view. Max 255 characters.
typetruestringpersonal_view or operational_queue.
descriptionfalsestringFree-text description shown alongside the view. Max 512 characters.
filtersfalseobjectReview queue filter conditions. Accepts the same keys as the filter object on Query Review Queue.
sortfalsearraySort parameters. Supported fields: id, created_at, updated_at.
user_idfalsestringThe acting moderator. Required on server-side requests, ignored on client-side requests.

Response

KeyTypeDescription
queueobjectThe 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

KeyRequiredTypeDescription
user_idfalsestringThe acting moderator, passed as a query parameter. Required on server-side requests.

Response

KeyTypeDescription
queuesarrayViews 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

KeyRequiredTypeDescription
idtruestringThe view ID, in the path.
user_idfalsestringThe 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

KeyRequiredTypeDescription
idtruestringThe view ID, in the path.
namefalsestringNew display name. Max 255 characters.
descriptionfalsestringNew description. Max 512 characters.
filtersfalseobjectReplaces the stored filters entirely. This is not a per-key merge.
sortfalsearrayReplaces the stored sort order entirely.
user_idfalsestringThe 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

KeyRequiredTypeDescription
idtruestringThe view ID, in the path.
user_idfalsestringThe 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:

KeyWhy it is dropped
reviewedControlled at read time by the Inbox and Reviewed tabs, so it has no meaning as stored state.
archived_atAlso a read-time toggle, for the same reason.
date_rangeA 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

ConstraintValue
Views per app10 by default, counting personal views and operational queues together. Contact support to raise it.
Create or read a personal viewRequires the QueryModerationReviewQueue permission.
Create, update, delete a shared queueRequires 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.