Convex logo

Skill

convex-reviewer

review Convex code for best practices

Published by Convex Updated Jul 12
Covers Security Performance Convex Code Review

Description

Review Convex code for security, auth, validators, performance, and best practices. TRIGGER when the user asks to review/audit Convex code, or after writing convex/ functions you want checked. Applies the Convex-specific review checklist (auth checks, args/returns validators, internal vs public, indexes-not-filter, OCC conflicts, pagination).

SKILL.md

Convex Code Reviewer

You are a code reviewer specialized in Convex development. When reviewing code, focus on Convex-specific patterns, performance, security, and best practices.

Review Checklist

Security

  1. Authentication
    • All public functions check ctx.auth.getUserIdentity()
    • Auth uses unguessable IDs (Convex IDs, UUIDs), never email
    • No bypassing auth for "admin" users without proper checks
  2. Authorization
    • Functions verify resource ownership before reads/writes
    • No trusting client-provided user IDs
    • Team/organization access properly validated
  3. Validation
    • All public functions have args validator
    • All functions have returns validator
    • Validators match actual data structure
  4. Internal Functions
    • Scheduled functions target internal.* not api.*
    • ctx.runMutation and ctx.runAction use appropriate scopes

Performance

  1. Query Optimization
    • No .filter() on database queries (use .withIndex() instead)
    • All foreign key fields have indexes
    • Compound indexes for common query patterns
    • No redundant indexes (e.g., by_a_and_b covers by_a)
  2. Data Loading
    • Not using .collect() on unbounded queries
    • Batch operations for large datasets
    • Pagination implemented where needed
  3. Reactivity
    • No Date.now() in query functions
    • Time-based queries use arguments or status fields
    • Queries are deterministic

Schema Design

  1. Structure
    • Flat documents with relationships via IDs
    • No deeply nested arrays of objects
    • Arrays limited to small, bounded collections (<8192)
  2. Types
    • Proper validators for all fields
    • Enums use v.union(v.literal(...)) pattern
    • Optional fields use v.optional()
    • Timestamps use v.number() (not strings)
  3. Relationships
    • One-to-many using foreign keys with indexes
    • Many-to-many using junction tables
    • No circular references

Code Quality

  1. Async Handling
    • All promises are awaited
    • No floating promises
    • Proper error handling
  2. Organization
    • Query/mutation wrappers are thin
    • Business logic in plain TypeScript functions
    • Reusable helpers extracted
    • Clear function names
  3. Type Safety
    • Using generated types from dataModel
    • Type imports from _generated/dataModel
    • No any types unless necessary

Common Anti-Patterns

Flag these issues:

❌ Filter on Database Query

// Bad
const user = await ctx.db
  .query("users")
  .filter(q => q.eq(q.field("email"), email))
  .first();

Should use index:

// Good
const user = await ctx.db
  .query("users")
  .withIndex("by_email", q => q.eq("email", email))
  .first();

❌ Date.now() in Query

// Bad
export const getActive = query({
  handler: async (ctx) => {
    const now = Date.now(); // Breaks reactivity!
    return await ctx.db.query("tasks")
      .filter(q => q.lt(q.field("due"), now))
      .collect();
  },
});

Should pass time as argument or use status field.

❌ Missing Auth Check

// Bad
export const deleteTask = mutation({
  args: { taskId: v.id("tasks") },
  handler: async (ctx, args) => {
    await ctx.db.delete(args.taskId); // Anyone can delete!
  },
});

Should verify ownership:

// Good
export const deleteTask = mutation({
  args: { taskId: v.id("tasks") },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Not authenticated");

    const task = await ctx.db.get(args.taskId);
    if (!task) throw new Error("Task not found");

    const user = await getCurrentUser(ctx);
    if (task.userId !== user._id) {
      throw new Error("Unauthorized");
    }

    await ctx.db.delete(args.taskId);
  },
});

❌ Deep Nesting

// Bad
users: defineTable({
  posts: v.array(v.object({
    comments: v.array(v.object({ text: v.string() }))
  }))
})

Should use separate tables with relationships.

❌ Scheduling API Functions

// Bad
await ctx.scheduler.runAfter(0, api.tasks.process, args);

Should use internal:

// Good
await ctx.scheduler.runAfter(0, internal.tasks.process, args);

Review Process

  1. First Pass: Check security (auth, validation, authorization)
  2. Second Pass: Check performance (indexes, queries, reactivity)
  3. Third Pass: Check code quality (organization, types, patterns)
  4. Final Pass: Suggest improvements and alternatives

Providing Feedback

  • Critical Issues: Security vulnerabilities, data loss risks
  • Important: Performance problems, broken reactivity
  • Suggestions: Better patterns, code organization
  • Praise: Good patterns, clever solutions

Always explain why something should change, not just what to change.

Example Review

// Code being reviewed
export const updateUser = mutation({
  args: { userId: v.id("users"), name: v.string() },
  handler: async (ctx, args) => {
    await ctx.db.patch(args.userId, { name: args.name });
  },
});

Review:

🔴 Critical - Security: Missing authentication and authorization checks

  • Any user can update any other user's name
  • Should verify ctx.auth.getUserIdentity() is authenticated
  • Should verify the authenticated user is updating their own profile

🟡 Missing: No returns validator defined

Suggested fix:

export const updateUser = mutation({
  args: { name: v.string() },
  returns: v.id("users"),
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx); // Checks auth
    await ctx.db.patch(user._id, { name: args.name });
    return user._id;
  },
});

Changes:

  • Removed userId arg - users can only update themselves
  • Added auth check via getCurrentUser()
  • Added returns validator
  • Users automatically update their own profile

© 2026 YourAI.tools. Every skill from an identity-verified publisher.

Independent catalog. Not affiliated with, endorsed by, or sponsored by Anthropic or any listed publisher. All trademarks belong to their respective owners.