
Scheduling - Automate Exports and Reminders Without a Backend
A schedule is just a timer plus an action you already have. SvGrid's client-side scheduler fires cron and one-off triggers to run exports and toast alerts, no job runner required.
Two requests turn up in almost every data app the moment it goes into daily use: "email me this report every weekday at 17:30" and "remind the desk at 09:00 to reconcile." The reflex is to stand up a backend job runner. But look closely at what those tasks actually are in a long-lived app - a dashboard that stays open on a wall screen, an ops console a team lives in all day - and each is just a timer plus an action you already have: an export, or an alert.
SvGrid's Scheduling module, part of the paid @svgrid/enterprise add-on, supplies exactly the missing middle. It gives you a pure cron matcher and a small client-side runtime that fires your callback when a schedule comes due, and it leaves the action to you. A scheduled report reuses the same export path as the toolbar button; a scheduled alert reuses the same toast you already call on save. Nothing new to learn on the action side - only a trigger.
The one honest caveat, stated up front
Schedules run in the browser tab, so the app has to be open when a schedule is due. That is not a limitation to apologize for - it is the whole design. A huge share of "scheduled" work in real apps is interactive and local: download this file, show that reminder, refresh this view for the always-on dashboard. For those, a server cron is overkill and adds infrastructure you then have to run, secure, and monitor. When you genuinely need delivery with nobody watching - nightly emails to people who are asleep, an authoritative audit trail, fan-out to many recipients - keep a server job for those and let the client own the interactive ones. The two compose cleanly.
A schedule is plain data
A schedule is a small object you can store in app state, a database row, or a saved view. It fires recurring on a cron expression or once at an ISO datetime:
import type { Schedule } from '@svgrid/enterprise'
const schedules: Schedule[] = [
{ id: 'eod', name: 'End-of-day CSV', cron: '30 17 * * 1-5' }, // weekdays 17:30
{ id: 'standup', name: 'Stand-up reminder', cron: '0 9 * * *' }, // daily 09:00
{ id: 'launch', name: 'Go-live snapshot', runAt: '2026-08-01T08:00:00' }, // once
{ id: 'audit', name: 'Weekly audit', cron: '0 6 * * 1', enabled: false }, // paused
]
That enabled: false matters more than it looks. Being able to keep a schedule around while switched off - no code edit, no deletion - is the difference between a toy and something a team actually manages.
Wiring it up
createScheduler ticks on an interval, fires onFire for everything due in the current minute, and guarantees at most one fire per schedule per minute - and exactly one, ever, for a one-off. Start it when the view mounts, stop it on teardown:
<script lang="ts">
import { createScheduler } from '@svgrid/enterprise'
import { toast, type SvGridApi } from '@svgrid/grid'
let api = $state<SvGridApi | null>(null)
$effect(() => {
if (!api) return
const scheduler = createScheduler({
schedules,
onFire(schedule) {
if (schedule.id === 'eod') {
api!.exportCsv({ filename: 'end-of-day' }) // the same export as the button
toast.success('End-of-day report downloaded')
} else {
toast.info(schedule.name ?? 'Reminder', { duration: 0 })
}
},
})
scheduler.start()
return () => scheduler.stop() // timer lifetime tied to the component
})
</script>
<SvGrid {data} {columns} {features} onApiReady={(a) => (api = a)} />
Returning scheduler.stop from the $effect ties the timer's lifetime to the component, so there is no leaked interval when the view unmounts. That is the kind of detail that separates a code sample from something you can ship.
Reports: schedule the view, not a frozen query
Any export the grid can do on demand, it can do on a schedule, because the callback simply calls the export API. CSV, TSV, and JSON are free in @svgrid/grid; Excel, PDF, and styled HTML come from @svgrid/enterprise and reuse the same call site:
onFire(schedule) {
if (schedule.id !== 'eod') return
api.exportCsv({ filename: 'eod', rows: 'all' }) // free
// await exportGrid(api, { format: 'xlsx', filename: 'eod' }) // enterprise
}
Because the export defaults to the current view, a scheduled report honors whatever filters and sort the user left in place. You are scheduling the view, not a query frozen at config time - so the 17:30 CSV reflects the same data the analyst was looking at, not a stale snapshot.
Alerts: a reminder is a toast on a timer
Scheduled alerts do not need a data-change trigger. A daily stand-up nudge, a market-open banner, an end-of-shift prompt - each is a toast fired on a cron match, sticky until dismissed with duration: 0:
onFire(schedule) {
if (schedule.id === 'standup') {
toast.info('Daily stand-up in 5 minutes', { title: schedule.name, duration: 0 })
}
}
Mount one <SvToaster /> near your app root and the queue renders itself, screen-reader announcements included.
Cron, without the folklore
Scheduling parses standard 5-field cron - *, lists (1,15), ranges (1-5), and steps (*/15). Day-of-week is 0-6 with Sunday as 0 (and 7 also accepted for Sunday). It even honors the genuinely surprising rule most hand-rolled matchers get wrong: when both day-of-month and day-of-week are restricted, cron fires if either matches, so 0 9 1 * 1 means "09:00 on the 1st or any Monday."
| Cron | Fires |
|---|---|
*/15 * * * * |
Every 15 minutes |
0 9 * * 1-5 |
Weekdays at 09:00 |
30 17 * * 1-5 |
Weekdays at 17:30 |
0 0 * * * |
Daily at midnight |
0 6 1 * * |
The 1st of each month at 06:00 |
The same list ships as CRON_PRESETS for populating a picker, and a malformed expression throws at setup time rather than silently failing at 3am - so a typo surfaces where you can see it, not in a missed report the next morning.
Testable by construction
Everything except the runtime's timer is pure and clock-injectable. nextRun and the scheduler's upcoming() feed a "Next run" column or a management panel, and isScheduleDue lets you unit-test your schedules against a fixed instant with no wall-clock flakiness:
import { isScheduleDue, nextRun } from '@svgrid/enterprise'
isScheduleDue({ id: 'eod', cron: '30 17 * * 1-5' }, new Date('2026-07-27T17:30')) // true
nextRun({ id: 'eod', cron: '30 17 * * 1-5' }, new Date()) // next weekday 17:30, or null
That injectable clock is why the module ships with a full test suite that runs in milliseconds instead of waiting for real minutes to tick by.
The shape of the idea
Scheduling is a good reminder that the most useful features are often not new machinery but a thin connector between things you already have. SvGrid already exports and already alerts. Scheduling is the twenty lines of trigger that turn "the user clicks the button" into "it happens on its own at 17:30" - without a server, a queue, or a cron daemon to babysit. For the always-on dashboard, that is not a compromise. It is the whole job, done where the data already lives.
Scheduling is available in @svgrid/enterprise. See the Scheduling tutorial for the full API and a schedules-panel walkthrough.
Related reading
- Export a Svelte Data Grid to Excel, CSV, and PDF
- Pivot Tables in Svelte - Summarize Data Without a Spreadsheet
- Evaluating SVAR Svelte DataGrid? What Changes if You Pick SvGrid
- Bundle Size of Svelte Data Grids - How to Compare
- Saved Views - Persist Grid Layout and Filters
Tagged: Export