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

vercel-schedules

Vercel Schedules guidance — run a private function, route, or Vercel Queues topic on a cron cadence or once at a set time. Covers static schedules in vercel.json, dynamic per-user schedules with @vercel/schedules, handleSchedule, the vercel schedules CLI, environments, jitter, and migrating from Cron Jobs. Use when adding scheduled or recurring work, one-time reminders, or replacing `crons`.

How do I install this agent skill?

npx skills add https://github.com/vercel/vercel-plugin --skill vercel-schedules
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    This skill provides guidance on using Vercel Schedules for recurring and one-time tasks. It covers static configuration in `vercel.json`, dynamic scheduling with the `@vercel/schedules` SDK, and security best practices like using private function targets and environment-based secrets. No security issues were detected.

  • Socketpass

    No alerts

  • Snykpass

    Risk: LOW · No issues

What does this agent skill do?

Vercel Schedules

You are an expert in Vercel Schedules, which runs code on a recurring cron cadence or once at a specific time.

Choose static or dynamic

StaticDynamic
Defined invercel.json schedules (or Build Output API config.json)@vercel/schedules SDK or vercel schedules CLI
Takes effectWhen the deployment becomes current for its environment/branchImmediately
Targetsfunction, path, or topictopic only
One-time runsNoYes
Payload / namespaceNo / always defaultOptional JSON (≤ 16 KB; 8 KB with --local) / any namespace
Update or deleteEdit config and redeploySDK or CLI

Use static for jobs that belong to the app (nightly cleanup, hourly sync). Use dynamic for jobs the app creates for users (a reminder per customer, a report at 09:00 in each tenant's timezone).

Choose a target

TargetUse whenRetries
function (default)Any new static schedule. The handler has no public URL, and only Vercel's scheduler can invoke it, so there's no secret to manage.No
topicFailed runs must be retried, you need a payload, or the schedule is dynamic.Yes, via Queues
pathOnly when migrating an existing Cron Job and you don't want to change any code.No

Prefer function over path. A path target exposes your handler to the public internet, and its only protection is a static CRON_SECRET that you have to store, check on every request, and rotate. A function target has neither problem. Don't pick path for new schedules, and don't pick it because a route "already exists". Wrapping the route in handleSchedule and adding the trigger is a small change.

If a failed run must be retried, target a queue topic.

Quickstart: private function on a static schedule

npm i @vercel/schedules
// api/cleanup.ts
import { handleSchedule } from '@vercel/schedules';

export const POST = handleSchedule(async (metadata) => {
  console.log(`${metadata.name} run ${metadata.executionId} for ${metadata.scheduledAt.toISOString()}`);
  await deleteExpiredRecords();
});
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "functions": {
    "api/cleanup.ts": {
      "experimentalTriggers": [{ "type": "schedule/v1beta" }]
    }
  },
  "schedules": {
    "cleanup": {
      "schedule": "0 * * * *",
      "function": "api/cleanup"
    }
  }
}

Test locally, then deploy:

vercel dev                                 # starts a local scheduler
vercel schedules invoke cleanup --local    # second terminal: run it now
vercel deploy --prod
vercel schedules invoke cleanup            # run once in production

Rules that break deployments

  • function is the build output path relative to .vercel/output/functions, without .func — not the source filename. api/jobs/report.ts → api/jobs/report. When unsure, run vercel build and look in .vercel/output/functions.
  • The target function must declare { "type": "schedule/v1beta" }, and it must be its only trigger. It can't also have a queue/v2beta trigger.
  • Don't add routes or rewrites to a schedule/v1beta function. It is private by design.
  • For a function in a service, set service plus function relative to .vercel/output/services/<name>/functions. vercel dev can't run these yet (DEV_SCHEDULE_SERVICE_FUNCTION_UNSUPPORTED).
  • Set exactly one of function, path, or topic.
  • A framework's Build Output API schedules and vercel.json schedules with the same name fail with SCHEDULE_NAME_CONFLICT.

Static schedule properties

PropertyNotes
scheduleRequired. Five-field cron, evaluated in timezone.
timezoneIANA name such as America/New_York. Defaults to UTC. Follows DST.
jitterMinutes, 1–15. Defaults to 1 on Pro/Enterprise. Hobby is always 15.
function / servicePrivate function target (see rules above).
path / requestMethodPublic route target, for code-free Cron Jobs migration only. Path must start with /. Method defaults to POST.
topicVercel Queues topic. Letters, digits, _, -.

The object key is the schedule name: 1–256 chars of ASCII letters, digits, ., _, -, starting with a letter or digit. Keep names stable. Renaming deletes the schedule and creates a new one with fresh state and history. On Hobby, a deployment can declare up to 100 static schedules. On every plan, the CLI rejects a vercel.json with more than 500 schedules entries (Vercel CLI 65.11+; older CLIs cap it at 100).

Cron syntax: digits, *, ,, -, / only. No MON/JAN names, and don't set both day-of-month and day-of-week.

Path targets (Cron Jobs migration only)

Use a path target only to move an existing Cron Job over without touching its route code. For anything else, use a function target. The route stays publicly reachable, so it must check CRON_SECRET on every request:

{
  "schedules": {
    "daily-report": { "schedule": "0 9 * * *", "path": "/api/daily-report", "requestMethod": "GET" }
  }
}
// api/daily-report.ts
export async function GET(request: Request) {
  const cronSecret = process.env.CRON_SECRET;
  if (!cronSecret || request.headers.get('authorization') !== `Bearer ${cronSecret}`) {
    return new Response('Unauthorized', { status: 401 });
  }
  await sendReport();
  return new Response(null, { status: 200 });
}

CRON_SECRET should be a random string of at least 16 characters. handleSchedule also works on path routes (GET or POST), but it does not check CRON_SECRET, so keep the check. Function targets never receive authorization or Cron Job headers, so don't require them there.

Queue targets and dynamic schedules

The consumer is a normal Vercel Queues consumer (⤳ skill: queues):

// api/reminders.ts
import { handleCallback } from '@vercel/queue';

interface ScheduleQueueMessage {
  scheduleId: string;
  executionId: string;
  name: string;
  namespace: string;
  scheduledAt: string; // ISO 8601, excludes jitter
  source: 'static' | 'dynamic';
  payload?: { userId: string };
}

export const POST = handleCallback(async (message: ScheduleQueueMessage) => {
  await sendReminder(message.payload!.userId, message.executionId);
});
{
  "functions": {
    "api/reminders.ts": {
      "experimentalTriggers": [{ "type": "queue/v2beta", "topic": "reminders" }]
    }
  }
}

Create schedules at runtime from app code. The SDK authenticates with the deployment's OIDC token and acts on that deployment's environment or branch:

import { Schedules, SchedulesApiError } from '@vercel/schedules';

// Recurring, per tenant
await Schedules.create({
  namespace: `tenant-${tenantId}`,
  name: 'daily-digest',
  target: { topic: 'digests' },
  expression: { type: 'cron', cron: '0 9 * * *' },
  timezone: tenant.timezone,
  payload: { tenantId },
});

// One-time, local date and time in `timezone` (no Z, no offset)
await Schedules.create({
  name: `reminder-${reminderId}`,
  target: { topic: 'reminders' },
  expression: { type: 'single', at: '2026-11-01T09:00' },
  timezone: 'America/New_York',
  payload: { userId },
});

// Idempotent create
try {
  await Schedules.create({ /* ... */ });
} catch (error) {
  if (!(error instanceof SchedulesApiError && error.code === 'schedule_already_exists')) throw error;
}

Other methods: get, list (paginate with cursor), update (dynamic only, can't switch cron ↔ one-time), enable (optionally until), disable, invoke (run now), delete (dynamic only). Use one namespace per tenant so a tenant's schedules can be listed and cleaned up together.

Handlers must be idempotent

  • A run can be delivered more than once, and queue targets retry. executionId is the same for every delivery of an occurrence. Record it and skip repeats.
  • After an outage, every unexpired occurrence is delivered, so a frequent schedule can fire several times in a row. If the work only makes sense near its scheduled time, compare scheduledAt to now and return early.
  • Runs are independent. A slow run doesn't delay the next one, so runs can overlap. Guard shared resources.
  • Function and path targets are not retried. A thrown error becomes a 500 in execution history, and that's it.

Environments

Vercel keeps separate schedules for production, each custom environment, each Preview branch, and CLI deployments per user.

  • Static schedules apply when a deployment becomes current, including promotions and rollbacks.
  • New static schedules start active in production and inactive in Preview and custom environments. Enable them in the dashboard or with vercel schedules enable <name> --git-branch <branch> --for 2h.
  • Enabled/disabled state persists across later deployments.
  • The CLI defaults to production. Use --environment, --git-branch, --user, or --local to pick another set.

Jitter and plan limits

HobbyPro / Enterprise
Jitter windowAlways 15 min1–15 min, default 1
Most frequent cronEvery 15 minEvery minute
Schedules per team1,00010,000 / 100,000

The shortest interval between runs must be at least the jitter window (jitter_exceeds_cadence). On Hobby, omit jitter or set it to 15. Downgrading to Hobby deletes schedules that run more often than every 15 minutes or set jitter below 15.

Migrate from Cron Jobs

Replace each crons entry with a named schedules entry. Keep path and schedule, and add "requestMethod": "GET", because Cron Jobs send GET and path schedules default to POST. Remove crons in the same change, or the route runs twice. Existing CRON_SECRET checks and vercel-cron/1.0 headers keep working. Leave timezone unset to keep UTC run times. This is the only case where a path target makes sense. If you can change code, go straight to a private function target instead: wrap the handler in handleSchedule, add the schedule/v1beta trigger, replace path/requestMethod with function, drop the CRON_SECRET check, and keep the same name so the schedule keeps its state and history.

Never do this on Vercel

  • node-cron, setInterval, or any in-process scheduler. Functions aren't long-lived, so they never fire reliably.
  • A path target for a new schedule. It exposes the handler publicly and depends on a static CRON_SECRET. Use a function target.
  • A path target with no CRON_SECRET check. Anyone on the internet can trigger it.
  • Renaming a schedule to "fix" it. That resets its state and history.

Manage and debug

vercel schedules list
vercel schedules inspect cleanup --json
vercel schedules executions list cleanup --since 7d
vercel schedules disable cleanup
vercel schedules enable cleanup --for 2h
vercel schedules invoke cleanup
vercel schedules create send-reminder --at 2026-11-01T09:00 --timezone America/New_York --topic reminders

In the dashboard, open the project and select Schedules to see next run times, execution history, and Run Schedule.

Common SDK error codes: invalid_cron_expression, invalid_jitter, jitter_exceeds_cadence, single_at_in_past, payload_too_large, static_schedule (tried to update/delete a static one), rate_limited (wait retryAfter seconds).

Related

  • ⤳ skill: queues for queue consumers, retries, and idempotency
  • ⤳ skill: workflow for multi-step durable logic triggered by a schedule
  • ⤳ skill: vercel-functions for function runtime and duration limits
  • Schedules docs

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/vercel/vercel-plugin/vercel-schedules">View vercel-schedules on skillZs</a>