Edit on val.town ↗

Run TypeScript on a schedule

The usual way to run a scheduled TypeScript script is to keep a machine on: a script run with bun or tsx, a node-schedule timer or a crontab entry, and a process manager to restart it when it dies. On Val Town the scheduler is part of the platform — you write the function, set the schedule, and there's no runtime to host and no process to keep alive.

Here's the 30-second version:

  1. Create a val and add a file.
  2. Click the + button in the top right of the editor and select CRON.
  3. Paste in your TypeScript and click Run to test it.
  4. Click the Cron trigger and set the schedule — a simple interval like "once an hour", or a cron expression.

A cron-triggered file exports a function that receives an Interval object:

export default async function (interval: Interval) {
  console.log("Cron val ran!");
}

The Interval type has this shape:

interface Interval {
  lastRunAt: Date | undefined;
}

Anything you return is ignored — output goes to the val's logs, or wherever your code sends it.

This job fetches GitHub's status API on every run and logs the current status, so the val's logs become a history of outages:

export default async function (interval: Interval) {
  const res = await fetch("https://www.githubstatus.com/api/v2/status.json");
  const { status } = await res.json();
  console.log(`GitHub status: ${status.description}`);
}

Paste it into a cron-triggered file, click Run, and check the logs. Any TypeScript works here — npm: imports included — so the body of your existing script usually drops in unchanged.

interval.lastRunAt is the time of the previous run (undefined on the first one), which is all you need for incremental jobs — polling a feed, processing new records, diffing since last time:

export default async function (interval: Interval) {
  const since = interval.lastRunAt ?? new Date(0);
  // fetch items and keep only the ones newer than `since`
}

Last updated