
Schema Builder
Generate and refine Convex `schema.ts` with validators, relational IDs, and indexes while you model app data.
Overview
Schema-builder is an agent skill for the Build phase that designs and generates Convex database schemas with validators, indexes, and relational ID patterns.
Install
npx skills add https://github.com/get-convex/agent-skills --skill schema-builderWhat is this skill?
- Document-relational Convex patterns with `v.id()` foreign keys instead of deep nesting
- Strict `v.*` validators with union/literal enums and timestamp fields
- Index guidance for lookup keys (userId, teamId) and bounded-array rules (<8192 items)
- Full `defineSchema` / `defineTable` template for new tables and migrations
- Covers adding tables, optimizing indexes, and flattening nested data to relational rows
- 4 schema design principles
- arrays bounded guidance under 8192 items
Adoption & trust: 549 installs on skills.sh; 31 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You need Convex tables and indexes that match how you query, but ad-hoc `schema.ts` edits break type safety or leave foreign keys unindexed.
Who is it for?
Solo builders starting or extending a Convex app who want document-relational modeling and index rules in one pass.
Skip if: Teams not using Convex, or apps where data modeling is already finalized and only unrelated frontend work remains.
When should I use this skill?
Creating or modifying `convex/schema.ts`, adding tables, designing relationships, or adding or optimizing indexes.
What do I get? / Deliverables
You get a structured `defineSchema` layout with `v.*` types, relationships, and index recommendations ready to drop into `convex/schema.ts`.
- `convex/schema.ts` table definitions
- Index definitions on foreign keys and query fields
- Validator patterns for enums and optional fields
Recommended Skills
Journey fit
Schema work happens when you are implementing the product data layer on Convex, which is core backend build work. `convex/schema.ts` and table definitions are backend infrastructure, not frontend or launch tasks.
How it compares
Convex-focused schema scaffolding in SKILL.md form—not a generic SQL ORM generator or a live database MCP.
Common Questions / FAQ
Who is schema-builder for?
Indie and solo developers building SaaS, APIs, or agent backends on Convex who need help authoring `schema.ts` with correct validators and indexes.
When should I use schema-builder?
During Build when creating `convex/schema.ts`, adding tables, designing ID-based relationships, optimizing indexes, or flattening nested documents into relational tables.
Is schema-builder safe to install?
It is instructional schema guidance; review the Security Audits panel on this Prism page and inspect the skill files in your repo before trusting generated schema in production.
SKILL.md
READMESKILL.md - Schema Builder
# Convex Schema Builder Build well-structured Convex schemas following best practices for relationships, indexes, and validators. ## When to Use - Creating a new `convex/schema.ts` file - Adding tables to existing schema - Designing data model relationships - Adding or optimizing indexes - Converting nested data to relational structure ## Schema Design Principles 1. **Document-Relational**: Use flat documents with ID references, not deep nesting 2. **Index Foreign Keys**: Always index fields used in lookups (userId, teamId, etc.) 3. **Limit Arrays**: Only use arrays for small, bounded collections (<8192 items) 4. **Type Safety**: Use strict validators with `v.*` types ## Schema Template ```typescript import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; export default defineSchema({ tableName: defineTable({ // Required fields field: v.string(), // Optional fields optional: v.optional(v.number()), // Relations (use IDs) userId: v.id("users"), // Enums with union + literal status: v.union( v.literal("active"), v.literal("pending"), v.literal("archived") ), // Timestamps createdAt: v.number(), updatedAt: v.optional(v.number()), }) // Index for queries by this field .index("by_user", ["userId"]) // Compound index for common query patterns .index("by_user_and_status", ["userId", "status"]) // Index for time-based queries .index("by_created", ["createdAt"]), }); ``` ## Common Patterns ### One-to-Many Relationship ```typescript export default defineSchema({ users: defineTable({ name: v.string(), email: v.string(), }).index("by_email", ["email"]), posts: defineTable({ userId: v.id("users"), title: v.string(), content: v.string(), }).index("by_user", ["userId"]), }); ``` ### Many-to-Many with Junction Table ```typescript export default defineSchema({ users: defineTable({ name: v.string(), }), projects: defineTable({ name: v.string(), }), projectMembers: defineTable({ userId: v.id("users"), projectId: v.id("projects"), role: v.union(v.literal("owner"), v.literal("member")), }) .index("by_user", ["userId"]) .index("by_project", ["projectId"]) .index("by_project_and_user", ["projectId", "userId"]), }); ``` ### Hierarchical Data ```typescript export default defineSchema({ comments: defineTable({ postId: v.id("posts"), parentId: v.optional(v.id("comments")), // null for top-level userId: v.id("users"), text: v.string(), }) .index("by_post", ["postId"]) .index("by_parent", ["parentId"]), }); ``` ### Small Bounded Arrays (OK to use) ```typescript export default defineSchema({ users: defineTable({ name: v.string(), // Small, bounded collections are fine roles: v.array(v.union( v.literal("admin"), v.literal("editor"), v.literal("viewer") )), tags: v.array(v.string()), // e.g., max 10 tags }), }); ``` ## Validator Reference ```typescript // Primitives v.string() v.number() v.boolean() v.null() v.id("tableName") // Optional v.optional(v.string()) // Union types (enums) v.union(v.literal("a"), v.literal("b")) // Objects v.object({ key: v.string(), nested: v.number(), }) // Arrays v.array(v.string()) // Records (arbitrary keys) v.record(v.string(), v.boolean()) // Any (avoid if possible) v.any() ``` ## Index Strategy 1. **Single-field indexes**: For simple lookups - `by_user: ["userId"]` - `by_email: ["email"]` 2. **Compound indexes**: For filtered queries - `by_user_and_status: ["userId", "status"]` - `by_team_and_created: ["teamId", "createdAt"]` 3. **Remove redundant**: `by_a_and_b` usually covers `by_a` ## Checklist - [ ] All