Skip to main content

Run Cron Jobs with CloudBase Cloud Function Timer Triggers

In one sentence: Add a type: timer configuration with a 7-field cron expression to functions[].triggers in cloudbaserc.json, then run tcb fn deploy to schedule a Cloud Function to trigger at a fixed time — typical use cases are daily reports, data cleanup, and cache warming.

Estimated time: 25 minutes | Difficulty: Basic

Applicable Scenarios

  • Applicable: Batch processing that runs once per day / hour / week at a fixed time (daily reports, reconciliation, cleaning expired data, warming cache)
  • Applicable: Aggregating external async events into daily-level reports
  • Not applicable: Second-level / millisecond-level real-time scheduling (message queues or persistent connections are more appropriate)
  • Not applicable: Critical transactions requiring strict "run exactly once globally" semantics (Timer Triggers may fire twice in edge cases — see "Step 4: Idempotency")

Prerequisites

DependencyVersion
Node.js (Cloud Function runtime)≥ 16.13
@cloudbase/clilatest
@cloudbase/node-sdk3.18.1 (if the scheduled task reads or writes the database)

Also required:

  • An active CloudBase environment ID
  • A cloudbaserc.json at the project root; if not present, run tcb init to generate one

Step 1: Write a Minimal Scheduled Function

Create cloudfunctions/dailyReport/index.js:

const cloudbase = require('@cloudbase/node-sdk');

const app = cloudbase.init({
env: process.env.TCB_ENV || cloudbase.SYMBOL_CURRENT_ENV,
});

const db = app.database();

exports.main = async (event, context) => {
// When triggered by a timer, event contains Type / Time fields (check trigger logs for exact fields)
console.log('[cron] triggered at', new Date().toISOString(), 'event:', event);

const since = new Date(Date.now() - 24 * 3600 * 1000);

const orders = await db
.collection('orders')
.where({
createdAt: db.command.gte(since),
})
.count();

await db.collection('daily_reports').add({
date: new Date().toISOString().slice(0, 10),
orderCount: orders.total,
generatedAt: db.serverDate(),
});

return { ok: true, orderCount: orders.total };
};

cloudfunctions/dailyReport/package.json:

{
"name": "dailyReport",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"@cloudbase/node-sdk": "^3.18.1"
}
}

Step 2: Configure the Timer Trigger

Add a section to cloudbaserc.json in the project root:

{
"envId": "your-env-id",
"functionRoot": "./cloudfunctions",
"functions": [
{
"name": "dailyReport",
"timeout": 60,
"memorySize": 256,
"runtime": "Nodejs16.13",
"handler": "index.main",
"triggers": [
{
"name": "every-day-9am",
"type": "timer",
"config": "0 0 9 * * * *"
}
]
}
]
}

config is a 7-field cron expression with the order "second minute hour day month weekday year". This differs from Linux's 5-field cron (minute hour day month weekday) — a common mistake.

MeaningCron
Every day at 9:00 AM0 0 9 * * * *
Every hour on the hour0 0 * * * * *
Every 5 minutes0 */5 * * * * *
Weekdays 9 AM–6 PM, every hour0 0 9-18 * * 1-5 *
1st of every month at 2:00 AM0 0 2 1 * * *
Every Monday at 10:00 AM0 0 10 * * 1 *

Timezone is as shown in the Console and documentation. CloudBase Timer Trigger timezone is labeled on the Console's "Cloud Functions → Triggers" page. Confirm it before deployment to avoid accidentally scheduling to "5:00 AM" by configuring in UTC.

Step 3: Deploy

tcb login --apiKeyId your-key-id --apiKey your-key
tcb fn deploy dailyReport --httpFn -e your-env-id

After deployment, go to Console → Cloud Functions → dailyReport → Triggers to verify that the every-day-9am trigger configured in cloudbaserc.json appears.

If the trigger was not auto-created, sync it separately:

tcb fn trigger create -e your-env-id

(Command subject to the current version of the CloudBase CLI documentation.)

To temporarily disable, click "Disable" in the Console — no redeployment needed.

Step 4: Idempotency

Timer Triggers fire once at the specified time in most cases, but may fire twice in quick succession during edge cases such as releases or node switches. If the business cannot tolerate duplicate executions, add an idempotency gate:

exports.main = async (event, context) => {
const today = new Date().toISOString().slice(0, 10);

// 1. Use date + task name as idempotency key; try to insert a lock record
try {
await db.collection('cron_locks').add({
_id: `dailyReport-${today}`, // throws if _id already exists
acquiredAt: db.serverDate(),
});
} catch (e) {
if (e.code === 'DOC_ALREADY_EXISTS' || e.code === -502002) {
console.log('[cron] already ran today, skip');
return { ok: true, skipped: true };
}
throw e;
}

// 2. Business logic
// ...

return { ok: true };
};

Key points:

  • Using "task name + time window" as _id ensures uniqueness and allows the lock to roll over naturally by day
  • The cron_locks collection can have a short retention period (7 days); configure auto-expiry in Console under "Database → Collection → Auto Expiry" to prevent accumulation of old data
  • The e.code field name above depends on the actual error object structure; log console.log(e) first when encountering an error to inspect the structure

Step 5: Failure Alerting

Scheduled tasks that fail will likely go unnoticed unless failures are surfaced proactively. The simplest integration is a WeCom group bot webhook:

const https = require('https');

async function notifyWecom(message) {
const webhook = process.env.WECOM_WEBHOOK;
if (!webhook) return;

const body = JSON.stringify({
msgtype: 'text',
text: { content: message },
});

return new Promise((resolve) => {
const req = https.request(
webhook,
{ method: 'POST', headers: { 'Content-Type': 'application/json' } },
(res) => res.on('data', () => {}).on('end', resolve),
);
req.write(body);
req.end();
});
}

exports.main = async (event) => {
try {
// Business logic
} catch (err) {
await db.collection('cron_failures').add({
task: 'dailyReport',
error: err.message,
stack: err.stack,
failedAt: db.serverDate(),
});
await notifyWecom(`[cron failed] dailyReport: ${err.message}`);
throw err; // rethrow so the platform logs it as well
}
};

For the complete WeCom webhook integration, see connect-wecom-webhook-cloud-function.

Verification

  1. After deployment, Console "Cloud Functions → dailyReport → Triggers" should show the trigger configuration
  2. To avoid waiting until 9 AM, temporarily change the cron to 0 */1 * * * * * (every minute), redeploy, verify it fires a couple of times, then change it back
  3. Console "Cloud Functions → dailyReport → Invocation Logs" — filter for "Timer Trigger" source; execution records at the corresponding time should appear
  4. Console "Database → daily_reports" should have a new record with date equal to today
  5. Intentionally throw new Error('test') in the function and redeploy; confirm the WeCom group receives the alert, then roll back

Common Errors

Error symptomCauseFix
Trigger not created after deploymenttriggers field in cloudbaserc.json is in the wrong location (placed at the top level instead of inside functions[])triggers must be nested under an element of the functions array
Cron expression fails to deployUsing 5-field Linux cron format by mistakeChange to 7-field: second minute hour day month weekday year
Time is off by 8 hoursTimezone not confirmedCheck the timezone label on the Console's Triggers page; if UTC, recalculate the expression in UTC
Executed twice at the same timeEdge case during deployment or scalingAdd an idempotency lock (Step 4)
Cross-day statistics fluctuate around midnightnew Date() and database serverDate() Timezone mismatchWrite key time fields consistently with db.serverDate(); use only date strings (YYYY-MM-DD) as keys in business logic
Function cut off after 60 secondsDefault timeout value is too smallIncrease timeout in cloudbaserc.json; maximum is 900 seconds

Next Steps