Skip to content
Back to skills

Convex Core

ASecurity

Core Convex development guidelines - functions, validators, schema, queries, mutations, and database patterns

  • 57 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 17, 2026
ai-agentstypescriptapidatabase

Works with

  • cursor
  • api

Security analysis

A100/100

Scanned October 17, 2026

npx -y skills add diegosouzapw/awesome-omni-skill --skill convex-core --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Convex Core?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Convex Core
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/diegosouzapw-convex-core/badge)](https://www.skillsdirectory.com/skills/diegosouzapw-convex-core)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: convex-core
description: Core Convex development guidelines - functions, validators, schema, queries, mutations, and database patterns
globs:
  - "convex/**/*.ts"
  - "convex/schema.ts"
triggers:
  - convex
  - query
  - mutation
  - schema
  - validator
  - index
  - database
  - ctx.db
  - defineTable
  - defineSchema
---

# Convex Core Development Guide

Complete guidelines for Convex functions, validators, schema, queries, mutations, and database patterns.

---

# Function Syntax (REQUIRED)

ALWAYS use the new function syntax:

```typescript
import { query } from "./_generated/server";
import { v } from "convex/values";

export const myFunction = query({
  args: {
    // Arguments with validators
  },
  returns: v.null(), // Return validator REQUIRED
  handler: async (ctx, args) => {
    // Function body
  },
});
```

---

# Validators Reference

| Type | Validator | Notes |
|------|-----------|-------|
| Id | `v.id(tableName)` | Document ID |
| Null | `v.null()` | Use instead of undefined |
| Int64 | `v.int64()` | NOT v.bigint() (deprecated) |
| Float64 | `v.number()` | |
| Boolean | `v.boolean()` | |
| String | `v.string()` | Max 1MB UTF-8 |
| Bytes | `v.bytes()` | Max 1MB |
| Array | `v.array(values)` | Max 8192 elements |
| Object | `v.object({...})` | Max 1024 entries |
| Record | `v.record(keys, values)` | Dynamic keys |
| Optional | `v.optional(validator)` | |
| Union | `v.union(v1, v2, ...)` | |
| Literal | `v.literal("value")` | For discriminated unions |

**NOT SUPPORTED:** `v.map()`, `v.set()`, `v.bigint()`

---

# Function Registration

## Public Functions
```typescript
import { query, mutation, action } from "./_generated/server";
import { api } from "./_generated/api";
// Reference: api.filename.functionName
```

## Internal Functions (Private)
```typescript
import { internalQuery, internalMutation, internalAction } from "./_generated/server";
import { internal } from "./_generated/api";
// Reference: internal.filename.functionName
```

## Critical Rules
- ALWAYS include `args` and `returns` validators
- If no return value, use `returns: v.null()`
- File-based routing: `convex/users.ts` → `api.users.functionName`

---

# Schema Definition

```typescript
// 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(),
    role: v.optional(v.union(v.literal("admin"), v.literal("user"))),
  })
    .index("by_email", ["email"])
    .index("by_role", ["role"]),

  messages: defineTable({
    userId: v.id("users"),
    content: v.string(),
    channelId: v.id("channels"),
  })
    .index("by_channel", ["channelId"])
    .index("by_user_and_channel", ["userId", "channelId"]),
});
```

## System Fields (Automatic)
- `_id`: `v.id(tableName)`
- `_creationTime`: `v.number()`

---

# Index Rules (CRITICAL)

```typescript
// WRONG - Will cause error!
.index("by_creation_time", ["_creationTime"])  // Built-in, don't add
.index("by_author_and_time", ["author", "_creationTime"])  // _creationTime is automatic

// CORRECT
.index("by_author", ["author"])  // _creationTime added automatically
.index("by_channel_and_author", ["channelId", "authorId"])
```

- Index names describe all fields: `by_field1_and_field2`
- Query order must match index order
- Never include `_creationTime` in index definition

---

# Query Patterns

## Using Indexes (REQUIRED)
```typescript
// CORRECT - Use withIndex
const messages = await ctx.db
  .query("messages")
  .withIndex("by_channel", (q) => q.eq("channelId", channelId))
  .order("desc")
  .take(10);

// WRONG - Never use filter()
const messages = await ctx.db
  .query("messages")
  .filter((q) => q.eq(q.field("channelId"), channelId))  // BAD!
  .collect();
```

## Get by ID
```typescript
const user = await ctx.db.get(userId);
if (!user) throw new Error("User not found");
```

## Ordering
```typescript
.order("asc")   // Ascending (default)
.order("desc")  // Descending
```

## Collecting Results
```typescript
.collect()     // Get all results
.take(n)       // Get first n results
.first()       // Get first result or null
.unique()      // Get single result, throws if multiple
```

---

# Mutations

```typescript
// Insert - returns Id
const id = await ctx.db.insert("users", { name: "Alice", email: "alice@example.com" });

// Patch - partial update
await ctx.db.patch(userId, { name: "Bob" });

// Replace - full replacement
await ctx.db.replace(userId, { name: "Bob", email: "bob@example.com" });

// Delete
await ctx.db.delete(userId);
```

## Delete Pattern (No .delete() on queries)
```typescript
const items = await ctx.db
  .query("items")
  .withIndex("by_user", (q) => q.eq("userId", userId))
  .collect();

for (const item of items) {
  await ctx.db.delete(item._id);
}
```

---

# Function Calling

```typescript
// From mutation or action
const user = await ctx.runQuery(api.users.get, { id: userId });
await ctx.runMutation(internal.users.update, { id: userId, name });

// From action only
await ctx.runAction(internal.ai.generate, { prompt });
```

## Type Annotation for Same-File Calls
```typescript
export const f = query({
  args: { name: v.string() },
  returns: v.string(),
  handler: async (ctx, args) => "Hello " + args.name,
});

export const g = query({
  args: {},
  returns: v.null(),
  handler: async (ctx) => {
    const result: string = await ctx.runQuery(api.example.f, { name: "Bob" });
    return null;
  },
});
```

---

# Pagination

```typescript
import { paginationOptsValidator } from "convex/server";

export const list = query({
  args: {
    paginationOpts: paginationOptsValidator,
    channelId: v.id("channels"),
  },
  handler: async (ctx, args) => {
    return await ctx.db
      .query("messages")
      .withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
      .order("desc")
      .paginate(args.paginationOpts);
  },
});
// Returns: { page, isDone, continueCursor }
```

---

# Full Text Search

```typescript
// Schema
defineTable({
  body: v.string(),
  channel: v.string(),
}).searchIndex("search_body", {
  searchField: "body",
  filterFields: ["channel"],
})

// Query
const results = await ctx.db
  .query("messages")
  .withSearchIndex("search_body", (q) =>
    q.search("body", "hello").eq("channel", "#general")
  )
  .take(10);
```

---

# System Limits

| Limit | Value |
|-------|-------|
| Function args/returns | 8 MiB |
| Array elements | 8192 |
| Object entries | 1024 |
| Document size | 1 MiB |
| Query/Mutation timeout | 1 second |
| DB read per query | 8 MiB / 16384 docs |
| DB write per mutation | 8 MiB / 8192 docs |

---

# TypeScript Types

```typescript
import { Id, Doc } from "./_generated/dataModel";

// Use Id<> for document IDs
function getUser(userId: Id<"users">): Promise<Doc<"users"> | null>

// Record with Id keys
const map: Record<Id<"users">, string> = {};

// Arrays with explicit types
const items: Array<{ id: Id<"items">; name: string }> = [];
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.