Convex Developers' Guide to Pixeltable: Queries, Mutations, Actions, and Where the Mental Models Diverge
All Stories
2026-06-2014 min read
ConvexFull-StackDeveloper ExperienceAI InfrastructurePythonDeclarativeQueriesMutationsArchitecture

Convex Developers' Guide to Pixeltable: Queries, Mutations, Actions, and Where the Mental Models Diverge

If you know Convex (queries, mutations, actions, and reactive subscriptions), this guide maps those primitives onto Pixeltable. We use the help-desk mental model from Database School's Convex course as a parallel walkthrough, with side-by-side code and an honest cheat sheet for full-stack developers building AI-heavy backends.

Pierre Brunelle

Pierre Brunelle

Pixeltable Team

Summary: Convex gives full-stack developers a reactive TypeScript backend: queries read, mutations write in transactions, actions call the outside world. Pixeltable gives AI developers a declarative data plane: tables, computed columns, embedding indexes, and @pxt.query functions. This post maps Convex primitives onto Pixeltable using the help-desk app from Database School's Convex course and the Convex docs overview. It is a translation guide, not a "replace Convex" pitch, structured so we can later extract sections into official docs.

Two Stacks, Same Shape, Different Center of Gravity#

Convex's architecture is three layers: a document-relational database, server functions (query / mutation / action), and client libraries that keep React in sync via WebSockets. Pixeltable's architecture is also three layers, but the guarantees differ:

  • Convex keeps your frontend and backend consistently reactive: when data changes, subscribed queries rerun and the UI updates.
  • Pixeltable keeps your data and AI transformations consistently derived: when source data changes, computed columns and embedding indexes update incrementally.

Convex is an excellent application coordination layer for TypeScript full-stack apps. Pixeltable is an excellent AI data layer for multimodal storage, model orchestration, and retrieval. Many production systems need both; AI-heavy backends often reimplement Convex's query/mutation/action split in Python glue. Pixeltable collapses the data + AI side into the schema.

For a feature matrix against Supabase and Convex, see our Compare AI backends page. For the broader "schema IS infrastructure" thesis, see Schema-Driven Infrastructure.

1. Database Layer#

Convex is a document-relational database: JSON-like documents in tables, relations via v.id() references, validators at the boundary, and ACID transactions with serializable isolation. Pixeltable is a multimodal table store: typed columns, computed expressions, and native media types.

ConvexPixeltableNotes
defineSchema + validatorspxt.create_table({ ... })Types enforced at insert boundary
Documents + v.id() relationsTables + ID columns (workspace_id, ticket_id)Relational model; unbounded collections (messages) get their own table
v.union status enumpxt.String + validation in write pathState machines enforced in mutation-equivalent code
Reactive query invalidationIncremental recomputationDerived data stays fresh; not live WebSocket push
File storage IDspxt.Image, pxt.Video, pxt.DocumentMedia is first-class in the schema

Parallel walkthrough: help-desk data model#

Database School's help desk models workspaces (tenants), customers, tickets, and messages as separate tables, not embedded arrays. That lesson applies directly in Pixeltable:

python

Model multi-tenancy early via workspace_id on every tenant-scoped table. That is the same advice Aaron gives in the Convex course, applied here with explicit columns instead of Convex validators.

2. Server Functions: Queries, Mutations, Actions#

This is the core mapping. Convex splits backend logic into three function types with strict rules. Pixeltable splits logic into reads, writes, and derived computation, with a different default for external side effects.

Queries (read-only)#

Convex queries are pure TypeScript: they read the database, cannot call the network, and their results can be subscribed to via useQuery. Pixeltable reads are table expressions or @pxt.query functions exposed over HTTP.

ConvexPixeltable
query({ handler: (ctx) => ctx.db.query("tickets").withIndex(...).collect() })tickets.select(...).where(...).order_by(...).collect()
Cached + live-subscribableRead-only by convention; serve via FastAPIRouter or pxt serve query
Screen-specific BFF query (getThread)@pxt.query for single-table reads; plain Python for multi-table assembly

Example: list open tickets by workspace (index-backed read)

typescript
python

Convex explicitly chooses an index with withIndex. Pixeltable filters and sorts on columns; for vector paths you add add_embedding_index and use .similarity() instead.

Mutations (transactional writes)#

Convex mutations are transactions: the entire handler commits or rolls back, with serializability and optimistic concurrency control. Pixeltable writes are insert, update, and delete on tables, with versioning and incremental recompute for downstream computed columns.

ConvexPixeltable
mutation({ handler: (ctx) => ctx.db.patch(...) })t.insert([...]), t.update(...), t.delete(...)
Automatic OCC retries on conflictServer-side guards + versioning
State machine in mutationValidate transition in Python before update; insert system message in same logical batch
Denormalized search row in same mutationComputed search_text column updates automatically on insert

Example: assign ticket with concurrent-write guard

typescript
python

Convex encodes the product decision inside the mutation and relies on OCC so two simultaneous assigns serialize cleanly. In Pixeltable you enforce the same rule in application code (or a @pxt.udf wrapper) before writing. The lesson from Database School still applies: do not trust the client with business rules.

Actions (external side effects)#

Convex actions call the network (OpenAI, email, webhooks) and must call a mutation to persist results. Pixeltable often collapses this split: external model calls live in computed columns, and results persist automatically when rows change.

ConvexPixeltable
action → OpenAI, Resend, webhooks@pxt.udf or computed column calling openai.* / huggingface.*
Must call mutation to persistComputed column persists on insert/update automatically
scheduler.runAfter(0, internalAction)Incremental engine runs after commit; no manual scheduler for derived columns
internalActionNon-exposed UDF / query; FastAPIRouter controls public HTTP surface

Key insight for Convex developers: many Convex "actions" become Pixeltable computed columns. The durable write and the side effect are declared together, not split across action → mutation.

Example: generate embeddings on ticket create

typescript
python

See Incremental Embedding Indexes for why this matters at scale. Our compare hub walks through the same RAG pipeline in ~50 lines on Pixeltable vs hundreds on Convex + external compute. For a runnable side-by-side multimodal pipeline (Convex action + scheduler + external compute vs Pixeltable computed columns), see the platform-comparison repo.

BFF query: getThread#

Database School's ticket detail screen uses a single getThread query that assembles workspace, ticket, customer, and messages server-side: backend-for-frontend without a separate BFF layer. In Pixeltable, use @pxt.query for single-table reads and a plain Python function for multi-table assembly (the same split Convex uses with getThread plus internalQuery helpers):

python
python

The frontend gets one round trip with the exact shape it needs. Single-table slices stay reusable via @pxt.query; multi-table screens compose them in plain Python at the serving layer.

3. Indexes and Access Patterns#

ConvexPixeltable
.index("by_workspace", ["workspaceId", "updatedAt"])Filter/sort columns; compound access via .where().order_by()
withSearchIndex on denormalized fieldComputed search_text + string or embedding index
vectorIndex + manual embed actionadd_embedding_index(): embed + index incrementally

Convex compound indexes follow the left-prefix rule: an index on [A, B, C] serves queries on A, A+B, and A+B+C. In Pixeltable, express the same access path as filters and sorts on those columns. For semantic search over tickets, use search_text.similarity(string=query) after adding an embedding index.

4. Client Layer: The Honest Gap#

Convex's useQuery is a major developer-experience win: live subscriptions, consistent multi-query snapshots, and no manual WebSocket wiring. Pixeltable does not ship a React hook. You serve data over HTTP and let the frontend poll, use SSE, or wrap endpoints in a Next.js API route.

ConvexPixeltable
useQuery WebSocket subscriptionHTTP via pxt serve / FastAPIRouter / Pixeltable Cloud
Optimistic updates built into clientFrontend concern; not in the data layer
All subscribed queries update at same logical timeConsistent per query; app composes multi-fetch

Bridge pattern: keep Convex (or Next.js) for reactive UI and auth; call Pixeltable for AI retrieval and multimodal pipelines. See our PixelAssist retrospective for a real Next.js → data-layer split.

5. Production Patterns (Database School Module 5)#

Course lessonConvexPixeltable
Cron jobscrons.intervalExternal cron (e.g. Vercel) hitting API; or insert-triggered incremental compute
Durable workflowsConvex WorkflowsComputed column DAG; see dependency graph
Rate limitsRate limiter componentApp-side rate limiting / serve middleware
Vector searchvectorSearch actioncolumn.similarity(string=query)
AI suggest replyAction + rate limit + OpenAI@pxt.query RAG over ticket + similar tickets via embedding index

6. Cheat Sheet (docs-ready)#

Convex primitivePixeltable equivalentWhen to use which
query@pxt.query / select().collect()Convex for live UI reads; Pixeltable for AI retrieval reads
mutationinsert / update / deleteBoth for durable writes; Convex adds OCC + live invalidation
actionComputed column or @pxt.udfPixeltable when side effect is derived data (embed, transcribe, classify)
schedulerIncremental engine (derived) or external cron (app)Pixeltable for pipeline steps; cron for calendar-time jobs
Schema / validatorscreate_table + add_computed_columnPixeltable schema also declares AI compute
IndexColumn filters + add_embedding_indexPixeltable for semantic / multimodal search
BFF query@pxt.query or plain Python for multi-table BFFSame pattern: compose reads server-side
Auth / tenancyworkspace_id + serve-layer authConvex stronger for app auth; Pixeltable for data-plane isolation
Live UI (useQuery)HTTP servingConvex wins for reactive TypeScript apps

7. When to Use Convex, Pixeltable, or Both#

Choose Convex when#

  • You are building a TypeScript full-stack app with live-updating UI
  • Auth, roles, invitations, and optimistic updates are core product features
  • Your backend is mostly CRUD + coordination, not heavy ML pipelines

Choose Pixeltable when#

  • Your backend is an AI data plane: embeddings, transcription, classification, multimodal RAG
  • You need incremental recomputation when source data changes, not manual backfills
  • You want one schema to declare storage, compute, indexes, and serving (transformations in the schema)

Use both when#

  • Convex handles reactive ticket UI, auth, and real-time presence
  • Pixeltable handles ticket embeddings, similar-ticket search, attachment transcription, and agent-suggested replies
  • The Convex action calls Pixeltable's HTTP query endpoint for retrieval; each system does what it is best at

Learn More#

Ready to Build?

Declarative. Multimodal. Incremental.

Focus on innovation, not infrastructure.