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.

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.