Loading skill
Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Design and generate Convex database schemas with proper validation, indexes, and relationships. Use when creating schema.ts or modifying table definitions.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-20 | ✗→✓ | ▲ Improved | 50% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 109% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 84% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 71% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 29% | 0% |
Build well-structured Convex schemas following best practices for relationships, indexes, and validators.
convex/schema.ts filev.* typestypescriptimport { 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"]), });
typescriptexport 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"]), });
typescriptexport 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"]), });
typescriptexport 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"]), });
typescriptexport 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 }), });
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()
by_user: ["userId"]by_email: ["email"]by_user_and_status: ["userId", "status"]by_team_and_created: ["teamId", "createdAt"]by_a_and_b usually covers by_av.union(v.literal(...)) patternv.number() (milliseconds since epoch)If converting from nested structures:
Before:
typescriptusers: defineTable({ posts: v.array(v.object({ title: v.string(), comments: v.array(v.object({ text: v.string() })), })), })
After:
typescriptusers: 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"]),
Other measured skills in the registry, with their headline benchmark lift.