Features

Workflow & Review Queues

The workflow module adds multi-stage content approval to any collection. Documents flow through configurable stages (e.g., draft, editorial review, legal check, published), with support for reviewer assignment, approval/rejection tracking, and automatic stage advancement.

Server-side execution

Workflow functions run inside Convex mutations and queries. Mutations are transactional -- if a workflow action fails, nothing is written. See Key Concepts for background.


Setting up workflows

1. Define workflow stages

Each collection with workflows needs a WorkflowCollectionConfig that lists the custom stages between draft and published:

import { createVextroWorkflowModule } from "vextro/convex/workflow";

const workflow = createVextroWorkflowModule({
  query,
  mutation,
  components,
  getCurrentUser,
  requireAdminRead,
  requireAdminWrite,
  workflowConfigs: [
    {
      collectionSlug: "articles",
      enabled: true,
      stages: [
        {
          name: "editorial-review",
          label: "Editorial Review",
          color: "blue",
          requiredRole: "editor",
          autoTransition: { afterApprovals: 2 },
        },
        {
          name: "legal-check",
          label: "Legal Check",
          color: "orange",
          requiredRole: "legal",
          autoTransition: { afterApprovals: 1 },
        },
      ],
    },
  ],
});

The full stage flow is always: draft → your custom stages in order → published. The draft and published stages are implicit -- you only define the stages in between.

2. Export the workflow functions

Expose the workflow module from your Convex functions file:

// convex/workflow.ts
export const {
  getWorkflowState,
  listReviewQueue,
  getWorkflowHistory,
  getWorkflowConfig,
  getReviewQueueCounts,
  submitForReview,
  approveDocument,
  rejectDocument,
  assignReviewer,
  advanceWorkflow,
  recallFromReview,
} = workflow;

3. Add the review queue page

Vextro includes a built-in review queue page that shows documents awaiting review, with stage filter tabs, assignment filters, and quick action buttons:

---
import VextroReviewQueuePage from "vextro/pages/VextroReviewQueuePage.astro";
---

<VextroReviewQueuePage
  convex={convex}
  api={api}
  basePath="/admin"
/>

Stage configuration

OptionTypeRequiredDefaultDescription
namestring*Unique identifier for the stage (used in URLs, queries, history)
labelstring*Human-readable label shown in the admin UI
colorstringStage pill color in the review queue UI
requiredRolestringRole required to approve at this stage
autoTransitionobjectAuto-advance configuration
autoTransition.afterApprovalsnumberNumber of approvals required to auto-advance to the next stage

Collection config

OptionTypeRequiredDefaultDescription
collectionSlugstring*The collection this workflow applies to
enabledboolean*Whether the workflow is active for this collection
stagesWorkflowStageConfig[]*Ordered list of custom stages between draft and published

Workflow lifecycle

Status transitions

draft ──submit──► stage-1 ──approve──► stage-2 ──approve──► published
  ▲                  │                    │
  └──────reject──────┘                    │
  └──────reject───────────────────────────┘
  └──────recall───────────────────────────┘
  • Submit: Moves a document from draft to the first custom stage. Creates or resets the workflow entry.
  • Approve: Records an approval on the current stage. If autoTransition.afterApprovals is configured and met, the document auto-advances to the next stage.
  • Reject: Moves the document back to draft with a required reason. Clears approvals.
  • Advance: Manually moves a document to the next stage (regardless of approvals).
  • Recall: Author recalls a document back to draft from any stage.
  • Publish: When a document advances past the last custom stage, the workflow entry is deleted and the document reaches published.

Auto-advance

When a stage has autoTransition.afterApprovals set, reaching that number of unique approvals automatically advances the document. Each user can only approve once per stage -- duplicate approvals are rejected.


Queries

getWorkflowState

Returns the current workflow entry for a document, or null if the document is not in a workflow.

const state = await ctx.runQuery(api.workflow.getWorkflowState, {
  documentId: "abc123",
});
// { _id, documentId, collectionSlug, stage, assignedTo, approvals, rejections, ... }

listReviewQueue

Lists documents in the review queue with optional filters. Results are enriched with document titles and collection labels.

// All review items
const queue = await ctx.runQuery(api.workflow.listReviewQueue, {});

// Filter by stage
const inReview = await ctx.runQuery(api.workflow.listReviewQueue, {
  stage: "editorial-review",
});

// Filter by assignee
const myQueue = await ctx.runQuery(api.workflow.listReviewQueue, {
  assignedTo: userId,
});

// Filter by collection and stage
const articles = await ctx.runQuery(api.workflow.listReviewQueue, {
  collectionSlug: "articles",
  stage: "legal-check",
  limit: 20,
});

getWorkflowHistory

Returns the history of workflow actions for a document, ordered newest first.

const history = await ctx.runQuery(api.workflow.getWorkflowHistory, {
  documentId: "abc123",
  limit: 20,
});
// [{ action, fromStage, toStage, userId, userName, comment, timestamp }, ...]

getWorkflowConfig

Returns the workflow configuration for a collection, or null if workflows are not enabled.

const config = await ctx.runQuery(api.workflow.getWorkflowConfig, {
  collectionSlug: "articles",
});

getReviewQueueCounts

Returns aggregate counts for the review queue -- total items, items assigned to the current user, and per-stage breakdowns.

const counts = await ctx.runQuery(api.workflow.getReviewQueueCounts, {});
// { total: 12, assignedToMe: 3, byStage: { "editorial-review": 7, "legal-check": 5 } }

Mutations

submitForReview

Submits a document for review. Creates a new workflow entry or resets an existing one to the first stage.

const entryId = await ctx.runMutation(api.workflow.submitForReview, {
  documentId: "abc123",
  collectionSlug: "articles",
  assignTo: reviewerUserId, // optional
  comment: "Ready for editorial review", // optional
});

approveDocument

Records an approval for the current stage. Returns whether the document auto-advanced.

const result = await ctx.runMutation(api.workflow.approveDocument, {
  documentId: "abc123",
  comment: "Looks good", // optional
});
// { advanced: true, newStage: "legal-check" }
// or { advanced: false, newStage: "editorial-review" } if more approvals needed

A user cannot approve the same document twice at the same stage. Attempting to do so throws an error.

rejectDocument

Rejects a document and moves it back to draft. A reason is required.

await ctx.runMutation(api.workflow.rejectDocument, {
  documentId: "abc123",
  reason: "Needs revision -- factual errors in section 3",
});

assignReviewer

Assigns a specific user as the reviewer for a document in the workflow.

await ctx.runMutation(api.workflow.assignReviewer, {
  documentId: "abc123",
  assignTo: reviewerUserId,
});

advanceWorkflow

Manually advances a document to the next stage, bypassing approval requirements.

const result = await ctx.runMutation(api.workflow.advanceWorkflow, {
  documentId: "abc123",
  comment: "Fast-tracked by admin", // optional
});
// { newStage: "legal-check" }

When advancing to published (past the final custom stage), the workflow entry is deleted. The history is preserved.

recallFromReview

Recalls a document back to draft from any stage. Clears approvals and reviewer assignment.

await ctx.runMutation(api.workflow.recallFromReview, {
  documentId: "abc123",
  comment: "Pulling back for major edits", // optional
});

Convex tables

The workflow module uses two tables:

TablePurpose
adminWorkflowEntriesActive workflow state for each document (stage, approvals, rejections, assignment)
adminWorkflowHistoryImmutable audit log of all workflow actions

These tables are created automatically when you use the Vextro Convex component.


Review queue UI

The built-in VextroReviewQueuePage provides:

  • Summary cards showing total items, items assigned to you, and per-stage counts
  • Stage filter tabs to view items at a specific workflow stage
  • Assignment filter to see only items assigned to you
  • Document table with collection label, stage pill, assigned reviewer, and last-updated timestamp
  • Quick actions -- approve or open a document directly from the queue

The page accepts stage and assigned URL query parameters for deep linking to filtered views.

Previous
Notifications