schema-builder
Design and generate Convex database schemas with proper validation, indexes, and relationships. Use when creating schema.ts or modifying table definitions.
How do I install this agent skill?
npx skills add https://github.com/get-convex/agent-skills --skill schema-builderIs this agent skill safe to install?
- Gen Agent Trust Hubpass
This skill provides documentation and code templates for designing database schemas for the Convex platform. It contains no executable code, does not perform network operations, and has no security risks.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
- Runlayerwarn
1/1 file flagged
What does this agent skill do?
Convex Schema Builder
Build well-structured Convex schemas following best practices for relationships, indexes, and validators.
When to Use
- Creating a new
convex/schema.tsfile - Adding tables to existing schema
- Designing data model relationships
- Adding or optimizing indexes
- Converting nested data to relational structure
Schema Design Principles
- Document-Relational: Use flat documents with ID references, not deep nesting
- Index Foreign Keys: Always index fields used in lookups (userId, teamId, etc.)
- Limit Arrays: Only use arrays for small, bounded collections (<8192 items)
- Type Safety: Use strict validators with
v.*types
Schema Template
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
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
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
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)
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
// 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
-
Single-field indexes: For simple lookups
by_user: ["userId"]by_email: ["email"]
-
Compound indexes: For filtered queries
by_user_and_status: ["userId", "status"]by_team_and_created: ["teamId", "createdAt"]
-
Remove redundant:
by_a_and_busually coversby_a
Checklist
- All foreign keys have indexes
- Common query patterns have compound indexes
- Arrays are small and bounded (or converted to relations)
- All fields have proper validators
- Enums use
v.union(v.literal(...))pattern - Timestamps use
v.number()(milliseconds since epoch)
Migration from Nested to Relational
If converting from nested structures:
Before:
users: defineTable({
posts: v.array(v.object({
title: v.string(),
comments: v.array(v.object({ text: v.string() })),
})),
})
After:
users: defineTable({
name: v.string(),
}),
posts: defineTable({
userId: v.id("users"),
title: v.string(),
}).index("by_user", ["userId"]),
comments: defineTable({
postId: v.id("posts"),
text: v.string(),
}).index("by_post", ["postId"]),
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/get-convex/agent-skills/schema-builder">View schema-builder on skillZs</a>