skillZs
★ LIVE SKILL TAGS ★
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
※ REAL INSTALL DATA ※
← back to all skills
get-convex/agent-skills567 installs

convex-helpers-guide

Discover and use convex-helpers utilities for relationships, filtering, sessions, custom functions, and more. Use when you need pre-built Convex patterns.

How do I install this agent skill?

npx skills add https://github.com/get-convex/agent-skills --skill convex-helpers-guide
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    No files were provided for analysis. This skill is empty and contains no executable code or instructions.

  • Socketpass

    No alerts

  • Snykpass

    Risk: LOW · No issues

  • Runlayerwarn

    1/1 file flagged

What does this agent skill do?

Convex Helpers Guide

Use convex-helpers to add common patterns and utilities to your Convex backend without reinventing the wheel.

What is convex-helpers?

convex-helpers is the official collection of utilities that complement Convex. It provides battle-tested patterns for common backend needs.

Installation:

npm install convex-helpers

Available Helpers

1. Relationship Helpers

Traverse relationships between tables in a readable, type-safe way.

Use when:

  • Loading related data across tables
  • Following foreign key relationships
  • Building nested data structures

Example:

import { getOneFrom, getManyFrom } from "convex-helpers/server/relationships";

export const getTaskWithUser = query({
  args: { taskId: v.id("tasks") },
  handler: async (ctx, args) => {
    const task = await ctx.db.get(args.taskId);
    if (!task) return null;

    // Get related user
    const user = await getOneFrom(
      ctx.db,
      "users",
      "by_id",
      task.userId,
      "_id"
    );

    // Get related comments
    const comments = await getManyFrom(
      ctx.db,
      "comments",
      "by_task",
      task._id,
      "taskId"
    );

    return { ...task, user, comments };
  },
});

Key Functions:

  • getOneFrom - Get single related document
  • getManyFrom - Get multiple related documents
  • getManyVia - Get many-to-many relationships through junction table

2. Custom Functions (Data Protection) - MOST IMPORTANT

This is Convex's alternative to Row Level Security (RLS). Instead of database-level policies, use custom function wrappers to automatically add auth and access control to all queries and mutations.

Create wrapped versions of query/mutation/action with custom behavior.

Use when:

  • Data protection and access control (PRIMARY USE CASE)
  • Want to add auth logic to all functions
  • Multi-tenant applications
  • Role-based access control (RBAC)
  • Need to inject common data into ctx
  • Building internal-only functions
  • Adding logging/monitoring to all functions

Why this instead of RLS:

  • TypeScript, not SQL policies
  • Full type safety
  • Easy to test and debug
  • More flexible than database policies
  • Works across your entire backend

Example: Custom Query with Auto-Auth

// convex/lib/customFunctions.ts
import { customQuery } from "convex-helpers/server/customFunctions";
import { query } from "../_generated/server";

export const authenticatedQuery = customQuery(
  query,
  {
    args: {}, // No additional args required
    input: async (ctx, args) => {
      const identity = await ctx.auth.getUserIdentity();
      if (!identity) {
        throw new Error("Not authenticated");
      }

      const user = await ctx.db
        .query("users")
        .withIndex("by_token", q =>
          q.eq("tokenIdentifier", identity.tokenIdentifier)
        )
        .unique();

      if (!user) throw new Error("User not found");

      // Add user to context
      return { ctx: { ...ctx, user }, args };
    },
  }
);

// Usage in your functions
export const getMyTasks = authenticatedQuery({
  handler: async (ctx) => {
    // ctx.user is automatically available!
    return await ctx.db
      .query("tasks")
      .withIndex("by_user", q => q.eq("userId", ctx.user._id))
      .collect();
  },
});

Example: Multi-Tenant Data Protection

import { customQuery } from "convex-helpers/server/customFunctions";
import { query } from "../_generated/server";

// Organization-scoped query - automatic access control
export const orgQuery = customQuery(query, {
  args: { orgId: v.id("organizations") },
  input: async (ctx, args) => {
    const user = await getCurrentUser(ctx);

    // Verify user is a member of this organization
    const member = await ctx.db
      .query("organizationMembers")
      .withIndex("by_org_and_user", q =>
        q.eq("orgId", args.orgId).eq("userId", user._id)
      )
      .unique();

    if (!member) {
      throw new Error("Not authorized for this organization");
    }

    // Inject org context
    return {
      ctx: {
        ...ctx,
        user,
        orgId: args.orgId,
        role: member.role
      },
      args
    };
  },
});

// Usage - data automatically scoped to organization
export const getOrgProjects = orgQuery({
  args: { orgId: v.id("organizations") },
  handler: async (ctx) => {
    // ctx.user and ctx.orgId automatically available and verified!
    return await ctx.db
      .query("projects")
      .withIndex("by_org", q => q.eq("orgId", ctx.orgId))
      .collect();
  },
});

Example: Role-Based Access Control

import { customMutation } from "convex-helpers/server/customFunctions";
import { mutation } from "../_generated/server";

export const adminMutation = customMutation(mutation, {
  args: {},
  input: async (ctx, args) => {
    const user = await getCurrentUser(ctx);

    if (user.role !== "admin") {
      throw new Error("Admin access required");
    }

    return { ctx: { ...ctx, user }, args };
  },
});

// Usage - only admins can call this
export const deleteUser = adminMutation({
  args: { userId: v.id("users") },
  handler: async (ctx, args) => {
    // Only admins reach this code
    await ctx.db.delete(args.userId);
  },
});

3. Filter Helper

Apply complex TypeScript filters to database queries.

Use when:

  • Need to filter by computed values
  • Filtering logic is too complex for indexes
  • Working with small result sets

Example:

import { filter } from "convex-helpers/server/filter";

export const getActiveTasks = query({
  handler: async (ctx) => {
    const now = Date.now();
    const threeDaysAgo = now - 3 * 24 * 60 * 60 * 1000;

    return await filter(
      ctx.db.query("tasks"),
      (task) =>
        !task.completed &&
        task.createdAt > threeDaysAgo &&
        task.priority === "high"
    ).collect();
  },
});

Note: Still prefer indexes when possible! Use filter for complex logic that can't be indexed.

4. Sessions

Track users across requests even when not logged in.

Use when:

  • Need to track anonymous users
  • Building shopping cart for guests
  • Tracking user behavior before signup
  • A/B testing without auth

Setup:

// convex/sessions.ts
import { SessionIdArg } from "convex-helpers/server/sessions";
import { query } from "./_generated/server";

export const trackView = query({
  args: {
    ...SessionIdArg, // Adds sessionId: v.string()
    pageUrl: v.string(),
  },
  handler: async (ctx, args) => {
    await ctx.db.insert("pageViews", {
      sessionId: args.sessionId,
      pageUrl: args.pageUrl,
      timestamp: Date.now(),
    });
  },
});

Client (React):

import { useSessionId } from "convex-helpers/react/sessions";
import { useQuery } from "convex/react";
import { api } from "../convex/_generated/api";

function MyComponent() {
  const sessionId = useSessionId();

  // Automatically includes sessionId in all requests
  useQuery(api.sessions.trackView, {
    sessionId,
    pageUrl: window.location.href,
  });
}

5. Zod Validation

Use Zod schemas instead of Convex validators.

Use when:

  • Already using Zod in your project
  • Want more complex validation logic
  • Need custom error messages

Example:

import { zCustomQuery } from "convex-helpers/server/zod";
import { z } from "zod";
import { query } from "./_generated/server";

const argsSchema = z.object({
  email: z.string().email(),
  age: z.number().min(18).max(120),
});

export const createUser = zCustomQuery(query, {
  args: argsSchema,
  handler: async (ctx, args) => {
    // args is typed from Zod schema
    return await ctx.db.insert("users", args);
  },
});

6. Alternative: Row-Level Security Helper

Note: Convex recommends using custom functions (see #2 above) as the primary data protection pattern. This RLS helper is an alternative approach that mimics traditional RLS.

However, custom functions are usually better because:

  • Type-safe at compile time (RLS is runtime)
  • More explicit (easy to see what auth is applied)
  • Better error messages
  • Easier to test

7. Migrations

Run data migrations safely.

Use when:

  • Backfilling new fields
  • Transforming existing data
  • Moving between schema versions

Example:

import { makeMigration } from "convex-helpers/server/migrations";

export const addDefaultPriority = makeMigration({
  table: "tasks",
  migrateOne: async (ctx, doc) => {
    if (doc.priority === undefined) {
      await ctx.db.patch(doc._id, { priority: "medium" });
    }
  },
});

// Run: npx convex run migrations:addDefaultPriority

8. Triggers

Execute code automatically when data changes.

Use when:

  • Sending notifications on data changes
  • Updating related records
  • Logging changes
  • Maintaining computed fields

Example:

import { Triggers } from "convex-helpers/server/triggers";

const triggers = new Triggers();

triggers.register("tasks", "insert", async (ctx, task) => {
  // Send notification when task is created
  await ctx.db.insert("notifications", {
    userId: task.userId,
    type: "task_created",
    taskId: task._id,
  });
});

Common Patterns

Pattern 1: Authenticated Queries with User Context

import { customQuery } from "convex-helpers/server/customFunctions";

export const authedQuery = customQuery(query, {
  args: {},
  input: async (ctx, args) => {
    const user = await getCurrentUser(ctx);
    return { ctx: { ...ctx, user }, args };
  },
});

// Now all queries automatically have user in context
export const getMyData = authedQuery({
  handler: async (ctx) => {
    // ctx.user is typed and available!
    return await ctx.db
      .query("data")
      .withIndex("by_user", q => q.eq("userId", ctx.user._id))
      .collect();
  },
});

Pattern 2: Loading Related Data

import { getOneFrom, getManyFrom } from "convex-helpers/server/relationships";

export const getPostWithDetails = query({
  args: { postId: v.id("posts") },
  handler: async (ctx, args) => {
    const post = await ctx.db.get(args.postId);
    if (!post) return null;

    const author = await getOneFrom(ctx.db, "users", "by_id", post.authorId, "_id");
    const comments = await getManyFrom(ctx.db, "comments", "by_post", post._id, "postId");

    const tagLinks = await getManyFrom(ctx.db, "postTags", "by_post", post._id, "postId");
    const tags = await Promise.all(
      tagLinks.map(link =>
        getOneFrom(ctx.db, "tags", "by_id", link.tagId, "_id")
      )
    );

    return { ...post, author, comments, tags };
  },
});

Pattern 3: Batch Operations with Error Handling

import { asyncMap } from "convex-helpers";

export const batchUpdateTasks = mutation({
  args: {
    taskIds: v.array(v.id("tasks")),
    status: v.string(),
  },
  handler: async (ctx, args) => {
    const results = await asyncMap(args.taskIds, async (taskId) => {
      try {
        const task = await ctx.db.get(taskId);
        if (task) {
          await ctx.db.patch(taskId, { status: args.status });
          return { success: true, taskId };
        }
        return { success: false, taskId, error: "Not found" };
      } catch (error) {
        return { success: false, taskId, error: error.message };
      }
    });

    return results;
  },
});

When to Use What

NeedUseImport From
Load related datagetOneFrom, getManyFromconvex-helpers/server/relationships
Auth in all functionscustomQueryconvex-helpers/server/customFunctions
Complex filtersfilterconvex-helpers/server/filter
Anonymous usersuseSessionIdconvex-helpers/react/sessions
Zod validationzCustomQueryconvex-helpers/server/zod
Data migrationsmakeMigrationconvex-helpers/server/migrations
TriggersTriggersconvex-helpers/server/triggers

Checklist

  • Installed convex-helpers: npm install convex-helpers
  • Using relationship helpers for related data
  • Created custom functions for common auth patterns
  • Using sessions for anonymous tracking (if needed)
  • Prefer indexes over filter when possible
  • Check convex-helpers docs for new utilities

Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.

<a href="https://skillzs.dev/skills/get-convex/agent-skills/convex-helpers-guide">View convex-helpers-guide on skillZs</a>