avoid-feature-creep
Prevent feature creep when building software, apps, and AI-powered products. Use this skill when planning features, reviewing scope, building MVPs, managing…
Defining and validating database schemas with proper typing, index configuration, optional fields, unions, and migration strategies for schema changes
$ npx -y skills add waynesutton/convexskills --skill convex-schema-validator --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/convex-schema-validatorContext preview
The summary Claude sees to decide when to auto-load this skill.
Defining and validating database schemas with proper typing, index configuration, optional fields, unions, and migration strategies for schema changes
name: convex-schema-validator displayName: Convex Schema Validator description: Defining and validating database schemas with proper typing, index configuration, optional fields, unions, and migration strategies for schema changes version: 1.0.0 author: Convex tags: [convex, schema, validation, typescript, indexes, migrations]
Define and validate database schemas in Convex with proper typing, index configuration, optional fields, unions, and strategies for schema migrations.
Before implementing, do not assume; fetch the latest documentation:
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
users: defineTable({
name: v.string(),
email: v.string(),
avatarUrl: v.optional(v.string()),
createdAt: v.number(),
}),
tasks: defineTable({
title: v.string(),
description: v.optional(v.string()),
completed: v.boolean(),
userId: v.id("users"),
priority: v.union(
v.literal("low"),
v.literal("medium"),
v.literal("high")
),
}),
});| Validator | TypeScript Type | Example | |-----------|----------------|---------| | `v.string()` | `string` | `"hello"` | | `v.number()` | `number` | `42`, `3.14` | | `v.boolean()` | `boolean` | `true`, `false` | | `v.null()` | `null` | `null` | | `v.int64()` | `bigint` | `9007199254740993n` | | `v.bytes()` | `ArrayBuffer` | Binary data | | `v.id("table")` | `Id<"table">` | Document reference | | `v.array(v)` | `T[]` | `[1, 2, 3]` | | `v.object({})` | `{ ... }` | `{ name: "..." }` | | `v.optional(v)` | `T \| undefined` | Optional field | | `v.union(...)` | `T1 \| T2` | Multiple types | | `v.literal(x)` | `"x"` | Exact value | | `v.any()` | `any` | Any value | | `v.record(k, v)` | `Record<K, V>` | Dynamic keys |
export default defineSchema({
messages: defineTable({
channelId: v.id("channels"),
authorId: v.id("users"),
content: v.string(),
sentAt: v.number(),
})
// Single field index
.index("by_channel", ["channelId"])
// Compound index
.index("by_channel_and_author", ["channelId", "authorId"])
// Index for sorting
.index("by_channel_and_time", ["channelId", "sentAt"]),
// Full-text search index
articles: defineTable({
title: v.string(),
body: v.string(),
category: v.string(),
})
.searchIndex("search_content", {
searchField: "body",
filterFields: ["category"],
}),
});export default defineSchema({
// Nested objects
profiles: defineTable({
userId: v.id("users"),
settings: v.object({
theme: v.union(v.literal("light"), v.literal("dark")),
notifications: v.object({
email: v.boolean(),
push: v.boolean(),
}),
}),
}),
// Arrays of objects
orders: defineTable({
customerId: v.id("users"),
items: v.array(v.object({
productId: v.id("products"),
quantity: v.number(),
price: v.number(),
})),
status: v.union(
v.literal("pending"),
v.literal("processing"),
v.literal("shipped"),
v.literal("delivered")
),
}),
// Record type for dynamic keys
analytics: defineTable({
date: v.string(),
metrics: v.record(v.string(), v.number()),
}),
});export default defineSchema({
events: defineTable(
v.union(
v.object({
type: v.literal("user_signup"),
userId: v.id("users"),
email: v.string(),
}),
v.object({
type: v.literal("purchase"),
userId: v.id("users"),
orderId: v.id("orders"),
amount: v.number(),
}),
v.object({
type: v.literal("page_view"),
sessionId: v.string(),
path: v.string(),
})
)
).index("by_type", ["type"]),
});export default defineSchema({
items: defineTable({
// Optional: field may not exist
description: v.optional(v.string()),
// Nullable: field exists but can be null
deletedAt: v.union(v.number(), v.null()),
// Optional and nullable
notes: v.optional(v.union(v.string(), v.null())),
}),
});Always include all indexed fields in the index name:
export default defineSchema({
posts: defineTable({
authorId: v.id("users"),
categoryId: v.id("categories"),
publishedAt: v.number(),
status: v.string(),
})
// Good: descriptive names
.index("by_author", ["authorId"])
.index("by_author_and_category", ["authorId", "categoryId"])
.index("by_category_and_status", ["categoryId", "status"])
.index("by_status_and_published", ["status", "publishedAt"]),
});// Before
users: defineTable({
name: v.string(),
email: v.string(),
})
// After - add as optional first
users: defineTable({
name: v.string(),
email: v.string(),
avatarUrl: v.optional(v.string()), // New optional field
})// convex/migrations.ts
import { internalMutation } from "./_generated/server";
import { v } from "convex/values";
export const backfillAvatars = internalMutation({
args: {},
returns: v.number(),
handler: async (ctx) => {
const users = await ctx.db
.query("users")
.filter((q) => q.eq(q.field("avatarUrl"), undefined))
.take(100);
for (const user of users) {
await ctx.db.patch(user._id, {
avatarUrl: `https://api.diOfficial Convex plugins for AI coding agents, providing development tools for building reactive backends with TypeScript.
Repo: waynesutton/convexskills
Prevent feature creep when building software, apps, and AI-powered products. Use this skill when planning features, reviewing scope, building MVPs, managing…
Building AI agents with the Convex Agent component including thread management, tool integration, streaming responses, RAG patterns, and workflow orchestration
Guidelines for building production-ready Convex apps covering function organization, query patterns, validation, TypeScript usage, error handling, and the Zen…
How to create, structure, and publish self-contained Convex components with proper isolation, exports, and dependency management
Scheduled function patterns for background tasks including interval scheduling, cron expressions, job monitoring, retry strategies, and best practices for…
Complete file handling including upload flows, serving files via URL, storing generated files from actions, deletion, and accessing file metadata from system…