Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
waynesutton avatar

Convex Cron Jobs

  • 2.5k installs
  • 401 repo stars
  • Updated February 6, 2026
  • waynesutton/convexskills

convex-cron-jobs documents Convex scheduled function patterns using interval and cron expression scheduling in crons.ts.

About

The convex-cron-jobs skill documents scheduled function patterns for Convex applications including interval scheduling, cron expressions, job monitoring, retry strategies, and long-running task best practices. It directs agents to fetch latest docs from docs.convex.dev scheduling pages before implementing rather than assuming API details. Basic setup uses cronJobs from convex/server with crons.interval for simple recurring tasks and crons.cron for standard five-field cron strings. Examples cover cleanup every hour, daily midnight UTC reports, five-minute external data sync, and thirty-second health checks as the minimum interval. The skill covers cron expression scheduling, internal function references, argument passing, monitoring via the Convex dashboard, failure retries, and patterns for cleanup, data syncing, and automated workflows. Developers use it when adding recurring background tasks to Convex backends without manual external schedulers or separate cron infrastructure.

  • Convex crons.ts setup with cronJobs, interval, and cron expression APIs.
  • Minimum interval thirty seconds; hourly cleanup and daily UTC cron examples.
  • Requires fetching latest docs.convex.dev scheduling docs before implementing.
  • Internal function references via internal namespace for scheduled handlers.
  • Dashboard monitoring and automatic retry on scheduled function failure.

Convex Cron Jobs by the numbers

  • 2,494 all-time installs (skills.sh)
  • +35 installs in the week ending Aug 2, 2026 (Skillselion tracking)
  • Ranked #217 of 4,348 Backend & APIs skills by installs in the Skillselion catalog
  • Security screen: LOW risk (skills.sh audit)
  • Data as of Aug 3, 2026 (Skillselion catalog sync)
At a glance

convex-cron-jobs capabilities & compatibility

Capabilities
crons.interval and crons.cron configuration patt · internal function scheduling with typed argument · cron expression and minimum interval guidance · cleanup, sync, and health check job examples · documentation fetch requirement before implement
Use cases
orchestration · api development · devops
From the docs

What convex-cron-jobs says it does

Before implementing, do not assume; fetch the latest documentation
SKILL.md
Every 30 seconds (minimum interval)
SKILL.md
Convex cron jobs allow you to schedule functions to run at regular intervals or specific times.
SKILL.md
npx skills add https://github.com/waynesutton/convexskills --skill convex-cron-jobs

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs2.5k
repo stars401
Security audit3 / 3 scanners passed
Last updatedFebruary 6, 2026
Repositorywaynesutton/convexskills

How do I schedule recurring cleanup or sync jobs in a Convex backend?

Schedule recurring Convex functions with interval and cron expression patterns for background jobs and cleanup tasks.

Who is it for?

Convex applications needing scheduled background tasks without external cron infrastructure.

Skip if: Skip for one-off manual function calls, non-Convex backends, or client-side timers only.

When should I use this skill?

User asks about Convex cron jobs, scheduled functions, interval scheduling, or background cleanup in Convex.

What you get

A crons.ts file with interval or cron schedules pointing to internal Convex functions.

  • crons.ts schedule definitions
  • recurring backend job configuration

Files

SKILL.mdMarkdownGitHub ↗

Convex Cron Jobs

Schedule recurring functions for background tasks, cleanup jobs, data syncing, and automated workflows in Convex applications.

Documentation Sources

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

  • Primary: https://docs.convex.dev/scheduling/cron-jobs
  • Scheduling Overview: https://docs.convex.dev/scheduling
  • Scheduled Functions: https://docs.convex.dev/scheduling/scheduled-functions
  • For broader context: https://docs.convex.dev/llms.txt

Instructions

Cron Jobs Overview

Convex cron jobs allow you to schedule functions to run at regular intervals or specific times. Key features:

  • Run functions on a fixed schedule
  • Support for interval-based and cron expression scheduling
  • Automatic retries on failure
  • Monitoring via the Convex dashboard

Basic Cron Setup

// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

// Run every hour
crons.interval(
  "cleanup expired sessions",
  { hours: 1 },
  internal.tasks.cleanupExpiredSessions,
  {}
);

// Run every day at midnight UTC
crons.cron(
  "daily report",
  "0 0 * * *",
  internal.reports.generateDailyReport,
  {}
);

export default crons;

Interval-Based Scheduling

Use crons.interval for simple recurring tasks:

// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

// Every 5 minutes
crons.interval(
  "sync external data",
  { minutes: 5 },
  internal.sync.fetchExternalData,
  {}
);

// Every 2 hours
crons.interval(
  "cleanup temp files",
  { hours: 2 },
  internal.files.cleanupTempFiles,
  {}
);

// Every 30 seconds (minimum interval)
crons.interval(
  "health check",
  { seconds: 30 },
  internal.monitoring.healthCheck,
  {}
);

export default crons;

Cron Expression Scheduling

Use crons.cron for precise scheduling with cron expressions:

// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

// Every day at 9 AM UTC
crons.cron(
  "morning notifications",
  "0 9 * * *",
  internal.notifications.sendMorningDigest,
  {}
);

// Every Monday at 8 AM UTC
crons.cron(
  "weekly summary",
  "0 8 * * 1",
  internal.reports.generateWeeklySummary,
  {}
);

// First day of every month at midnight
crons.cron(
  "monthly billing",
  "0 0 1 * *",
  internal.billing.processMonthlyBilling,
  {}
);

// Every 15 minutes
crons.cron(
  "frequent sync",
  "*/15 * * * *",
  internal.sync.syncData,
  {}
);

export default crons;

Cron Expression Reference

┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-6, Sunday=0)
│ │ │ │ │
* * * * *

Common patterns:

  • * * * * * - Every minute
  • 0 * * * * - Every hour
  • 0 0 * * * - Every day at midnight
  • 0 0 * * 0 - Every Sunday at midnight
  • 0 0 1 * * - First day of every month
  • */5 * * * * - Every 5 minutes
  • 0 9-17 * * 1-5 - Every hour from 9 AM to 5 PM, Monday through Friday

Internal Functions for Crons

Cron jobs should call internal functions for security:

// convex/tasks.ts
import { internalMutation, internalQuery } from "./_generated/server";
import { v } from "convex/values";

// Cleanup expired sessions
export const cleanupExpiredSessions = internalMutation({
  args: {},
  returns: v.number(),
  handler: async (ctx) => {
    const oneHourAgo = Date.now() - 60 * 60 * 1000;
    
    const expiredSessions = await ctx.db
      .query("sessions")
      .withIndex("by_lastActive")
      .filter((q) => q.lt(q.field("lastActive"), oneHourAgo))
      .collect();

    for (const session of expiredSessions) {
      await ctx.db.delete(session._id);
    }

    return expiredSessions.length;
  },
});

// Process pending tasks
export const processPendingTasks = internalMutation({
  args: {},
  returns: v.null(),
  handler: async (ctx) => {
    const pendingTasks = await ctx.db
      .query("tasks")
      .withIndex("by_status", (q) => q.eq("status", "pending"))
      .take(100);

    for (const task of pendingTasks) {
      await ctx.db.patch(task._id, {
        status: "processing",
        startedAt: Date.now(),
      });
      
      // Schedule the actual processing
      await ctx.scheduler.runAfter(0, internal.tasks.processTask, {
        taskId: task._id,
      });
    }

    return null;
  },
});

Cron Jobs with Arguments

Pass static arguments to cron jobs:

// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

// Different cleanup intervals for different types
crons.interval(
  "cleanup temp files",
  { hours: 1 },
  internal.cleanup.cleanupByType,
  { fileType: "temp", maxAge: 3600000 }
);

crons.interval(
  "cleanup cache files",
  { hours: 24 },
  internal.cleanup.cleanupByType,
  { fileType: "cache", maxAge: 86400000 }
);

export default crons;
// convex/cleanup.ts
import { internalMutation } from "./_generated/server";
import { v } from "convex/values";

export const cleanupByType = internalMutation({
  args: {
    fileType: v.string(),
    maxAge: v.number(),
  },
  returns: v.number(),
  handler: async (ctx, args) => {
    const cutoff = Date.now() - args.maxAge;
    
    const oldFiles = await ctx.db
      .query("files")
      .withIndex("by_type_and_created", (q) => 
        q.eq("type", args.fileType).lt("createdAt", cutoff)
      )
      .collect();

    for (const file of oldFiles) {
      await ctx.storage.delete(file.storageId);
      await ctx.db.delete(file._id);
    }

    return oldFiles.length;
  },
});

Monitoring and Logging

Add logging to track cron job execution:

// convex/tasks.ts
import { internalMutation } from "./_generated/server";
import { v } from "convex/values";

export const cleanupWithLogging = internalMutation({
  args: {},
  returns: v.null(),
  handler: async (ctx) => {
    const startTime = Date.now();
    let processedCount = 0;
    let errorCount = 0;

    try {
      const expiredItems = await ctx.db
        .query("items")
        .withIndex("by_expiresAt")
        .filter((q) => q.lt(q.field("expiresAt"), Date.now()))
        .collect();

      for (const item of expiredItems) {
        try {
          await ctx.db.delete(item._id);
          processedCount++;
        } catch (error) {
          errorCount++;
          console.error(`Failed to delete item ${item._id}:`, error);
        }
      }

      // Log job completion
      await ctx.db.insert("cronLogs", {
        jobName: "cleanup",
        startTime,
        endTime: Date.now(),
        duration: Date.now() - startTime,
        processedCount,
        errorCount,
        status: errorCount === 0 ? "success" : "partial",
      });
    } catch (error) {
      // Log job failure
      await ctx.db.insert("cronLogs", {
        jobName: "cleanup",
        startTime,
        endTime: Date.now(),
        duration: Date.now() - startTime,
        processedCount,
        errorCount,
        status: "failed",
        error: String(error),
      });
      throw error;
    }

    return null;
  },
});

Batching for Large Datasets

Handle large datasets in batches to avoid timeouts:

// convex/tasks.ts
import { internalMutation } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";

const BATCH_SIZE = 100;

export const processBatch = internalMutation({
  args: {
    cursor: v.optional(v.string()),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    const result = await ctx.db
      .query("items")
      .withIndex("by_status", (q) => q.eq("status", "pending"))
      .paginate({ numItems: BATCH_SIZE, cursor: args.cursor ?? null });

    for (const item of result.page) {
      await ctx.db.patch(item._id, {
        status: "processed",
        processedAt: Date.now(),
      });
    }

    // Schedule next batch if there are more items
    if (!result.isDone) {
      await ctx.scheduler.runAfter(0, internal.tasks.processBatch, {
        cursor: result.continueCursor,
      });
    }

    return null;
  },
});

External API Calls in Crons

Use actions for external API calls:

// convex/sync.ts
"use node";

import { internalAction } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";

export const syncExternalData = internalAction({
  args: {},
  returns: v.null(),
  handler: async (ctx) => {
    // Fetch from external API
    const response = await fetch("https://api.example.com/data", {
      headers: {
        Authorization: `Bearer ${process.env.API_KEY}`,
      },
    });

    if (!response.ok) {
      throw new Error(`API request failed: ${response.status}`);
    }

    const data = await response.json();

    // Store the data using a mutation
    await ctx.runMutation(internal.sync.storeExternalData, {
      data,
      syncedAt: Date.now(),
    });

    return null;
  },
});

export const storeExternalData = internalMutation({
  args: {
    data: v.any(),
    syncedAt: v.number(),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    await ctx.db.insert("externalData", {
      data: args.data,
      syncedAt: args.syncedAt,
    });
    return null;
  },
});
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

crons.interval(
  "sync external data",
  { minutes: 15 },
  internal.sync.syncExternalData,
  {}
);

export default crons;

Examples

Schema for Cron Job Logging

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

export default defineSchema({
  cronLogs: defineTable({
    jobName: v.string(),
    startTime: v.number(),
    endTime: v.number(),
    duration: v.number(),
    processedCount: v.number(),
    errorCount: v.number(),
    status: v.union(
      v.literal("success"),
      v.literal("partial"),
      v.literal("failed")
    ),
    error: v.optional(v.string()),
  })
    .index("by_job", ["jobName"])
    .index("by_status", ["status"])
    .index("by_startTime", ["startTime"]),

  sessions: defineTable({
    userId: v.id("users"),
    token: v.string(),
    lastActive: v.number(),
    expiresAt: v.number(),
  })
    .index("by_user", ["userId"])
    .index("by_lastActive", ["lastActive"])
    .index("by_expiresAt", ["expiresAt"]),

  tasks: defineTable({
    type: v.string(),
    status: v.union(
      v.literal("pending"),
      v.literal("processing"),
      v.literal("completed"),
      v.literal("failed")
    ),
    data: v.any(),
    createdAt: v.number(),
    startedAt: v.optional(v.number()),
    completedAt: v.optional(v.number()),
  })
    .index("by_status", ["status"])
    .index("by_type_and_status", ["type", "status"]),
});

Complete Cron Configuration Example

// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

// Cleanup jobs
crons.interval(
  "cleanup expired sessions",
  { hours: 1 },
  internal.cleanup.expiredSessions,
  {}
);

crons.interval(
  "cleanup old logs",
  { hours: 24 },
  internal.cleanup.oldLogs,
  { maxAgeDays: 30 }
);

// Sync jobs
crons.interval(
  "sync user data",
  { minutes: 15 },
  internal.sync.userData,
  {}
);

// Report jobs
crons.cron(
  "daily analytics",
  "0 1 * * *",
  internal.reports.dailyAnalytics,
  {}
);

crons.cron(
  "weekly summary",
  "0 9 * * 1",
  internal.reports.weeklySummary,
  {}
);

// Health checks
crons.interval(
  "service health check",
  { minutes: 5 },
  internal.monitoring.healthCheck,
  {}
);

export default crons;

Best Practices

  • Never run npx convex deploy unless explicitly instructed
  • Never run any git commands unless explicitly instructed
  • Only use crons.interval or crons.cron methods, not deprecated helpers
  • Always call internal functions from cron jobs for security
  • Import internal from _generated/api even for functions in the same file
  • Add logging and monitoring for production cron jobs
  • Use batching for operations that process large datasets
  • Handle errors gracefully to prevent job failures
  • Use meaningful job names for dashboard visibility
  • Consider timezone when using cron expressions (Convex uses UTC)

Common Pitfalls

1. Using public functions - Cron jobs should call internal functions only 2. Long-running mutations - Break large operations into batches 3. Missing error handling - Unhandled errors will fail the entire job 4. Forgetting timezone - All cron expressions use UTC 5. Using deprecated helpers - Avoid crons.hourly, crons.daily, etc. 6. Not logging execution - Makes debugging production issues difficult

References

  • Convex Documentation: https://docs.convex.dev/
  • Convex LLMs.txt: https://docs.convex.dev/llms.txt
  • Cron Jobs: https://docs.convex.dev/scheduling/cron-jobs
  • Scheduling Overview: https://docs.convex.dev/scheduling
  • Scheduled Functions: https://docs.convex.dev/scheduling/scheduled-functions

Related skills

FAQ

What is the minimum scheduling interval?

Thirty seconds is the minimum interval for crons.interval scheduling.

Should agents assume Convex cron API details?

No. Fetch latest documentation from docs.convex.dev scheduling pages before implementing.

How are scheduled handlers referenced?

Use internal namespace references such as internal.tasks.cleanupExpiredSessions in crons.ts.

Is Convex Cron Jobs safe to install?

skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.