[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CCDAXV3MYC3ZAES4GSM433":3,"skill-same:01M6CCDAXV3MYC3ZAES4GSM433":83},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":29,"linkOnly":30,"markdownContent":31,"markdownBody":32,"frontMatter":33,"refName":38,"commitSha":39,"skillDirectory":40,"forksCount":14,"repositoryHtmlUrl":41,"repositoryHomepageUrl":42,"ownerHtmlUrl":43,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":44,"install":45,"resources":58},"01M6CCDAXV3MYC3ZAES4GSM433","convex-schema-validator",null,"Defining and validating database schemas with proper typing, index configuration, optional fields, unions, and migration strategies for schema changes","baisharg\u002FASTN","baisharg","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F204916775?v=4","01M6CC8XHZYVK2DR22FZ1BESCG","BAISH","01M6CC8XP400QZ9DXAWTC8EG3X",2,"AGPL-3.0","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fconvex-schema-validator\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28],"convex","schema","convex schema validator","typescript","validation","database","index","migration","migrations","indexes","2026-11-10 10:12:30",false,"---\nname: convex-schema-validator\ndisplayName: Convex Schema Validator\ndescription: Defining and validating database schemas with proper typing, index configuration, optional fields, unions, and migration strategies for schema changes\nversion: 1.0.0\nauthor: Convex\ntags: [convex, schema, validation, typescript, indexes, migrations]\n---\n\n# Convex Schema Validator\n\nDefine and validate database schemas in Convex with proper typing, index configuration, optional fields, unions, and strategies for schema migrations.\n\n## Documentation Sources\n\nBefore implementing, do not assume; fetch the latest documentation:\n\n- Primary: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Fschemas\n- Indexes: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Findexes\n- Data Types: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Ftypes\n- For broader context: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n\n## Instructions\n\n### Basic Schema Definition\n\n```typescript\n\u002F\u002F convex\u002Fschema.ts\nimport { defineSchema, defineTable } from 'convex\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport default defineSchema({\n  users: defineTable({\n    name: v.string(),\n    email: v.string(),\n    avatarUrl: v.optional(v.string()),\n    createdAt: v.number(),\n  }),\n\n  tasks: defineTable({\n    title: v.string(),\n    description: v.optional(v.string()),\n    completed: v.boolean(),\n    userId: v.id('users'),\n    priority: v.union(v.literal('low'), v.literal('medium'), v.literal('high')),\n  }),\n})\n```\n\n### Validator Types\n\n| Validator        | TypeScript Type  | Example             |\n| ---------------- | ---------------- | ------------------- |\n| `v.string()`     | `string`         | `\"hello\"`           |\n| `v.number()`     | `number`         | `42`, `3.14`        |\n| `v.boolean()`    | `boolean`        | `true`, `false`     |\n| `v.null()`       | `null`           | `null`              |\n| `v.int64()`      | `bigint`         | `9007199254740993n` |\n| `v.bytes()`      | `ArrayBuffer`    | Binary data         |\n| `v.id(\"table\")`  | `Id\u003C\"table\">`    | Document reference  |\n| `v.array(v)`     | `T[]`            | `[1, 2, 3]`         |\n| `v.object({})`   | `{ ... }`        | `{ name: \"...\" }`   |\n| `v.optional(v)`  | `T \\| undefined` | Optional field      |\n| `v.union(...)`   | `T1 \\| T2`       | Multiple types      |\n| `v.literal(x)`   | `\"x\"`            | Exact value         |\n| `v.any()`        | `any`            | Any value           |\n| `v.record(k, v)` | `Record\u003CK, V>`   | Dynamic keys        |\n\n### Index Configuration\n\n```typescript\nexport default defineSchema({\n  messages: defineTable({\n    channelId: v.id('channels'),\n    authorId: v.id('users'),\n    content: v.string(),\n    sentAt: v.number(),\n  })\n    \u002F\u002F Single field index\n    .index('by_channel', ['channelId'])\n    \u002F\u002F Compound index\n    .index('by_channel_and_author', ['channelId', 'authorId'])\n    \u002F\u002F Index for sorting\n    .index('by_channel_and_time', ['channelId', 'sentAt']),\n\n  \u002F\u002F Full-text search index\n  articles: defineTable({\n    title: v.string(),\n    body: v.string(),\n    category: v.string(),\n  }).searchIndex('search_content', {\n    searchField: 'body',\n    filterFields: ['category'],\n  }),\n})\n```\n\n### Complex Types\n\n```typescript\nexport default defineSchema({\n  \u002F\u002F Nested objects\n  profiles: defineTable({\n    userId: v.id('users'),\n    settings: v.object({\n      theme: v.union(v.literal('light'), v.literal('dark')),\n      notifications: v.object({\n        email: v.boolean(),\n        push: v.boolean(),\n      }),\n    }),\n  }),\n\n  \u002F\u002F Arrays of objects\n  orders: defineTable({\n    customerId: v.id('users'),\n    items: v.array(\n      v.object({\n        productId: v.id('products'),\n        quantity: v.number(),\n        price: v.number(),\n      }),\n    ),\n    status: v.union(\n      v.literal('pending'),\n      v.literal('processing'),\n      v.literal('shipped'),\n      v.literal('delivered'),\n    ),\n  }),\n\n  \u002F\u002F Record type for dynamic keys\n  analytics: defineTable({\n    date: v.string(),\n    metrics: v.record(v.string(), v.number()),\n  }),\n})\n```\n\n### Discriminated Unions\n\n```typescript\nexport default defineSchema({\n  events: defineTable(\n    v.union(\n      v.object({\n        type: v.literal('user_signup'),\n        userId: v.id('users'),\n        email: v.string(),\n      }),\n      v.object({\n        type: v.literal('purchase'),\n        userId: v.id('users'),\n        orderId: v.id('orders'),\n        amount: v.number(),\n      }),\n      v.object({\n        type: v.literal('page_view'),\n        sessionId: v.string(),\n        path: v.string(),\n      }),\n    ),\n  ).index('by_type', ['type']),\n})\n```\n\n### Optional vs Nullable Fields\n\n```typescript\nexport default defineSchema({\n  items: defineTable({\n    \u002F\u002F Optional: field may not exist\n    description: v.optional(v.string()),\n\n    \u002F\u002F Nullable: field exists but can be null\n    deletedAt: v.union(v.number(), v.null()),\n\n    \u002F\u002F Optional and nullable\n    notes: v.optional(v.union(v.string(), v.null())),\n  }),\n})\n```\n\n### Index Naming Convention\n\nAlways include all indexed fields in the index name:\n\n```typescript\nexport default defineSchema({\n  posts: defineTable({\n    authorId: v.id('users'),\n    categoryId: v.id('categories'),\n    publishedAt: v.number(),\n    status: v.string(),\n  })\n    \u002F\u002F Good: descriptive names\n    .index('by_author', ['authorId'])\n    .index('by_author_and_category', ['authorId', 'categoryId'])\n    .index('by_category_and_status', ['categoryId', 'status'])\n    .index('by_status_and_published', ['status', 'publishedAt']),\n})\n```\n\n### Schema Migration Strategies\n\n#### Adding New Fields\n\n```typescript\n\u002F\u002F Before\nusers: defineTable({\n  name: v.string(),\n  email: v.string(),\n})\n\n\u002F\u002F After - add as optional first\nusers: defineTable({\n  name: v.string(),\n  email: v.string(),\n  avatarUrl: v.optional(v.string()), \u002F\u002F New optional field\n})\n```\n\n#### Backfilling Data\n\n```typescript\n\u002F\u002F convex\u002Fmigrations.ts\nimport { internalMutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport const backfillAvatars = internalMutation({\n  args: {},\n  returns: v.number(),\n  handler: async (ctx) => {\n    const users = await ctx.db\n      .query('users')\n      .filter((q) => q.eq(q.field('avatarUrl'), undefined))\n      .take(100)\n\n    for (const user of users) {\n      await ctx.db.patch(user._id, {\n        avatarUrl: `https:\u002F\u002Fapi.dicebear.com\u002F7.x\u002Finitials\u002Fsvg?seed=${user.name}`,\n      })\n    }\n\n    return users.length\n  },\n})\n```\n\n#### Making Optional Fields Required\n\n```typescript\n\u002F\u002F Step 1: Backfill all null values\n\u002F\u002F Step 2: Update schema to required\nusers: defineTable({\n  name: v.string(),\n  email: v.string(),\n  avatarUrl: v.string(), \u002F\u002F Now required after backfill\n})\n```\n\n## Examples\n\n### Complete E-commerce Schema\n\n```typescript\n\u002F\u002F convex\u002Fschema.ts\nimport { defineSchema, defineTable } from 'convex\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport default defineSchema({\n  users: defineTable({\n    email: v.string(),\n    name: v.string(),\n    role: v.union(v.literal('customer'), v.literal('admin')),\n    createdAt: v.number(),\n  })\n    .index('by_email', ['email'])\n    .index('by_role', ['role']),\n\n  products: defineTable({\n    name: v.string(),\n    description: v.string(),\n    price: v.number(),\n    category: v.string(),\n    inventory: v.number(),\n    isActive: v.boolean(),\n  })\n    .index('by_category', ['category'])\n    .index('by_active_and_category', ['isActive', 'category'])\n    .searchIndex('search_products', {\n      searchField: 'name',\n      filterFields: ['category', 'isActive'],\n    }),\n\n  orders: defineTable({\n    userId: v.id('users'),\n    items: v.array(\n      v.object({\n        productId: v.id('products'),\n        quantity: v.number(),\n        priceAtPurchase: v.number(),\n      }),\n    ),\n    total: v.number(),\n    status: v.union(\n      v.literal('pending'),\n      v.literal('paid'),\n      v.literal('shipped'),\n      v.literal('delivered'),\n      v.literal('cancelled'),\n    ),\n    shippingAddress: v.object({\n      street: v.string(),\n      city: v.string(),\n      state: v.string(),\n      zip: v.string(),\n      country: v.string(),\n    }),\n    createdAt: v.number(),\n    updatedAt: v.number(),\n  })\n    .index('by_user', ['userId'])\n    .index('by_user_and_status', ['userId', 'status'])\n    .index('by_status', ['status']),\n\n  reviews: defineTable({\n    productId: v.id('products'),\n    userId: v.id('users'),\n    rating: v.number(),\n    comment: v.optional(v.string()),\n    createdAt: v.number(),\n  })\n    .index('by_product', ['productId'])\n    .index('by_user', ['userId']),\n})\n```\n\n### Using Schema Types in Functions\n\n```typescript\n\u002F\u002F convex\u002Fproducts.ts\nimport { query, mutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { Doc, Id } from '.\u002F_generated\u002FdataModel'\n\n\u002F\u002F Use Doc type for full documents\ntype Product = Doc\u003C'products'>\n\n\u002F\u002F Use Id type for references\ntype ProductId = Id\u003C'products'>\n\nexport const get = query({\n  args: { productId: v.id('products') },\n  returns: v.union(\n    v.object({\n      _id: v.id('products'),\n      _creationTime: v.number(),\n      name: v.string(),\n      description: v.string(),\n      price: v.number(),\n      category: v.string(),\n      inventory: v.number(),\n      isActive: v.boolean(),\n    }),\n    v.null(),\n  ),\n  handler: async (ctx, args): Promise\u003CProduct | null> => {\n    return await ctx.db.get(args.productId)\n  },\n})\n```\n\n## Best Practices\n\n- Never run `npx convex deploy` unless explicitly instructed\n- Never run any git commands unless explicitly instructed\n- Always define explicit schemas rather than relying on inference\n- Use descriptive index names that include all indexed fields\n- Start with optional fields when adding new columns\n- Use discriminated unions for polymorphic data\n- Validate data at the schema level, not just in functions\n- Plan index strategy based on query patterns\n\n## Common Pitfalls\n\n1. **Missing indexes for queries** - Every withIndex needs a corresponding schema index\n2. **Wrong index field order** - Fields must be queried in order defined\n3. **Using v.any() excessively** - Lose type safety benefits\n4. **Not making new fields optional** - Breaks existing data\n5. **Forgetting system fields** - \\_id and \\_creationTime are automatic\n\n## References\n\n- Convex Documentation: https:\u002F\u002Fdocs.convex.dev\u002F\n- Convex LLMs.txt: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n- Schemas: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Fschemas\n- Indexes: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Findexes\n- Data Types: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Ftypes\n","# Convex Schema Validator\n\nDefine and validate database schemas in Convex with proper typing, index configuration, optional fields, unions, and strategies for schema migrations.\n\n## Documentation Sources\n\nBefore implementing, do not assume; fetch the latest documentation:\n\n- Primary: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Fschemas\n- Indexes: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Findexes\n- Data Types: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Ftypes\n- For broader context: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n\n## Instructions\n\n### Basic Schema Definition\n\n```typescript\n\u002F\u002F convex\u002Fschema.ts\nimport { defineSchema, defineTable } from 'convex\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport default defineSchema({\n  users: defineTable({\n    name: v.string(),\n    email: v.string(),\n    avatarUrl: v.optional(v.string()),\n    createdAt: v.number(),\n  }),\n\n  tasks: defineTable({\n    title: v.string(),\n    description: v.optional(v.string()),\n    completed: v.boolean(),\n    userId: v.id('users'),\n    priority: v.union(v.literal('low'), v.literal('medium'), v.literal('high')),\n  }),\n})\n```\n\n### Validator Types\n\n| Validator        | TypeScript Type  | Example             |\n| ---------------- | ---------------- | ------------------- |\n| `v.string()`     | `string`         | `\"hello\"`           |\n| `v.number()`     | `number`         | `42`, `3.14`        |\n| `v.boolean()`    | `boolean`        | `true`, `false`     |\n| `v.null()`       | `null`           | `null`              |\n| `v.int64()`      | `bigint`         | `9007199254740993n` |\n| `v.bytes()`      | `ArrayBuffer`    | Binary data         |\n| `v.id(\"table\")`  | `Id\u003C\"table\">`    | Document reference  |\n| `v.array(v)`     | `T[]`            | `[1, 2, 3]`         |\n| `v.object({})`   | `{ ... }`        | `{ name: \"...\" }`   |\n| `v.optional(v)`  | `T \\| undefined` | Optional field      |\n| `v.union(...)`   | `T1 \\| T2`       | Multiple types      |\n| `v.literal(x)`   | `\"x\"`            | Exact value         |\n| `v.any()`        | `any`            | Any value           |\n| `v.record(k, v)` | `Record\u003CK, V>`   | Dynamic keys        |\n\n### Index Configuration\n\n```typescript\nexport default defineSchema({\n  messages: defineTable({\n    channelId: v.id('channels'),\n    authorId: v.id('users'),\n    content: v.string(),\n    sentAt: v.number(),\n  })\n    \u002F\u002F Single field index\n    .index('by_channel', ['channelId'])\n    \u002F\u002F Compound index\n    .index('by_channel_and_author', ['channelId', 'authorId'])\n    \u002F\u002F Index for sorting\n    .index('by_channel_and_time', ['channelId', 'sentAt']),\n\n  \u002F\u002F Full-text search index\n  articles: defineTable({\n    title: v.string(),\n    body: v.string(),\n    category: v.string(),\n  }).searchIndex('search_content', {\n    searchField: 'body',\n    filterFields: ['category'],\n  }),\n})\n```\n\n### Complex Types\n\n```typescript\nexport default defineSchema({\n  \u002F\u002F Nested objects\n  profiles: defineTable({\n    userId: v.id('users'),\n    settings: v.object({\n      theme: v.union(v.literal('light'), v.literal('dark')),\n      notifications: v.object({\n        email: v.boolean(),\n        push: v.boolean(),\n      }),\n    }),\n  }),\n\n  \u002F\u002F Arrays of objects\n  orders: defineTable({\n    customerId: v.id('users'),\n    items: v.array(\n      v.object({\n        productId: v.id('products'),\n        quantity: v.number(),\n        price: v.number(),\n      }),\n    ),\n    status: v.union(\n      v.literal('pending'),\n      v.literal('processing'),\n      v.literal('shipped'),\n      v.literal('delivered'),\n    ),\n  }),\n\n  \u002F\u002F Record type for dynamic keys\n  analytics: defineTable({\n    date: v.string(),\n    metrics: v.record(v.string(), v.number()),\n  }),\n})\n```\n\n### Discriminated Unions\n\n```typescript\nexport default defineSchema({\n  events: defineTable(\n    v.union(\n      v.object({\n        type: v.literal('user_signup'),\n        userId: v.id('users'),\n        email: v.string(),\n      }),\n      v.object({\n        type: v.literal('purchase'),\n        userId: v.id('users'),\n        orderId: v.id('orders'),\n        amount: v.number(),\n      }),\n      v.object({\n        type: v.literal('page_view'),\n        sessionId: v.string(),\n        path: v.string(),\n      }),\n    ),\n  ).index('by_type', ['type']),\n})\n```\n\n### Optional vs Nullable Fields\n\n```typescript\nexport default defineSchema({\n  items: defineTable({\n    \u002F\u002F Optional: field may not exist\n    description: v.optional(v.string()),\n\n    \u002F\u002F Nullable: field exists but can be null\n    deletedAt: v.union(v.number(), v.null()),\n\n    \u002F\u002F Optional and nullable\n    notes: v.optional(v.union(v.string(), v.null())),\n  }),\n})\n```\n\n### Index Naming Convention\n\nAlways include all indexed fields in the index name:\n\n```typescript\nexport default defineSchema({\n  posts: defineTable({\n    authorId: v.id('users'),\n    categoryId: v.id('categories'),\n    publishedAt: v.number(),\n    status: v.string(),\n  })\n    \u002F\u002F Good: descriptive names\n    .index('by_author', ['authorId'])\n    .index('by_author_and_category', ['authorId', 'categoryId'])\n    .index('by_category_and_status', ['categoryId', 'status'])\n    .index('by_status_and_published', ['status', 'publishedAt']),\n})\n```\n\n### Schema Migration Strategies\n\n#### Adding New Fields\n\n```typescript\n\u002F\u002F Before\nusers: defineTable({\n  name: v.string(),\n  email: v.string(),\n})\n\n\u002F\u002F After - add as optional first\nusers: defineTable({\n  name: v.string(),\n  email: v.string(),\n  avatarUrl: v.optional(v.string()), \u002F\u002F New optional field\n})\n```\n\n#### Backfilling Data\n\n```typescript\n\u002F\u002F convex\u002Fmigrations.ts\nimport { internalMutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport const backfillAvatars = internalMutation({\n  args: {},\n  returns: v.number(),\n  handler: async (ctx) => {\n    const users = await ctx.db\n      .query('users')\n      .filter((q) => q.eq(q.field('avatarUrl'), undefined))\n      .take(100)\n\n    for (const user of users) {\n      await ctx.db.patch(user._id, {\n        avatarUrl: `https:\u002F\u002Fapi.dicebear.com\u002F7.x\u002Finitials\u002Fsvg?seed=${user.name}`,\n      })\n    }\n\n    return users.length\n  },\n})\n```\n\n#### Making Optional Fields Required\n\n```typescript\n\u002F\u002F Step 1: Backfill all null values\n\u002F\u002F Step 2: Update schema to required\nusers: defineTable({\n  name: v.string(),\n  email: v.string(),\n  avatarUrl: v.string(), \u002F\u002F Now required after backfill\n})\n```\n\n## Examples\n\n### Complete E-commerce Schema\n\n```typescript\n\u002F\u002F convex\u002Fschema.ts\nimport { defineSchema, defineTable } from 'convex\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport default defineSchema({\n  users: defineTable({\n    email: v.string(),\n    name: v.string(),\n    role: v.union(v.literal('customer'), v.literal('admin')),\n    createdAt: v.number(),\n  })\n    .index('by_email', ['email'])\n    .index('by_role', ['role']),\n\n  products: defineTable({\n    name: v.string(),\n    description: v.string(),\n    price: v.number(),\n    category: v.string(),\n    inventory: v.number(),\n    isActive: v.boolean(),\n  })\n    .index('by_category', ['category'])\n    .index('by_active_and_category', ['isActive', 'category'])\n    .searchIndex('search_products', {\n      searchField: 'name',\n      filterFields: ['category', 'isActive'],\n    }),\n\n  orders: defineTable({\n    userId: v.id('users'),\n    items: v.array(\n      v.object({\n        productId: v.id('products'),\n        quantity: v.number(),\n        priceAtPurchase: v.number(),\n      }),\n    ),\n    total: v.number(),\n    status: v.union(\n      v.literal('pending'),\n      v.literal('paid'),\n      v.literal('shipped'),\n      v.literal('delivered'),\n      v.literal('cancelled'),\n    ),\n    shippingAddress: v.object({\n      street: v.string(),\n      city: v.string(),\n      state: v.string(),\n      zip: v.string(),\n      country: v.string(),\n    }),\n    createdAt: v.number(),\n    updatedAt: v.number(),\n  })\n    .index('by_user', ['userId'])\n    .index('by_user_and_status', ['userId', 'status'])\n    .index('by_status', ['status']),\n\n  reviews: defineTable({\n    productId: v.id('products'),\n    userId: v.id('users'),\n    rating: v.number(),\n    comment: v.optional(v.string()),\n    createdAt: v.number(),\n  })\n    .index('by_product', ['productId'])\n    .index('by_user', ['userId']),\n})\n```\n\n### Using Schema Types in Functions\n\n```typescript\n\u002F\u002F convex\u002Fproducts.ts\nimport { query, mutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { Doc, Id } from '.\u002F_generated\u002FdataModel'\n\n\u002F\u002F Use Doc type for full documents\ntype Product = Doc\u003C'products'>\n\n\u002F\u002F Use Id type for references\ntype ProductId = Id\u003C'products'>\n\nexport const get = query({\n  args: { productId: v.id('products') },\n  returns: v.union(\n    v.object({\n      _id: v.id('products'),\n      _creationTime: v.number(),\n      name: v.string(),\n      description: v.string(),\n      price: v.number(),\n      category: v.string(),\n      inventory: v.number(),\n      isActive: v.boolean(),\n    }),\n    v.null(),\n  ),\n  handler: async (ctx, args): Promise\u003CProduct | null> => {\n    return await ctx.db.get(args.productId)\n  },\n})\n```\n\n## Best Practices\n\n- Never run `npx convex deploy` unless explicitly instructed\n- Never run any git commands unless explicitly instructed\n- Always define explicit schemas rather than relying on inference\n- Use descriptive index names that include all indexed fields\n- Start with optional fields when adding new columns\n- Use discriminated unions for polymorphic data\n- Validate data at the schema level, not just in functions\n- Plan index strategy based on query patterns\n\n## Common Pitfalls\n\n1. **Missing indexes for queries** - Every withIndex needs a corresponding schema index\n2. **Wrong index field order** - Fields must be queried in order defined\n3. **Using v.any() excessively** - Lose type safety benefits\n4. **Not making new fields optional** - Breaks existing data\n5. **Forgetting system fields** - \\_id and \\_creationTime are automatic\n\n## References\n\n- Convex Documentation: https:\u002F\u002Fdocs.convex.dev\u002F\n- Convex LLMs.txt: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n- Schemas: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Fschemas\n- Indexes: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Findexes\n- Data Types: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Ftypes\n",{"name":5,"tags":34,"author":35,"version":36,"description":7,"displayName":37},[19,20,23,22,28,27],"Convex","1.0.0","Convex Schema Validator","main","83212e6d909b6a0fe9e93fd5a3ff1723300f5b5b",".claude\u002Fskills\u002Fconvex-schema-validator","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN","https:\u002F\u002Fsafetytalent.org","https:\u002F\u002Fgithub.com\u002Fbaisharg","2026-11-13 01:54:27",{"source":8,"skillName":5,"commands":46,"prompt":56,"zipAvailable":57},[47,50,53],{"tool":48,"command":49},"npx","npx skills add baisharg\u002FASTN --skill convex-schema-validator",{"tool":51,"command":52},"bunx","bunx skills add baisharg\u002FASTN --skill convex-schema-validator",{"tool":54,"command":55},"pnpm","pnpm dlx skills add baisharg\u002FASTN --skill convex-schema-validator","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fconvex-schema-validator\u002FSKILL.md",true,[59,65,71,75,79],{"path":60,"resourceType":61,"resourceTypeDesc":62,"sizeBytes":63,"sourceHtmlUrl":64},"kendex.settings.toml.example","OTHER","其他",2453,"https:\u002F\u002Fgithub.com\u002Fvanillagreencom\u002Fkendex\u002Fblob\u002Fmain\u002Fskills\u002Fdev\u002Fkendex.settings.toml.example",{"path":66,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":69,"sourceHtmlUrl":70},"README.md","REFERENCE","参考文档",2649,"https:\u002F\u002Fgithub.com\u002Fvanillagreencom\u002Fkendex\u002Fblob\u002Fmain\u002Fskills\u002Fdev\u002FREADME.md",{"path":72,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":73,"sourceHtmlUrl":74},"workflows\u002Fdev-fix.md",11711,"https:\u002F\u002Fgithub.com\u002Fvanillagreencom\u002Fkendex\u002Fblob\u002Fmain\u002Fskills\u002Fdev\u002Fworkflows\u002Fdev-fix.md",{"path":76,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":77,"sourceHtmlUrl":78},"workflows\u002Fdev-implement.md",33863,"https:\u002F\u002Fgithub.com\u002Fvanillagreencom\u002Fkendex\u002Fblob\u002Fmain\u002Fskills\u002Fdev\u002Fworkflows\u002Fdev-implement.md",{"path":80,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":81,"sourceHtmlUrl":82},"workflows\u002Flong-running-validation.md",5113,"https:\u002F\u002Fgithub.com\u002Fvanillagreencom\u002Fkendex\u002Fblob\u002Fmain\u002Fskills\u002Fdev\u002Fworkflows\u002Flong-running-validation.md",[84,102,116,128,142,159],{"skillNo":85,"name":86,"nameZh":6,"description":87,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":88,"categories":89,"keywords":95,"publishedAt":29},"01M6CCE5DVER8DQWJDY51HAJF7","schema-builder","Design and generate Convex database schemas with proper validation, indexes, and relationships. Use when creating schema.ts or modifying table definitions.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fschema-builder\u002FSKILL.md",[90],{"categoryNo":91,"categoryName":92,"taxonomyType":93,"taxonomyTypeDesc":94},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",[96,20,97,28,23,98,99,24,100,19,101],"schema builder","builder","use","table","design","generate",{"skillNo":103,"name":104,"nameZh":6,"description":105,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":106,"categories":107,"keywords":108,"publishedAt":29},"01M6CCE36AA57CG36R4VHKFRXV","integration-tanstack-start","PostHog integration for TanStack Start full-stack applications","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fposthog-integration-tanstack-start\u002FSKILL.md",[],[109,110,111,112,113,114,115],"integration","start","tanstack","integration tanstack start","posthog","applications","stack",{"skillNo":117,"name":118,"nameZh":6,"description":119,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":120,"categories":121,"keywords":122,"publishedAt":29},"01M6CCE0FK6N8XAB093X7TTQ4F","migration-helper","Plan and execute Convex schema migrations safely, including adding fields, creating tables, and data transformations. Use when schema changes affect existing data.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fmigration-helper\u002FSKILL.md",[],[26,123,124,125,126,127,98,20,27,19],"migration helper","schema migrations safely","data","plan","execute",{"skillNo":129,"name":130,"nameZh":6,"description":131,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":132,"categories":133,"keywords":134,"publishedAt":29},"01M6CCDY3644G8WXXP0BDNMZPR","gsd:sprint","Autonomous phase execution with Codex validation. Runs plan\u002Fexecute\u002Fverify cycles in a bash loop until done or halted.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-sprint\u002FSKILL.md",[],[130,135,136,23,110,137,138,139,140,126,141,127],"resume","codex","execution","verify","Bash","loop","autonomous",{"skillNo":143,"name":144,"nameZh":6,"description":145,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":146,"categories":147,"keywords":148,"publishedAt":29},"01M6CCDVTZRTFWWGREMV6FF956","gsd:second-opinion","Get a second opinion from OpenAI Codex (GPT-5.2-codex) on code, architecture, or implementation decisions. Use when you want independent verification or an alternative perspective.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-second-opinion\u002FSKILL.md",[],[149,150,151,136,152,153,154,155,156,157,98,158],"gsd:second opinion","second opinion","question","architecture","code","verification","openai","gpt","decisions","implementation",{"skillNo":160,"name":161,"nameZh":6,"description":162,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":163,"categories":164,"keywords":165,"publishedAt":29},"01M6CCDSDPYYNEDERHP01N30PT","gsd:milestone-sprint","Run entire milestone autonomously with Codex validation. Auto-detects current milestone, executes all phases, and runs audit.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-milestone-sprint\u002FSKILL.md",[],[166,167,23,168,169,170,135,136],"gsd:milestone sprint","run","audit","interactive","auto"]