Make Building MultimodalAI Data Apps Dead Simple
Make Building Multimodal AI Data Apps Dead SimpleOther stacks split the framework, the library, and the service. Pixeltable unifies them the way Next.js and Vercel unified the frontend: the schema is the spec.
Declare → Local → Cloud
schema.pyDeclare the app in Python
Schema, computed columns, indexes, and HTTP routes in one declarative Python file. Same logic for local and cloud.
pxt serveRun locally for fast iteration
pip install and iterate on your laptop. Tables, pipelines, and endpoints without stitching five services.
pxt service createDeploy the same app to cloud
Bind the same file to a hosted database and serve the same endpoints. No rewrite between environments.
Schema-Defined Infrastructure
The table schema is the spec. Storage, compute, indexes, and serving materialize from it — instead of blob storage, a vector database, an orchestrator, and the glue between them.
Subclass TableModel. Every annotation becomes a stored column.
import pixeltable as pxtTableModel = pxt.model_base()class Videos(TableModel, name='videos'):video: pxt.Video # also pxt.Image, pxt.Audio, pxt.Document, pxt.Jsontitle: pxt.String
After this step
videos table
Operational Integrity
The same file governs the app after launch: schema changes arrive as a plan you review, and the runtime owns recompute, failures, media, and concurrency.
Only what changed recomputes
label = label_scene(detections, Videos.description)The dependency graph comes from the assignments, so adding a hundred videos costs a hundred videos of work, not the whole table. Model calls inside a recompute are batched, rate-limited, and retried.
Schema changes ship as a reviewed plan
pxt schema diff schema.py my_appEvery operation is marked safe or DESTRUCTIVE before anything runs. Dropping a column or an index needs --allow-destructive, and in CI a pending change exits 2.
Swap a model beside the old one
EmbeddingIndex(label, string_embed=e5_large)Add the new index to __indexes__ next to the current one. The old index keeps answering queries while the new one backfills; removing it is a separate step you authorize.
Failed rows are data, not exceptions
pxt errors my_app/frames --col labelThe cell stores None plus errortype and errormsg instead of raising, so one unreadable video never fails the batch. Fix the cause and recompute just those rows.
Media stays where it already lives
video: pxt.Video # s3://clips/demo.mp4A media column references your object storage or local disk rather than copying it, so the source of truth never forks and the bill does not double.
Any version is recoverable
pxt revert my_app/frames --steps 3Every insert, update, and schema change is a version. pxt history shows exactly what a revert would undo before you run it.
Long work returns a job handle
background = trueA route that transcodes or transcribes answers immediately with an id and a job_url, then reports done or error when the model calls finish.
Concurrency you never configure
def search(...) # not async defSync handlers get a thread each, with an isolated database connection under SERIALIZABLE isolation and retries on transient conflicts.
Production operationsVersion control and lineageBrowse media and errors with pxt dashboard
Start with one file
Declare once. Run locally. Deploy to cloud. pip install pixeltable
Apache 2.0 and free to self-host. Or paste a schema in the browser.