Skip to content

Background jobs

Use crowdinApp.cron.schedule(expression, task) to schedule background work. For work across installed accounts, forEachCrowdinConnection supplies an authenticated Crowdin API client for each stored installation, renews tokens automatically, and isolates installation failures.

import { addCrowdinEndpoints, express } from '@crowdin/app-project-module';
const app = express();
const crowdinApp = addCrowdinEndpoints(app, {
name: 'Project cache',
identifier: 'project-cache',
description: 'Refresh project metadata hourly',
clientId: process.env.CLIENT_ID!,
clientSecret: process.env.CLIENT_SECRET!,
});
crowdinApp.cron.schedule('0 * * * *', async () => {
const result = await crowdinApp.forEachCrowdinConnection(async ({ client, credentials }) => {
const projects = await client.projectsGroupsApi.withFetchAll().listProjects();
await crowdinApp.saveMetadata({
id: 'project-cache',
crowdinId: credentials.id,
metadata: JSON.stringify(projects.data.map(({ data }) => data)),
});
});
console.log(`Updated ${result.processed}/${result.total} installations; ${result.failed.length} failed`);
});
app.listen(3000);

The scheduler defaults to Node.js cron. For serverless hosting, provide a custom Cron implementation and invoke it from the hosting platform’s scheduled handler; see Cloudflare Workers deployment. The helper itself does not schedule work and can also be awaited from an admin route or webhook handler.

crowdinApp.cron.schedule applies resilient defaults to every task, so a heartbeat that is late — because of a CPU stall, synchronous IO, or a sibling task holding the loop — runs the slot late instead of silently skipping it:

  • Missed-execution tolerance. A heartbeat that is late by less than the tolerance still runs its slot. The default is derived from the expression (half the gap between slots, capped at 10 minutes), which sits well above a few-second stall yet below the next slot. Override it for all tasks with cronOptions.missedExecutionTolerance (ms) or the CRON_MISSED_EXECUTION_TOLERANCE_MS environment variable; the cap on the derived value is cronOptions.maxMissedExecutionTolerance / CRON_MAX_MISSED_EXECUTION_TOLERANCE_MS. A tolerance is always clamped below the slot gap, since a heartbeat a full gap late is the next slot.
  • Overlap prevention. A new run is blocked while the previous run of the same task is still in flight (noOverlap, on by default). Disable with cronOptions.noOverlap: false. Because a tolerated late run can land close to the next slot, keep overlap prevention on for tasks that must not run concurrently.
  • Structured logging. Every missed or blocked run is logged with the task name and how late the heartbeat was (lateBy=…ms), so a lost run is never silent. A dropped run (heartbeat later than the tolerance) is reported through the error channel — your onError handler, if set — so it stays visible to monitoring and is not gated on the logger being enabled; an overlap-blocked run is logged at info level.
const crowdinApp = addCrowdinEndpoints(app, {
// …
cronOptions: {
missedExecutionTolerance: 5 * 60 * 1000, // run a slot up to 5 minutes late instead of dropping it
noOverlap: true,
},
});

These options apply only to the built-in node-cron scheduler and are ignored when a custom cron is supplied.

With the Cloudflare Workers adapter, configure a single Cron Trigger, "crons": ["* * * * *"]. On each tick the adapter runs every task whose expression matches that minute, whichever expressions the app and the module scheduled, so wrangler.jsonc never has to list them:

  • Time zone. Expressions are matched in UTC, the time zone of Cron Triggers. Day-of-month and day-of-week are matched as by the default scheduler, so an expression fires on the same days on Node.js and on Workers.
  • Time limit. A tick waits for all its tasks, so they share the Cron Trigger’s limits: 15 minutes of wall time, and 30 seconds of CPU on the Paid plan for a trigger that fires more often than hourly. The Free plan allows 10 ms of CPU per invocation.
  • Missed ticks. A tick that Cloudflare skips is not replayed. The task runs again at its next matching time.
  • Overlap. noOverlap and the tolerance options do not apply. A task that takes longer than its interval can run in two isolates at once, so keep tasks safe to repeat.
  • Quota. On the Free plan, the trigger uses 1,440 of the 100,000 daily requests.
  • Failures. A failing task is logged with its name and does not stop the other tasks of the tick.
OptionDefaultBehavior
concurrencyCRON_ORG_CONCURRENCY, or 5Positive safe integer; installations run in batches of at most this size.
checkSubscriptiontrueSkip installations whose subscription is expired. Set to false for work that must also run for expired subscriptions, such as cleanup.

The callback receives { client, credentials }. credentials is the original storage snapshot identifying the installation, not a source of current API tokens. Use client for API calls. The callback runs once per stored installation; enumerate projects inside it when needed.

The result contains total, processed, skippedExpired, and failed ({ crowdinId, error }[]). processed counts successfully completed callbacks; each installation has exactly one outcome. Client preparation and callback errors are logged and collected without aborting other installations. Empty storage returns zero counts and an empty failed array.

Invalid explicit concurrency rejects with RangeError; a storage listing failure rejects the whole run. Unauthorized configuration throws immediately. Subscription checks retain the SDK’s existing behavior: handled subscription-service HTTP errors allow processing to continue.

Background token preparation uses the existing crowdin_app, authorization_code, and crowdin_agent flows. crowdin_app_with_code requires a code from a request JWT and is not supported for background iteration: each stored installation is reported in failed, and its callback is not called.

Concurrency applies to one invocation only. Separate invocations or app replicas can overlap; this helper adds no retries or distributed locking. Design callbacks to tolerate repeated execution where needed. Avoid logging whole credentials or result errors without the application’s normal error sanitization.