Cron Jobs
Cron jobs are JavaScript code snippets stored in the database and executed on a schedule using standard cron expressions. They run server-side with access to all platform services.
Database Schema
daas_cron_jobs
| Column | Type | Description |
|---|---|---|
id | uuid | Primary key |
name | text | Human-readable name |
description | text | Optional description |
schedule | text | 5-field cron expression |
timezone | text | IANA timezone (e.g., Asia/Singapore) |
code | text | JavaScript code to execute |
status | text | active or inactive |
timeout_ms | integer | Maximum execution time in ms |
memory_limit_mb | integer | Sandbox memory limit in MB (default 64) |
running | boolean | Lock flag — true while executing |
running_since | timestamptz | When the current run started |
last_run_at | timestamptz | Last execution time |
last_run_status | text | running, success, error, or timeout |
next_run_at | timestamptz | Calculated next run time |
daas_cron_history
| Column | Type | Description |
|---|---|---|
id | uuid | Primary key |
job_id | uuid | FK → daas_cron_jobs |
job_name | text | Job name at time of run (denormalized) |
triggered_at | timestamptz | When the job was scheduled to fire |
started_at | timestamptz | When execution actually began |
finished_at | timestamptz | When execution ended |
duration_ms | integer | Total execution time |
status | text | running, success, error, or timeout |
error | text | Error message if failed |
logs | text[] | Lines captured from console.log |
triggered_by | text | schedule, manual, or extension |
Cron Expression Reference
┌───────── minute (0–59)
│ ┌─────── hour (0–23)
│ │ ┌───── day of month (1–31)
│ │ │ ┌─── month (1–12)
│ │ │ │ ┌─ day of week (0–7, 0=Sun)
│ │ │ │ │
* * * * *| Expression | Meaning |
|---|---|
* * * * * | Every minute |
0 * * * * | Every hour |
0 0 * * * | Every day at midnight |
0 9 * * 1 | Every Monday at 9:00 AM |
*/15 * * * * | Every 15 minutes |
0 0 1 * * | First day of every month |
0 6,18 * * * | 6 AM and 6 PM daily |
Code Sandbox
Each job runs in an isolated sandbox with these globals:
| Available | Not available |
|---|---|
context — job metadata | setTimeout / setInterval |
services — platform services | require / import |
console.log/warn/error/info | eval |
| Standard JS globals | File system access |
Available services
// Items CRUD
const svc = await services.items('articles');
await svc.readByQuery({ filter: { status: { _eq: 'draft' } } });
await svc.createOne({ title: 'Auto-generated' });
await svc.updateOne(id, { processed: true });
await svc.deleteOne(id);
// Schema services
await services.collections();
await services.fields();
await services.relations();
// Files and versions
await services.files();
await services.versions();
// Send email
await services.mail({ to: 'user@example.com', subject: 'Report', html: '<p>Done</p>' });
// Trigger another cron job
await services.cron.trigger('job-id-or-name');
// Whitelisted environment variables
const apiKey = services.env.MY_API_KEY;
// Safe HTTP (domain-restricted — configure EXTENSION_ALLOWED_DOMAINS)
const res = await services.fetch('https://api.example.com/data');
// Custom services
const notifier = await services.custom('slack-notify');
// Raw Supabase (bypasses RLS — use with caution)
const { data } = await services.supabase.from('table').select('*');Example: Cleanup old records
// Delete items older than 30 days
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - 30);
const svc = await services.items('daas_activity');
const old = await svc.readByQuery({
filter: { timestamp: { _lt: cutoff.toISOString() } },
fields: ['id'],
limit: 500,
});
for (const row of old) {
await svc.deleteOne(row.id);
}
console.log(`Cleaned up ${old.length} old activity records`);REST API
| Method | Path | Description |
|---|---|---|
GET | /api/cron | List cron jobs |
POST | /api/cron | Create cron job |
GET | /api/cron/:id | Get cron job |
PATCH | /api/cron/:id | Update cron job |
DELETE | /api/cron/:id | Delete cron job |
POST | /api/cron/:id/run | Trigger job manually |
POST | /api/cron/:id/clone | Clone a job (always created inactive) |
GET | /api/cron/:id/history | Get history for one job |
GET | /api/cron/history | Get all job history |
The running lock prevents concurrent executions of the same job. If a job times out without clearing the lock, use PATCH /api/cron/:id to set running: false.
Clustered Deployments
In production the app runs as a multi-process cluster (one worker per core). The cron scheduler runs on a single leader worker, so each job fires exactly once per tick regardless of worker count; if the leader worker exits, another worker takes over scheduling. Creating, updating, activating, or deleting a job via the API takes effect immediately — the change is relayed to the leader over the cluster message bus, whichever worker served the request.
Cron console.log output is captured into the job’s history (logs), which is stored in the database and visible from any worker. The in-memory Logs page, however, only shows output from the worker serving your request — see Logs.