Recipe: Scheduled Cleanup & Maintenance¶
Run periodic maintenance tasks — expire old records, generate reports, sync external data — using scheduled triggers.
Problem¶
Your app accumulates data that needs periodic cleanup: expired sessions, stale drafts, completed orders older than 90 days, or external data that needs regular syncing. You need a cron-like mechanism to run these tasks automatically.
Solution¶
Create a function with a scheduled trigger that runs on an interval or cron schedule.
Example: Delete expired records¶
Step 1: Create the function¶
async function run() {
const collectionName = triggerParams.collection || 'sessions';
const maxAgeDays = triggerParams.maxAgeDays || 30;
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
// Query records older than the cutoff
const expired = await api.queryRecords(collectionName, {
'data.expiresAt[lt]': cutoffDate.toISOString(),
pageSize: 100,
});
if (!expired.data || expired.data.length === 0) {
api.log({ message: 'No expired records found', collection: collectionName });
return { success: true, data: { deleted: 0 } };
}
let deleted = 0;
for (const record of expired.data) {
try {
await api.deleteRecord(collectionName, record.id);
deleted++;
} catch (error) {
api.log({ error: error.message, recordId: record.id });
}
}
api.log({ message: 'Cleanup complete', collection: collectionName, deleted });
return { success: true, data: { deleted, checked: expired.data.length } };
}
Step 2: Create a scheduled trigger¶
- In the Console, go to Functions → Triggers → New Trigger
- Select Scheduled as the trigger type
- Set the schedule:
- Interval: every 6 hours (21600 seconds)
- Or cron:
0 2 * * *(daily at 2 AM UTC)
- Set static params:
That's it — the function runs automatically on the schedule.
Example: Generate a daily summary¶
Aggregate data and store a summary record for reporting:
async function run() {
const today = new Date().toISOString().split('T')[0];
// Count orders by status
const pending = await api.queryRecords('orders', {
'data.status': 'pending',
pageSize: 1,
});
const completed = await api.queryRecords('orders', {
'data.status': 'completed',
'data.completedAt[gte]': `${today}T00:00:00.000Z`,
pageSize: 1,
});
const summary = {
date: today,
pendingOrders: pending.meta?.total || 0,
completedToday: completed.meta?.total || 0,
generatedAt: new Date().toISOString(),
};
// Store the summary as a record
await api.createRecord('daily-summaries', summary);
api.log({ message: 'Daily summary generated', summary });
return { success: true, data: summary };
}
Example: Sync data from an external API¶
Pull data from an external source on a schedule:
async function run() {
const apiKey = triggerParams.externalApiKey;
const lastSyncField = 'data.lastSyncedAt';
// Fetch latest data from external API
const externalData = await api.httpGet(
'https://api.example.com/v1/products',
{ headers: { 'Authorization': `Bearer ${apiKey}` } }
);
if (!externalData || !externalData.products) {
return { success: false, error: 'Failed to fetch external data' };
}
let synced = 0;
for (const product of externalData.products) {
// Upsert: check if record exists, update or create
const existing = await api.queryRecords('products', {
'data.externalId': product.id,
pageSize: 1,
});
if (existing.data && existing.data.length > 0) {
await api.updateRecord('products', existing.data[0].id, {
name: product.name,
price: product.price,
lastSyncedAt: new Date().toISOString(),
});
} else {
await api.createRecord('products', {
externalId: product.id,
name: product.name,
price: product.price,
lastSyncedAt: new Date().toISOString(),
});
}
synced++;
}
api.log({ message: 'Sync complete', synced });
return { success: true, data: { synced } };
}
Add the external API domain to Logic → Domains.
Common gotchas¶
- Execution timeout: Scheduled functions have the same timeout as other functions. If you're processing thousands of records, paginate and process in batches. Consider breaking large jobs into multiple function runs.
- Overlapping runs: If a scheduled function takes longer than the interval, the next run will start while the previous one is still running. Design functions to be idempotent (safe to run twice on the same data).
- Empty
executionParams: Scheduled triggers pass{}asexecutionParams. All config goes intriggerParams(the static params on the trigger). - Timezone: Cron schedules run in UTC. If you need local time logic, convert inside the function.
- Monitoring: Check Functions → Runs in the Console or the Trigger Health page to verify your scheduled function is running and succeeding.