Valet

Schema API

defineSchema, defineTable, validators, indexes, and sync configuration.

// valet/schema.ts
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    todos: defineTable({
        title: v.string(),
        completed: v.boolean(),
        priority: v.number(),
        userId: v.id('users'),
    })
        .index('by_priority', ['priority'])
        .sync({
            filter: (q, ctx) => q.eq('userId', ctx.auth.userId),
            mode: 'full',
        }),

    users: defineTable({
        name: v.string(),
        email: v.string(),
    })
        .index('by_email', ['email'])
        .searchIndex('search_users', { searchField: 'name' }),
})

The schema API defines your database tables, validators, indexes, and sync configuration. Every Valet project has a single schema.ts file in the valet/ directory that exports the result of defineSchema().

defineSchema

// valet/schema.ts
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    todos: defineTable({
        title: v.string(),
        completed: v.boolean(),
    }),
    users: defineTable({
        name: v.string(),
    }),
})

Compiles table definitions into a schema object. The schema is consumed by codegen to produce typed hooks, API references, and schema.json.

Signature

function defineSchema<T extends SchemaDefinition>(tables: T): CompiledSchema<T>

Parameters

ParameterTypeRequiredDescription
tablesSchemaDefinitionYesAn object where each key is a table name and each value is a TableBuilder from defineTable().

Returns

A CompiledSchema<T> object containing the compiled table definitions, validators, indexes, and sync configuration.

defineTable

// valet/schema.ts
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    messages: defineTable({
        body: v.string(),
        authorId: v.id('users'),
        channelId: v.id('channels'),
        sentAt: v.number(),
    }),
})

Defines a single table with its field validators. Returns a TableBuilder that you chain with .index(), .searchIndex(), .sync(), and .backfill().

Signature

function defineTable<T extends FieldDefinitions>(fields: T): TableBuilder<T>

Parameters

ParameterTypeRequiredDescription
fieldsFieldDefinitionsYesAn object where each key is a field name and each value is a validator from the v namespace.

Every table automatically gets an _id field of type Id<"tableName">. Do not declare _id in the field definitions.

TableBuilder methods

.index(name, fields)

// valet/schema.ts
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    todos: defineTable({
        title: v.string(),
        completed: v.boolean(),
        priority: v.number(),
        createdAt: v.number(),
    })
        .index('by_priority', ['priority'])
        .index('by_completed_and_created', ['completed', 'createdAt']),
})

Adds a database index for efficient filtering and ordering.

ParameterTypeRequiredDescription
namestringYesA unique name for the index.
fieldsstring[]YesAn ordered array of field names to include in the index. Compound indexes support queries filtering on these fields in order.

Returns the TableBuilder for chaining.

.searchIndex(name, config)

// valet/schema.ts
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    articles: defineTable({
        title: v.string(),
        body: v.string(),
        category: v.string(),
    }).searchIndex('search_articles', {
        searchField: 'body',
    }),
})

Adds a full-text search index on a string field.

ParameterTypeRequiredDescription
namestringYesA unique name for the search index.
config.searchFieldstringYesThe string field to index for full-text search.

Returns the TableBuilder for chaining.

.sync(config)

// valet/schema.ts
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    // Full sync with a filter: each user only syncs their own todos
    todos: defineTable({
        title: v.string(),
        userId: v.id('users'),
    }).sync({
        filter: (q, ctx) => q.eq('userId', ctx.auth.userId),
        mode: 'full',
    }),

    // Windowed sync: only sync messages from the last 7 days
    messages: defineTable({
        body: v.string(),
        sentAt: v.number(),
    }).sync({
        filter: (q, ctx) => q.gte('sentAt', 0),
        mode: 'windowed',
        window: { field: 'sentAt', duration: 7 * 24 * 60 * 60 * 1000 },
    }),

    // No sync: server-only table
    analytics: defineTable({
        event: v.string(),
        timestamp: v.number(),
    }).sync({ mode: 'none' }),
})

Configures the sync filter for a table. The sync filter determines which documents are synced to each client.

ParameterTypeRequiredDescription
configSyncConfigYesThe sync configuration object.

SyncConfig

FieldTypeRequiredDescription
filterSyncFilterFnDepends on modeA function (q, ctx) => FilterExpr that returns a filter expression determining which documents sync to this client. Required for 'full' and 'windowed' modes. Must not be set for 'none' mode.
modeSyncModeYesThe execution mode for sync.
windowWindowConfigOnly for 'windowed'Time window configuration. Required when mode is 'windowed'.

SyncMode

ValueDescription
'full'Sync all matching documents to the client. The sync filter determines which documents match.
'windowed'Sync only documents within a time window. Combines the sync filter with a time-based constraint.
'none'Do not sync this table. Documents are only accessible via server-side queries.

WindowConfig

FieldTypeRequiredDescription
fieldstringYesThe numeric field to use as the time axis (typically a timestamp in milliseconds).
durationnumberYesThe window size in milliseconds. Documents older than this are excluded from sync.

SyncFilterFn

type SyncFilterFn<Doc> = (
    q: TypedSyncFilterBuilder<Doc>,
    ctx: SyncContext
) => FilterExpr

The filter function receives a query builder for constructing filter expressions and a context object with auth information. Return a filter expression that determines which documents sync to the client.

SyncContext

FieldTypeDescription
authSyncAuthContextThe authenticated user's information.

Returns the TableBuilder for chaining.

.backfill(column, fn)

// valet/schema.ts
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    todos: defineTable({
        title: v.string(),
        completed: v.boolean(),
        priority: v.number(),
    }).backfill('priority', () => 0),
})

Registers a backfill function for a newly added column. When codegen pushes the schema to the server, the server runs this function against every existing row that has a null value for the column.

ParameterTypeRequiredDescription
columnstringYesThe name of the column to backfill. Must be a field defined in the table.
fn(doc: Document) => FieldValueYesA function that receives the existing document and returns the value to set for the new column.

Returns the TableBuilder for chaining.

Validators (v namespace)

// valet/schema.ts
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    tasks: defineTable({
        title: v.string(),
        count: v.number(),
        position: v.int(),
        completed: v.boolean(),
        attachment: v.bytes(),
        deletedAt: v.null(),
        assignee: v.id('users'),
        tags: v.array(v.string()),
        metadata: v.object({ source: v.string() }),
        description: v.optional(v.string()),
        status: v.union(v.literal('active'), v.literal('done')),
    }),

    users: defineTable({
        name: v.string(),
    }),
})

The v namespace provides validator constructors for defining field types. Import v from 'valet-dev/server'.

Primitive validators

ValidatorTypeScript TypeDescription
v.string()stringUTF-8 string.
v.number()number64-bit floating-point number (IEEE 754).
v.int()number64-bit integer. Rejects floats at runtime.
v.boolean()booleantrue or false.
v.bytes()ArrayBufferArbitrary binary data.
v.null()nullThe value null.

Reference validators

ValidatorTypeScript TypeDescription
v.id("tableName")Id<"tableName">A document ID referencing a row in the specified table. Branded string at compile time, plain string at runtime.

Compound validators

ValidatorTypeScript TypeDescription
v.array(element)T[]An array where each element matches the inner validator.
v.object(fields){ ... }A nested object with the specified field validators.
v.optional(inner)T | undefinedThe field may be absent. Wraps any other validator.
v.union(...variants)T1 | T2 | ...The value must match one of the provided validators.
v.literal(value)Exact value typeThe value must be exactly the provided string, number, or boolean.

.deprecated()

// valet/schema.ts
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    todos: defineTable({
        title: v.string(),
        completed: v.boolean(),
        // Old field -- still in the database, hidden from generated types
        done: v.boolean().deprecated(),
    }),
})

Chain .deprecated() on any validator to mark the field as deprecated. The field remains in the database and continues to validate, but codegen excludes it from the generated TypeScript types. Use this to phase out fields without a breaking schema change.

Signature

validator.deprecated(): DeprecatedValidator

Works on any validator: v.string().deprecated(), v.optional(v.number()).deprecated(), etc.

Id<TableName>

// valet/todos.ts
import { defineQuery, defineMutation, v } from './_generated/valet/api'
import type { Id } from 'valet-dev/server'

export const get = defineQuery({
    args: { id: v.id('todos') },
    execution: 'local',
    handler: async (ctx, args) => {
        // args.id is Id<"todos"> -- a branded string
        return ctx.db.get(args.id)
    },
})

export const remove = defineMutation({
    args: { id: v.id('todos') },
    handler: async (ctx, args) => {
        await ctx.db.delete(args.id)
    },
})

Id<TableName> is a branded string type. At runtime it is a plain string. At compile time it carries the table name, preventing you from passing an Id<"users"> where an Id<"todos"> is expected.

Codegen produces Id types for each table in api.d.ts. You can also import the generic Id type from 'valet-dev/server'.

PropertyValue
Runtime typestring
Compile-time typestring & { __tableName: TableName }
Generated bydb.insert() return value, doc._id field
Validated byv.id("tableName")

Converting from string

Id<TableName> is branded at compile time only — at runtime it is a plain string. When you have a plain string from an external source (URL params, local storage, a third-party API), cast it to the appropriate Id type with as:

import type { Id } from './_generated/valet/api'

const todoId = params.id as Id<'todos'>
const todo = await ctx.db.get(todoId)

Going the other direction is automatic — Id<"todos"> is assignable to string with no cast needed.

On this page