Skip to content

The content backend for everything Convex.

Define your collections in TypeScript. Vex generates the Convex schema and types. Your app gets typed queries. Your clients get a real-time admin panel. No API layer. No second database.

pnpm create vexcms@alpha
1
command to a running CMS

pnpm create vexcms@alpha scaffolds Next.js, Convex, auth, and the admin panel in one step

0
API layers to maintain

Convex is the database. No connection string, no REST or GraphQL tier in between

12
field types

text, url, number, checkbox, select, date, color, upload, relationship, group, array, blocks

8
packages on npm

core, react, next, cli, better-auth, file-storage-convex, richtext-plate, create-vexcms

One config. The whole backend.

Your collection definitions are the only place you write your content model. The Convex tables, the TypeScript types, the Zod validators, the admin forms, and the access rules all come from them.

Schema and types, generated

vex dev watches your collections and writes the Convex schema, the TypeScript interfaces, and the Zod validators. You never hand-maintain schema.ts again.

Typed reads, ready to use

The packages export find, get, create, update, and remove for both the server and the client. Call them inside your own Convex functions, or through the local API from a server component.

Types all the way to the component

Fields, relationships, and return shapes are checked from the table to the JSX. Rename a field and the compiler hands you every call site.

An admin panel you can hand to a client

It is a route in your own app, not a dashboard on someone else's domain. Every list view is a live Convex subscription. Rows and totals update as people work.

Access control that runs in the query

Rules per document and per field. Constraints compile to a withIndex range inside the Convex query. Per-call overrides and an anonymous role are built in.

Page building from typed blocks

A blocks field composes typed content blocks into a discriminated union. Each block pairs one config with one React renderer. This page is built from them.

You write the collection. Vex writes the schema.

On the left, a blog's Posts collection, written once by hand. On the right, the Convex table vex dev emits from it. Validators, ids, and indexes included. Never edited by hand.

You write
ts
export const posts = defineCollection({
  slug: "posts",
  admin: { useAsTitle: "title", icon: "Newspaper" },
  fields: {
    title: text({ label: "Title", required: true }),
    slug: text({ label: "Slug", required: true, index: "by_slug" }),
    excerpt: text({ label: "Excerpt" }),
    featured: checkbox({ label: "Featured" }),
    readingMinutes: number({ label: "Reading Minutes" }),
    coverImage: upload({ to: "images", label: "Cover Image" }),
    author: relationship({ label: "Author", collection: { slug: "authors" } }),
    tags: array({ label: "Tags", items: text({ label: "Tag" }) }),
    seo: group({
      label: "SEO",
      fields: {
        metaTitle: text({ label: "Meta Title" }),
        metaDescription: text({ label: "Meta Description" }),
      },
    }),
  },
  labels: { singular: "Post", plural: "Posts" },
})
Vex generates
ts
// ⚠️ AUTO-GENERATED BY VEX CMS — DO NOT EDIT ⚠️
// Run 'vex dev' or 'vex generate' to update this file.

import { defineTable } from "convex/server"
import { v } from "convex/values"

export const posts = defineTable({
  title: v.string(),
  slug: v.string(),
  excerpt: v.optional(v.string()),
  featured: v.optional(v.boolean()),
  readingMinutes: v.optional(v.number()),
  coverImage: v.optional(v.array(v.id("images"))),
  author: v.optional(v.array(v.id("authors"))),
  tags: v.optional(v.array(v.string())),
  seo: v.optional(
    v.object({ metaTitle: v.optional(v.string()), metaDescription: v.optional(v.string()) })
  ),
})
  .index("by_slug", ["slug"])
  .index("by_author", ["author"])

Built on Convex

Everything Convex does, your CMS does too.

VexCMS is built for Convex and nothing else. That is the trade. In return, none of the guarantees below are features we had to build. They are Convex's, and they come with the install. Every one is documented on convex.dev.

  • Live queries. Every read is a subscription, so edits land in every open client. Nothing to poll, no cache to invalidate.
  • ACID transactions. A mutation that touches five documents either lands completely or not at all.
  • Scheduling and crons. Publish at a time, expire a banner, reindex overnight.
  • File storage. The media library is Convex storage sitting behind an upload field.
  • Full-text and vector search over your content, from the same backend that stores it.
  • Convex's security and compliance posture, inherited whole rather than re-implemented.
ts
import { find } from "@vexcms/core/server"

import { query } from "./_generated/server"

// An ordinary Convex query. Because it is an ordinary Convex
// query, every client reading it is subscribed to it — that is
// Convex's doing, not something VexCMS bolted on top.
export const featuredPosts = query({
  args: {},
  handler: async (ctx) =>
    find({
      ctx,
      collection: "posts",
      withIndex: { name: "by_featured", range: (q) => q.eq("featured", true) },
      access: { bypass: true },
    }),
})

Coming from Payload

The config you already know, on a backend that subscribes.

Collections, fields, globals, blocks, access control: the shapes are the ones you already write. Your content modelling carries over. What changes is the layer underneath.

  • defineCollection and defineGlobal, with the field helpers you expect from a code-first CMS.
  • A blocks field that composes typed content blocks into a real page builder.
  • Document- and field-level access control, with constraints that compile to an indexed range.
  • An upload field over a searchable media library, backed by Convex file storage.
  • Email, password, and OAuth through Better Auth, with organisations and API keys as plugins.
  • No database to run, no adapter to pick, no cache to invalidate. Convex is all three.

From nothing to a live CMS in four steps.

The scaffolder wires Convex, auth, and the admin panel together. Your part is the field definitions.

  1. 01

    Scaffold the project

    pnpm create vexcms@alpha gives you a Next.js app with Convex, Better Auth, and the admin panel already wired together

  2. 02

    Define your collections

    Declare fields with defineCollection and the field helpers. vex dev watches the file and regenerates the Convex schema, the types, and the validators as you type

  3. 03

    Build the pages

    Compose pages from typed blocks. Each block pairs one config with one renderer. Editors manage them from the admin panel

  4. 04

    Deploy

    Push to Convex and deploy the Next.js app. Every read is a subscription, so content changes reach open clients on their own

Questions we keep getting.

Open an issue

A headless CMS that runs inside your Convex backend. You define collections in TypeScript. VexCMS generates the Convex schema and types, exports typed read and write functions, and gives your team a real-time admin panel.

Scaffold it and see.

One command gives you a Next.js app, a Convex deployment, auth, the admin panel, and a marketing site like this one. Seeded, editable, and yours.