Pixeltable: computed columns, declared routes, and the app.py contract
The unified multimodal backend for AI data apps. Database, orchestration, and serving in one Python file.
At a glance
- What is it?
- Pixeltable collapses object storage, a vector store, an orchestrator and endpoint code into one Python file. The interesting part is not the pitch but the split between pxt schema update and pxt service update, and what that split assumes about how you deploy.
- Who is it for?
- Adopt Pixeltable if your data is genuinely multimodal and your pipeline is mostly insert, compute, index, serve, because that is the loop the TableModel contract is built around. Do not adopt it if you need ad hoc SQL over a schema that changes every week, or if your serving layer already exists and you only want a feature store; mounting via app.include_router is documented, but the schema lifecycle still belongs to pxt.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem Pixeltable is aimed at, stated as a file count
The README frames the project as a replacement for a stack: object storage, a vector database, an orchestrator, and the endpoint code that copies between them. The claim is that all four collapse into one application file. That is a specific problem statement, not a general one. It applies when your pipeline is a chain of derived artifacts over media: an image goes in, a caption or embedding comes out, a search index is built over the result, and an HTTP route exposes it. In that shape, most of the engineering effort is glue, and the glue is where the bugs live. Pixeltable's answer is to make the derived artifacts columns. A transform is a computed column, an index is a declaration, and an HTTP route is a declaration in the same file. The intended user is a Python developer building an AI data app who does not want to operate four systems to serve one endpoint. The README also names a second audience explicitly: coding agents. There is a get-started.md playbook, an llms-full.txt, a skill.md capability file, and an npx skills add command. That is a deliberate bet that the person writing app.py may not be a person.
Annotations versus assignments, and why the distinction carries the design
The example application file makes a distinction that the rest of the system depends on. A line like doc_id: pxt.Int is an annotation, described in the README as a value you insert. A line like title_upper = pxtf.string.upper(title) is an assignment, described as computed on insert and on update. The same file distinguishes a pxt.udf, a Python function the columns can call, from the columns that call it. So summary = excerpt(title) is a computed column over a function defined three lines above it, in the same file. That is the mechanism: the table is a Python class, the columns are class attributes, and the difference between a stored value and a derived value is the difference between a type annotation and an assignment. The README notes that the same file holds pxt.Image, pxt.Video, pxt.Audio or pxt.Document columns, and that a computed column over one of those is another assignment. It does not spell out what happens when a computed column over a video fails halfway through a batch, and that is the kind of question the README leaves to the linked media-processing use-case page. What is visible is the contract: insert a row and everything below it runs.
The pxt CLI has two verbs that do not do what you expect
The install path is pip install 'pixeltable[serve]', then pxt init, then pxt service example --out app.py to write the starter application. From there the README is unusually explicit about a trap: pxt schema update creates the catalog and its tables but does not start HTTP, and pxt service update starts HTTP but does not create tables. Two commands, two responsibilities, and running only one leaves you with either a schema nobody can reach or a server over tables that do not exist. The same pattern repeats for Cloud. pxt db update creates or updates the hosted database but does not insert rows. And pxt service run is local only, so it cannot target Cloud; the Cloud path is pxt db update pxt://org:mydb, then pxt schema update app.py pxt://org:mydb, then pxt service update app.py pxt://org:mydb. One more detail worth copying rather than paraphrasing: the port is assigned, so the README tells you to read it back with URL=$(pxt service list --json | jq -r '.[0].endpoint') rather than hardcoding it. The curl example posts JSON to $URL/docs and the documented response is {"title_upper":"HELLO","summary":"Hello"}, which is the insert route returning the computed columns named in its outputs list.
The routes are declared, which is a constraint as much as a feature
The ingest router in the example is built with FastAPIRouter(name='ingest'), then ingest.add_insert_route(Docs, path='/docs', inputs=[Docs.doc_id, Docs.title, Docs.body], outputs=[Docs.title_upper, Docs.summary]). A second route, add_compute_route, takes only a title and returns only title_upper. The inputs and outputs are column references, not strings, so the route signature is checked against the table definition rather than against a hand-written Pydantic model. That is the payoff. The cost is that the route is a declaration over a table, not arbitrary handler code. If your endpoint needs branching logic that depends on a request header, or a response shape that is not a projection of columns, you are outside what the example shows. The README does offer an escape hatch for the other direction: to mount the routes on an existing FastAPI app, app.include_router(...). It also offers a way to skip endpoints entirely: run pxt schema update, insert from Python, then export_sql. Those two sentences are the most useful in the README for anyone who already has a service and only wants the compute layer.
The starter kit and the skill version you actually have installed
uvx pixeltable-new myapp copies one app from the starter kit, then cd myapp, uv sync, pxt schema update app.py agent, pxt service update app.py agent. The default copy is a chat app; --video copies video search. The README gives two operational notes that matter more than the copy step. First, inserting into the knowledge table needs no API key, but the /ask route needs ANTHROPIC_API_KEY. Second, and this is the one to read twice: skill 2.8.0 and later writes a TableModel in app.py, and if your agent emits create_table in application code, the installed skill is stale and should be reinstalled. The README adds that notebooks and tests still use pxt.create_table(), while an app puts tables in app.py and creates them with pxt schema update. So there are two idioms in circulation, they look similar, and the difference is whether the code is a script or an application. If you follow a tutorial written against the older idiom and then run pxt schema update, the mismatch will not be obvious from the error.
Where the single-file model stops being the right answer
The design assumes your schema is stable enough to live in a class and be applied with a command. That is a real assumption. If your team's workflow is ad hoc SQL against a schema that changes weekly, the TableModel contract is friction, and the export_sql path exists precisely because some users want the tables without the serving layer. A second limitation is visible in the command split itself. pxt service run is local only per the README, so the local development loop and the Cloud deployment loop are not the same command sequence, and you should not assume a service that works locally will be promoted by changing a URI. A third is that the README's example covers string columns and a trivial udf. It links to media pipelines and RAG pages rather than showing them, so anyone evaluating video or document columns is evaluating the linked documentation, not the README. And Pixeltable Cloud, which is how the hosted path is delivered, is described at the top of the README as in Limited Beta, with an email address for interest. That is a gate, not a footnote.
Compared with assembling Postgres, an orchestrator and a vector store
The alternative most readers already run is Postgres plus a vector extension, plus something like Airflow or Prefect for the compute graph, plus a FastAPI service that copies rows between them. The difference is not performance; it is where the dependency graph lives. In the assembled stack, the graph lives in the orchestrator, the schema lives in migrations, and the serving code lives in a third place, and nothing enforces that the three agree. In Pixeltable, the graph is the column definitions, the schema is the class, and the routes reference columns by attribute, so the three are the same artifact. That is a genuine reduction in surface area, and it is also a loss of independence: you cannot swap the orchestrator without touching the table definition. The README's own framing of the trade is the list of things that collapse, which is honest about what you are giving up. If your compute graph is genuinely heterogeneous, with steps that are not per-row derivations, the column model will not describe it and you will be fighting the abstraction rather than using it.
Apache 2.0, the release cadence, and what to check before you commit
The licence is Apache 2.0, stated in the README and in the repository metadata, which permits commercial use and modification; the usual obligations around notices and attribution apply, and this is not legal advice. The recent release list shows v0.7.6, v0.7.5 and v0.7.4 within roughly a week of each other in early September 2026, and the repository metadata shows the last push on 2026-09-10. That cadence is worth noting when you pin a version: a fast-moving 0.x line means upgrade cost is a real line item, not a theoretical one, and the README's own warning about skill 2.8.0 versus older skills is an example of a version boundary that changes generated code rather than just fixing a bug. Before adopting, check the version of the skill your agent has installed, confirm whether your deployment target is local or Cloud (since pxt service run cannot reach Cloud), and decide whether the schema-as-class model fits a schema you expect to keep stable. The REST of the evaluation is in the linked pages, not the README.
Editorial conclusion
Adopt Pixeltable if your data is genuinely multimodal and your pipeline is mostly insert, compute, index, serve, because that is the loop the TableModel contract is built around. Do not adopt it if you need ad hoc SQL over a schema that changes every week, or if your serving layer already exists and you only want a feature store; mounting via app.include_router is documented, but the schema lifecycle still belongs to pxt. Verify three things before committing: that your installed skill is 2.8.0 or later so your agent emits a TableModel rather than create_table, that your deployment does not need pxt service run against Cloud (the README says it is local only), and that you are willing to read the assigned port back from pxt service list --json instead of hardcoding it.
Community notes