Creating Scheduled Jobs
Scheduled jobs in SolidX allow you to run recurring tasks such as sending notifications, cleaning up records, syncing data, or performing regular maintenance.
This section walks you through how to create and integrate custom scheduled jobs into your application.
For the full metadata schema reference, see Scheduled Jobs Metadata.
Adding a New Scheduled Job
Follow these steps to define and use a custom scheduled job:
1. Create a Job Service
Create a new service class that implements the IScheduledJob interface and decorate it with @ScheduledJobProvider().
Example: Late Fee Calculator Job
import { Injectable, Logger } from '@nestjs/common';
import { IScheduledJob, ScheduledJob, ScheduledJobProvider } from '@solidxai/core';
import { FeesService } from '../fees/fees.service';
@Injectable()
@ScheduledJobProvider()
export class LateFeeCalculatorJob implements IScheduledJob {
private readonly logger = new Logger(LateFeeCalculatorJob.name);
constructor(private readonly feesService: FeesService) {}
async execute(job: ScheduledJob): Promise<void> {
this.logger.log(`Running job: ${job.scheduleName}`);
try {
const overdueAccounts = await this.feesService.findOverdue();
for (const account of overdueAccounts) {
await this.feesService.applyLateFee(account);
}
this.logger.log(`Late fees applied to ${overdueAccounts.length} accounts`);
} catch (err) {
this.logger.error('Late fee calculation failed', err.stack);
}
}
}
2. Register the Service
Add the job class to the providers array of its NestJS module so the framework can inject and resolve it.
3. Define the Job in Metadata
Add an entry to the scheduledJobs array in your module metadata file:
solid-api/module-metadata/{module-name}/{module-name}-metadata.json
Example: Daily job with a time window
{
"scheduledJobs": [
{
"scheduleName": "Late Fee Calculation",
"isActive": true,
"frequency": "Daily",
"startTime": "08:00:00",
"endTime": "18:00:00",
"startDate": null,
"endDate": null,
"dayOfMonth": null,
"job": "LateFeeCalculatorJob",
"moduleUserKey": "fees-portal"
}
]
}
This runs LateFeeCalculatorJob every day (including weekends — dayOfWeek is only enforced for Weekly frequency). The startTime/endTime window means the scheduler will hold off executing the job until 08:00 if it comes due earlier, and will skip it entirely if it comes due after 18:00 (waiting for the next day's window to open). No date range is set, so the job runs indefinitely.
Example: Weekly job on specific days
{
"scheduledJobs": [
{
"scheduleName": "Weekly Overdue Report",
"isActive": true,
"frequency": "Weekly",
"startTime": "07:00:00",
"endTime": null,
"startDate": null,
"endDate": null,
"dayOfMonth": null,
"dayOfWeek": "[\"Monday\",\"Thursday\"]",
"job": "OverdueReportJob",
"moduleUserKey": "fees-portal"
}
]
}
This runs OverdueReportJob once every 7 days, but only if the due date falls on a Monday or Thursday — if it doesn't, the scheduler skips it every minute until an eligible day arrives, then runs and resets the 7-day clock from that point. It will not execute before 07:00, and has no upper time boundary. No date range is set, so it runs indefinitely.
Configuration Reference
| Property | Type | Description |
|---|---|---|
scheduleName | string | Human-readable name for the job |
isActive | boolean | Whether the job is enabled. Defaults to false |
frequency | string | How often the job runs — see Supported Frequencies |
cronExpression | string | Required when frequency is "Custom" — a valid cron expression |
startTime | string | null | Earliest time of day to run, in HH:MM:SS format |
endTime | string | null | Latest time of day to run, in HH:MM:SS format |
startDate | string | null | ISO date — job will not run before this date |
endDate | string | null | ISO date — job will not run after this date |
dayOfWeek | string | Stringified JSON array of day names, e.g. "[\"Monday\",\"Friday\"]" |
dayOfMonth | number | null | Day of the month for Monthly jobs (1–31) |
job | string | Exact class name of the job implementation (case-sensitive) |
moduleUserKey | string | User key of the module this job belongs to |
Supported Frequencies
| Value | Interval |
|---|---|
Every Minute | Every 60 seconds |
Hourly | Every 60 minutes |
Daily | Every 24 hours |
Weekly | Every 7 days, filtered by dayOfWeek |
Monthly | Same day next month, filtered by dayOfMonth |
Custom | Arbitrary schedule defined by a cron expression |
Custom Cron Expressions
When the built-in frequencies aren't precise enough, set frequency to "Custom" and provide a standard cron expression in the cronExpression field.
{
"scheduleName": "Bi-hourly Sync",
"isActive": true,
"frequency": "Custom",
"cronExpression": "0 */2 * * *",
"job": "DataSyncJob",
"moduleUserKey": "my-module"
}
Notes:
- Cron expressions are evaluated in UTC.
- The minimum allowed interval is 1 minute. Expressions that resolve to a shorter interval are rejected and the job falls back to a daily schedule.
- If the expression is invalid or missing, the platform logs an error and falls back to a 24-hour interval.
Info
Use a tool like crontab.guru to build and validate your cron expressions before adding them to metadata.
How It Works
The SolidX scheduler runs an internal polling loop every minute (powered by @Cron(CronExpression.EVERY_MINUTE) under the hood). On each tick it:
- Fetches due jobs — queries all active jobs where
nextRunAt ≤ nowornextRunAtis null (newly created jobs that haven't run yet). - Applies scheduling gates — each job is checked against
startDate/endDate(date range),startTime/endTime(time-of-day window),dayOfWeek(for Weekly), anddayOfMonth(for Monthly) before being allowed to execute. - Guards against overlap — if a job is still running from a previous tick it is skipped, preventing concurrent duplicate executions.
- Dispatches execution — the job's
execute(job)method is called with the fullScheduledJobentity, giving the handler access to all metadata. - Updates tracking fields — after a successful run,
lastRunAtis stamped andnextRunAtis computed based on the configured frequency, so the scheduler knows exactly when to pick it up next.
Environment Control
Set SOLID_SCHEDULER_ENABLED=false in your environment to disable the scheduler entirely (useful for CLI contexts or environments where background jobs should not run).