
npm install @convex-dev/cronsThis Convex component provides functionality for registering and managing cron jobs at runtime. Convex comes with built-in support for cron jobs but they must be statically defined at deployment time. This library allows for dynamic registration of cron jobs at runtime.
// Register a cron to run once per day. const daily = await crons.register( ctx, { kind: "cron", cronspec: "0 0 * * *" }, internal.example.logStuff, { message: "daily cron" } ); // Register a cron to run every hour. const hourly = await crons.register( ctx, { kind: "interval", ms: 3600000 }, internal.example.logStuff, { message: "hourly cron" } ); It supports intervals in milliseconds as well as cron schedules with the same format as the unix cron command:
* * * * * * ┬ ┬ ┬ ┬ ┬ ┬ │ │ │ │ │ | │ │ │ │ │ └── day of week (0 - 7, 1L - 7L) (0 or 7 is Sun) │ │ │ │ └───── month (1 - 12) │ │ │ └──────── day of month (1 - 31, L) │ │ └─────────── hour (0 - 23) │ └────────────── minute (0 - 59) └───────────────── second (0 - 59, optional) The design of this component is based on the Cronvex demo app that's described in this Stack post.
You'll need an existing Convex project to use the component. Convex is a hosted backend platform, including a database, serverless functions, and a ton more you can learn about here.
Run npm create convex or follow any of the quickstarts to set one up.
Install the component package:
npm install @convex-dev/crons Create a convex.config.ts file in your app's convex/ folder and install the component by calling use:
// convex/convex.config.ts import { defineApp } from "convex/server"; import crons from "@convex-dev/crons/convex.config"; const app = defineApp(); app.use(crons); export default app; A Crons wrapper can be instantiated within your Convex code as:
import { components } from "./_generated/api"; import { Crons } from "@convex-dev/crons"; const crons = new Crons(components.crons); The Crons wrapper class provides the following methods:
register(ctx, schedule, fn, args, name?): Registers a new cron job.get(ctx, { name | id }): Gets a cron job by name or ID.list(ctx): Lists all cron jobs.delete(ctx, { name | id }): Deletes a cron job by name or ID.Example usage:
import { v } from "convex/values"; import { internalMutation } from "./_generated/server"; import { internal } from "./_generated/api"; // Dummy function that we're going to schedule. export const logStuff = internalMutation({ args: { message: v.string(), }, handler: async (_ctx, args) => { console.log(args.message); }, }); // Run a bunch of cron operations as a test. Note that this function runs as a // transaction and cleans up after itself so you won't actually see these crons // showing up in the database while it's in progress. export const doSomeStuff = internalMutation({ handler: async (ctx) => { // Register some crons. const namedCronId = await crons.register( ctx, { kind: "interval", ms: 3600000 }, internal.example.logStuff, { message: "Hourly cron test" }, "hourly-test" ); console.log("Registered new cron job with ID:", namedCronId); const unnamedCronId = await crons.register( ctx, { kind: "cron", cronspec: "0 * * * *" }, internal.example.logStuff, { message: "Minutely cron test" } ); console.log("Registered new cron job with ID:", unnamedCronId); // Get the cron job by name. const cronByName = await crons.get(ctx, { name: "hourly-test" }); console.log("Retrieved cron job by name:", cronByName); // Get the cron job by ID. const cronById = await crons.get(ctx, { id: unnamedCronId }); console.log("Retrieved cron job by ID:", cronById); // List all cron jobs. const allCrons = await crons.list(ctx); console.log("All cron jobs:", allCrons); // Delete the cron jobs. await crons.delete(ctx, { name: "hourly-test" }); console.log("Deleted cron job by name:", "hourly-test"); await crons.delete(ctx, { id: unnamedCronId }); console.log("Deleted cron job by ID:", unnamedCronId); // Verify deletion. const deletedCronByName = await crons.get(ctx, { name: "hourly-test" }); console.log("Deleted cron job (should be null):", deletedCronByName); const deletedCronById = await crons.get(ctx, { id: unnamedCronId }); console.log("Deleted cron job (should be null):", deletedCronById); }, }); If you'd like to statically define cronjobs like in the built-in crons.ts Convex feature you can do so via an init script that idempotently registers a cron with a given name. e.g., in an init.ts file that gets run on every deploy via convex dev --run init.
// Register a daily cron job. This could be called from an init script to make // sure it's always registered, like the built-in crons in Convex. export const registerDailyCron = internalMutation({ handler: async (ctx) => { if ((await crons.get(ctx, { name: "daily" })) === null) { await crons.register( ctx, { kind: "cron", cronspec: "0 0 * * *" }, internal.example.logStuff, { message: "daily cron", }, "daily" ); } }, }); Crons are created transactionally and will be guaranteed to exist after the mutation that creates them has run. It's thus possible to write workflows like the following that schedules a cron and then deletes itself as soon as it runs, without any additional error handling about the cron not existing.
// This will schedule a cron job to run every 10 seconds but then delete itself // the first time it runs. export const selfDeletingCron = internalMutation({ handler: async (ctx) => { const cronId = await crons.register( ctx, { kind: "interval", ms: 10000 }, internal.example.deleteSelf, { name: "self-deleting-cron" }, "self-deleting-cron" ); console.log("Registered self-deleting cron job with ID:", cronId); }, }); // Worker function that deletes a cron job. export const deleteSelf = internalMutation({ args: { name: v.string() }, handler: async (ctx, { name }) => { console.log("Self-deleting cron job running. Name:", name); await crons.delete(ctx, { name }); console.log("Self-deleting cron job has been deleted. Name:", name); }, });