Getting Started

Coming from Directus

If you have used Directus, Vextro's approach will feel both familiar and different. Both generate admin UIs from data definitions and let you manage arbitrary tables through a dashboard. The biggest difference: Directus wraps an existing SQL database, while Vextro is built natively on Convex -- a real-time, transactional backend that replaces your database, API layer, and WebSocket server.

Concept mapping

DirectusVextroNotes
Data StudioVextro admin panelAuto-generated CRUD UI from schema
Collections (auto-introspected)defineVextroCollection()Vextro defines collections in code; Directus introspects SQL tables
Virtual collections (shared table)Virtual collectionsBoth can present filtered views of a shared table
Runtime collection creationcreateCollection mutationVextro can create collections at runtime without code deploys
SingletonsdefineVextroGlobal()Single-document settings, same concept
Fields (DB columns)f field buildersVextro fields produce both schema validators and admin UI config
Display templatesuseAsTitle + list columnsVextro uses useAsTitle for document labels, listConfig.columns for list views
Interfaces (custom inputs)Custom components + field registryBoth support overriding the default input for a field type
Displays (custom renderers)Relationship renderers + pluginsVextro uses a plugin-based renderer system
Flows (automation)Hooks + Convex scheduled functionsVextro hooks are code-first; Convex schedulers replace cron/trigger flows
Roles & permissionsaccess config + getUserRoles callbackPermission strings matched against user roles
SharesNot applicableVextro does not have a public link-sharing system
Translationslocalization configPer-collection locale configuration with field-level control
Insights (dashboards)Not built-inVextro focuses on content management, not analytics dashboards
Extensions (modules, layouts, etc.)Plugins (planned)Extensibility via the plugin API
REST / GraphQL APIsNo HTTP API -- call Convex functions directlyType-safe function calls replace REST endpoints
SQL database (Postgres, MySQL, etc.)Convex databaseReal-time, transactional, hosted

Key differences

Code-first vs database-first

Directus discovers collections by introspecting your SQL schema. You create tables in the database and Directus generates the UI.

Vextro works the opposite way: you define collections in TypeScript with defineVextroCollection(), and Vextro generates both the Convex schema and the admin UI from a single source of truth.

// Vextro: define once, get schema + admin UI
export const products = defineVextroCollection({
  label: "Products",
  tableName: "products",
  collectionType: "content",
  fields: {
    name: f.text({ required: true }),
    sku: f.text({ required: true }),
    price: f.number({ required: true }),
    description: f.richText(),
    category: f.select({ options: ["electronics", "clothing", "food"] }),
  },
});

// Use products.table in your Convex schema
export default defineSchema({
  products: products.table,
});

Virtual collections bridge the gap

If you prefer the Directus-style approach of managing existing tables without code changes, Vextro's virtual collections provide exactly that. You can point virtual collections at any existing Convex table and get a full admin UI without writing field builders.

// Manage an existing table without defineVextroCollection
const userManagement = {
  slug: "users",
  label: "Users",
  collectionType: "system",
  tableName: "users",
  listConfig: {
    columns: ["displayName", "email", "role"],
    searchableFields: ["displayName", "email"],
  },
  access: {
    read: "admin:users:read",
    update: "admin:users:write",
  },
};

You can also create virtual collections at runtime through the admin UI, just like adding a collection in Directus.

Real-time by default

In Directus, real-time updates require configuring WebSocket connections and subscribing to specific collections. With Convex, every query is a live subscription automatically. When data changes, all connected clients update instantly -- no WebSocket setup, no polling, no configuration.

No REST or GraphQL layer

Directus generates REST and GraphQL endpoints for every collection. Vextro does not -- because Convex does not need them. Your frontend calls Convex functions directly with full type safety:

// Directus: REST API
const response = await fetch("/items/products?filter[category][_eq]=electronics");

// Vextro/Convex: direct function call
const products = await convex.query(api.products.listByCategory, { category: "electronics" });

Atomic transactions

Directus hooks can encounter partial failures since they operate on a traditional SQL database. Convex mutations are atomic transactions -- if anything throws, everything rolls back. This eliminates an entire class of data consistency bugs.

Trait-based collection behavior

Directus collections all behave the same way -- the database schema determines everything. Vextro collections have configurable traits that control which automatic features apply:

// A collection with status workflow, timestamps, and versioning
defineVextroCollection({
  collectionType: "content",
  traits: { statusWorkflow: true, versioning: true },
  // ...
});

// A collection with just timestamps, no status workflow
defineVextroCollection({
  collectionType: "operational",
  traits: { statusWorkflow: false, autoTimestamps: true },
  // ...
});

Directus Flows vs Vextro Hooks

Directus Flows are a visual automation builder with triggers, operations, and conditions. Vextro takes a code-first approach with hooks and Convex scheduled functions.

Directus FlowsVextro equivalent
Event trigger (item.create, item.update)beforeChange / afterChange hooks
Schedule trigger (cron)Convex cron jobs (crons.ts)
Webhook triggerWebhook configuration in admin
Send email operationafterChange hook + email service
Run script operationHook functions (full Node.js)
Condition operationLogic in hook functions
// Vextro: hook equivalent of a Directus Flow
defineVextroCollection({
  // ...
  hooks: {
    afterChange: [
      async ({ data, operation, ctx }) => {
        if (operation === "create") {
          // Send notification, update related records, etc.
          await ctx.scheduler.runAfter(0, api.notifications.send, {
            message: `New product created: ${data.name}`,
          });
        }
      },
    ],
  },
});

Migration path

1. Map your collections

For each Directus collection, create a defineVextroCollection() call with the equivalent fields. If you have tables you want to manage without defining field builders, use virtual collections instead.

2. Define your schema

Use the collection .table property to generate Convex table definitions. Add them to convex/schema.ts. Run npx convex dev to create the tables.

3. Move your Flows to hooks

Replace Directus Flows with Vextro hooks for event-driven logic, and Convex cron jobs for scheduled tasks. See Hooks for the complete API.

4. Migrate permissions

Replace Directus role/permission configurations with Vextro's access config and getUserRoles callback. See Access Control.

5. Migrate data

Use Vextro's Import & Export feature to load existing content via CSV or JSON. For large datasets, write a Convex action that reads from your SQL database and inserts into Convex.

6. Write frontend queries

Replace Directus SDK / REST calls with Convex query functions. See Querying Data for examples.

Next steps

Previous
Coming from PayloadCMS