OpenClaw
Deploy a managed OpenClaw agent in 60 seconds
Launch on Hostinger →
Hermes Agent
Run your Hermes agent, fully managed
Launch on Hostinger →
Apify
6,000+ web scrapers for your agent, free to start
Try Apify free →
Firecrawl
Crawl and scrape any site into clean data
Try Firecrawl free →
Context.dev
One API to scrape, enrich, and extract the web
Start building free →
SetupClaw
Done-for-you OpenClaw for founders and teams
Get it set up for you →
DataForSEO
SEO data APIs for your agent, $1 free credit
Try DataForSEO free →
Your product here
Reach thousands of AI builders a month
Learn more →
Claude Market
Menu
SkillsMCPPluginsSubmit MCPSkillPluginMCPMCP, plugin, or skillAdvertise
Claude Market
SkillsMCPPluginsSubmit MCPSkillPluginMCPMCP, plugin, or skillAdvertise
Skills/waynesutton/convexskills/convex-schema-validator
convex-schema-validator logo

convex-schema-validator

waynesutton/convexskills
2K installs402 stars
Run it on Hostinger, 20% off →Your friend gets 20% off too, using this linkFree API →|View on GitHub|Create your own skill →

Installation

npx skills add https://github.com/waynesutton/convexskills --skill convex-schema-validator

Summary

Defining and validating database schemas with proper typing, index configuration, optional fields, unions, and migration strategies for schema changes

SKILL.md

Convex Schema Validator

Define and validate database schemas in Convex with proper typing, index configuration, optional fields, unions, and strategies for schema migrations.

Documentation Sources

Before implementing, do not assume; fetch the latest documentation:

  • Primary: https://docs.convex.dev/database/schemas
  • Indexes: https://docs.convex.dev/database/indexes
  • Data Types: https://docs.convex.dev/database/types
  • For broader context: https://docs.convex.dev/llms.txt

Instructions

Basic Schema Definition

// 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 Types

ValidatorTypeScript TypeExample
v.string()string"hello"
v.number()number42, 3.14
v.boolean()booleantrue, false
v.null()nullnull
v.int64()bigint9007199254740993n
v.bytes()ArrayBufferBinary 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()anyAny value
v.record(k, v)Record<K, V>Dynamic keys

Index Configuration

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"],
    }),
});

Complex Types

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()),
  }),
});

Discriminated Unions

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"]),
});

Optional vs Nullable Fields

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())),
  }),
});

Index Naming Convention

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"]),
});

Schema Migration Strategies

Adding New Fields
// 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
})
Backfilling Data
// 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.dicebear.com/7.x/initials/svg?seed=${user.name}`,
      });
    }

    return users.length;
  },
});
Making Optional Fields Required
// Step 1: Backfill all null values
// Step 2: Update schema to required
users: defineTable({
  name: v.string(),
  email: v.string(),
  avatarUrl: v.string(), // Now required after backfill
})

Examples

Complete E-commerce Schema

// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    email: v.string(),
    name: v.string(),
    role: v.union(v.literal("customer"), v.literal("admin")),
    createdAt: v.number(),
  })
    .index("by_email", ["email"])
    .index("by_role", ["role"]),

  products: defineTable({
    name: v.string(),
    description: v.string(),
    price: v.number(),
    category: v.string(),
    inventory: v.number(),
    isActive: v.boolean(),
  })
    .index("by_category", ["category"])
    .index("by_active_and_category", ["isActive", "category"])
    .searchIndex("search_products", {
      searchField: "name",
      filterFields: ["category", "isActive"],
    }),

  orders: defineTable({
    userId: v.id("users"),
    items: v.array(v.object({
      productId: v.id("products"),
      quantity: v.number(),
      priceAtPurchase: v.number(),
    })),
    total: v.number(),
    status: v.union(
      v.literal("pending"),
      v.literal("paid"),
      v.literal("shipped"),
      v.literal("delivered"),
      v.literal("cancelled")
    ),
    shippingAddress: v.object({
      street: v.string(),
      city: v.string(),
      state: v.string(),
      zip: v.string(),
      country: v.string(),
    }),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index("by_user", ["userId"])
    .index("by_user_and_status", ["userId", "status"])
    .index("by_status", ["status"]),

  reviews: defineTable({
    productId: v.id("products"),
    userId: v.id("users"),
    rating: v.number(),
    comment: v.optional(v.string()),
    createdAt: v.number(),
  })
    .index("by_product", ["productId"])
    .index("by_user", ["userId"]),
});

Using Schema Types in Functions

// convex/products.ts
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import { Doc, Id } from "./_generated/dataModel";

// Use Doc type for full documents
type Product = Doc<"products">;

// Use Id type for references
type ProductId = Id<"products">;

export const get = query({
  args: { productId: v.id("products") },
  returns: v.union(
    v.object({
      _id: v.id("products"),
      _creationTime: v.number(),
      name: v.string(),
      description: v.string(),
      price: v.number(),
      category: v.string(),
      inventory: v.number(),
      isActive: v.boolean(),
    }),
    v.null()
  ),
  handler: async (ctx, args): Promise<Product | null> => {
    return await ctx.db.get(args.productId);
  },
});

Best Practices

  • Never run npx convex deploy unless explicitly instructed
  • Never run any git commands unless explicitly instructed
  • Always define explicit schemas rather than relying on inference
  • Use descriptive index names that include all indexed fields
  • Start with optional fields when adding new columns
  • Use discriminated unions for polymorphic data
  • Validate data at the schema level, not just in functions
  • Plan index strategy based on query patterns

Common Pitfalls

  1. Missing indexes for queries - Every withIndex needs a corresponding schema index
  2. Wrong index field order - Fields must be queried in order defined
  3. Using v.any() excessively - Lose type safety benefits
  4. Not making new fields optional - Breaks existing data
  5. Forgetting system fields - _id and _creationTime are automatic

References

  • Convex Documentation: https://docs.convex.dev/
  • Convex LLMs.txt: https://docs.convex.dev/llms.txt
  • Schemas: https://docs.convex.dev/database/schemas
  • Indexes: https://docs.convex.dev/database/indexes
  • Data Types: https://docs.convex.dev/database/types

Score

0–100
63/ 100

Grade

C

Popularity15/30

2,443 installs — growing adoption.

Completeness27/30

Documented: full SKILL.md body, description, one-line install. Missing: category/license metadata.

Trust15/25

Community skill with a public GitHub source repository you can review.

Freshness6/15

No update timestamp is tracked for this skill in our catalog.

Scored automatically from popularity, completeness, trust, and freshness — computed only from data in our catalog, never fabricated.

Proud of your score? Add this badge to your README.

Paste a snippet into your GitHub README. The badge updates automatically and links back to this page.

Convex Schema Validator skill score badge previewScore badge

Markdown

[![Convex Schema Validator skill](https://www.claudemarket.ai/skills/waynesutton/convexskills/convex-schema-validator/badges/score.svg)](https://www.claudemarket.ai/skills/waynesutton/convexskills/convex-schema-validator)

HTML

<a href="https://www.claudemarket.ai/skills/waynesutton/convexskills/convex-schema-validator"><img src="https://www.claudemarket.ai/skills/waynesutton/convexskills/convex-schema-validator/badges/score.svg" alt="Convex Schema Validator skill"/></a>

Convex Schema Validator FAQ

How do I install the Convex Schema Validator skill?

Run “npx skills add https://github.com/waynesutton/convexskills --skill convex-schema-validator” in your terminal. The skill is added to your agent's skills directory and picked up automatically on the next run — no restart or extra configuration needed.

What does the Convex Schema Validator skill do?

Defining and validating database schemas with proper typing, index configuration, optional fields, unions, and migration strategies for schema changes The full SKILL.md on this page shows the exact instructions the skill gives your agent.

Is the Convex Schema Validator skill free?

Yes. Convex Schema Validator is a free, open-source skill published from waynesutton/convexskills. As with any third-party skill, review the source repository before installing it into an agent with sensitive access.

Does Convex Schema Validator work with Claude Code and OpenClaw?

Yes. Skills use the portable SKILL.md format, so Convex Schema Validator works with Claude Code, OpenClaw, Codex, Hermes, and any other agent that reads SKILL.md skills.

Recommended skills

Browse all →
convex-quickstart logo

convex-quickstart

get-convex/agent-skills

97K installsInstall
convex-create-component logo

convex-create-component

get-convex/agent-skills

96K installsInstall
convex-performance-audit logo

convex-performance-audit

get-convex/agent-skills

94K installsInstall
convex-setup-auth logo

convex-setup-auth

get-convex/agent-skills

94K installsInstall
convex-migration-helper logo

convex-migration-helper

get-convex/agent-skills

94K installsInstall
find-skills logo

find-skills

vercel-labs/skills

2.8M installsInstall

Related guides

Hand-picked reading to help you choose, install, and use agent skills.

GuideHow To Use Openclaw Skills For Database MigrationsGuideBest Openclaw Skills 2026GuideHow To Evaluate Openclaw Skill Before Installing

Skills by category

FrontendBackend & APIsTesting & QASecurityDevOps & CI/CDMCP & ToolingAutomationData & Analysis+20 more

MCP servers by category

AI & MLDeveloper ToolsVector & MemoryFiles & DocsDatabasesFinance & PaymentsBrowser & ScrapingCommunication+8 more

Plugins by category

developmentproductivitycommunicationdesignsecuritydatabaseworkflowcompliance+34 more

The Agent Stack

Weekly Claude Code, Agent SDK, and MCP moves worth your time — free.

Claude Market

AI agent skills directory, marketplace, and workflow hub for OpenClaw, Hermes Agent, Claude Code, Codex, and MCP-powered operator stacks.

Independent project, not affiliated with Anthropic.

Resources

  • Browse Skills
  • Browse MCP Servers
  • Browse Plugins

More

  • Submit a Tool
  • Create a Skill
  • Advertise
  • Free Tools
  • API
  • Shipping
  • Contact
  • Terms
  • Privacy
© 2026 Claude Market · Not affiliated with Anthropic
Fazier badgeFeatured on Twelve ToolsFeatured on Wired BusinessRemote OpenClaw - Featured on AI Agents DirectoryListed on Turbo0Featured on Uneed