convex-cron-jobs
About
Convex Cron Jobs provides scheduled function patterns for background tasks like cleanup jobs and data syncing in Convex applications. It enables interval scheduling, cron expressions, job monitoring, and retry strategies for automated workflows. Use this skill to implement and manage reliable, recurring background tasks.
Quick Install
Claude Code
Recommended/plugin add https://github.com/majiayu000/claude-skill-registrygit clone https://github.com/majiayu000/claude-skill-registry.git ~/.claude/skills/convex-cron-jobsCopy and paste this command in Claude Code to install this skill
Documentation
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 minute0 * * * *- Every hour0 0 * * *- Every day at midnight0 0 * * 0- Every Sunday at midnight0 0 1 * *- First day of every month*/5 * * * *- Every 5 minutes0 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 deployunless explicitly instructed - Never run any git commands unless explicitly instructed
- Only use
crons.intervalorcrons.cronmethods, not deprecated helpers - Always call internal functions from cron jobs for security
- Import
internalfrom_generated/apieven 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
- Using public functions - Cron jobs should call internal functions only
- Long-running mutations - Break large operations into batches
- Missing error handling - Unhandled errors will fail the entire job
- Forgetting timezone - All cron expressions use UTC
- Using deprecated helpers - Avoid
crons.hourly,crons.daily, etc. - 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
GitHub Repository
Related Skills
content-collections
MetaThis skill provides a production-tested setup for Content Collections, a TypeScript-first tool that transforms Markdown/MDX files into type-safe data collections with Zod validation. Use it when building blogs, documentation sites, or content-heavy Vite + React applications to ensure type safety and automatic content validation. It covers everything from Vite plugin configuration and MDX compilation to deployment optimization and schema validation.
sglang
MetaSGLang is a high-performance LLM serving framework that specializes in fast, structured generation for JSON, regex, and agentic workflows using its RadixAttention prefix caching. It delivers significantly faster inference, especially for tasks with repeated prefixes, making it ideal for complex, structured outputs and multi-turn conversations. Choose SGLang over alternatives like vLLM when you need constrained decoding or are building applications with extensive prefix sharing.
Algorithmic Art Generation
MetaThis skill helps developers create algorithmic art using p5.js, focusing on generative art, computational aesthetics, and interactive visualizations. It automatically activates for topics like "generative art" or "p5.js visualization" and guides you through creating unique algorithms with features like seeded randomness, flow fields, and particle systems. Use it when you need to build reproducible, code-driven artistic patterns.
cloudflare-turnstile
MetaThis skill provides comprehensive guidance for implementing Cloudflare Turnstile as a CAPTCHA-alternative bot protection system. It covers integration for forms, login pages, API endpoints, and frameworks like React/Next.js/Hono, while handling invisible challenges that maintain user experience. Use it when migrating from reCAPTCHA, debugging error codes, or implementing token validation and E2E tests.
