{"record":{"id":"0e2c2f818256c4fd","repo":"n8n-io/n8n","slug":"cron-expression-must-have-min-cron-field-count","errorCode":null,"errorMessage":"Cron expression must have ${MIN_CRON_FIELD_COUNT} or ${MAX_CRON_FIELD_COUNT} fields (seconds optional), got ${fieldCount}: ${JSON.stringify(expression)}","messagePattern":"Cron expression must have (.+?) or (.+?) fields \\(seconds optional\\), got (.+?): (.+?)","errorType":"validation","errorClass":"InvalidScheduleError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/scheduler/src/core/recurrence/kinds/cron.ts","lineNumber":36,"sourceCode":"\n/**\n * Checks that a cron schedule is usable: a 5- or 6-field expression in a real\n * timezone. Also used for `recurring_cron`, which reuses the cron expression.\n * @param schedule The cron (or recurring_cron) schedule to check.\n * @throws {InvalidScheduleError} When the expression or timezone is invalid.\n */\nexport function validateCron(schedule: CronSchedule | RecurringCronSchedule): void {\n\t// Raw DB rows may reach here untyped, so check the runtime type before use.\n\tconst expression: unknown = schedule.cronExpression;\n\tif (typeof expression !== 'string') {\n\t\tthrow new InvalidScheduleError(\n\t\t\t`${schedule.kind}.cronExpression must be a string, got ${JSON.stringify(expression)}`,\n\t\t);\n\t}\n\n\tconst fieldCount = expression.trim().split(/\\s+/).length;\n\tif (fieldCount < MIN_CRON_FIELD_COUNT || fieldCount > MAX_CRON_FIELD_COUNT) {\n\t\tthrow new InvalidScheduleError(\n\t\t\t`Cron expression must have ${MIN_CRON_FIELD_COUNT} or ${MAX_CRON_FIELD_COUNT} fields (seconds optional), got ${fieldCount}: ${JSON.stringify(expression)}`,\n\t\t);\n\t}\n\n\t// A null timezone is the instance default, resolved by the caller.\n\tif (schedule.timezone !== null && !IANAZone.isValidZone(schedule.timezone)) {\n\t\tthrow new InvalidScheduleError(`Unknown IANA timezone: ${JSON.stringify(schedule.timezone)}`);\n\t}\n\n\ttry {\n\t\tCronExpressionParser.parse(expression, { tz: schedule.timezone ?? 'UTC' });\n\t} catch (error) {\n\t\tthrow new InvalidScheduleError(\n\t\t\t`Invalid cron expression ${JSON.stringify(expression)}: ${(error as Error).message}`,\n\t\t);\n\t}\n}\n","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/scheduler/src/core/recurrence/kinds/cron.ts#L18-L54","documentation":"Thrown by validateCron when the cron expression does not have 5 or 6 fields (6 = with seconds). MIN_CRON_FIELD_COUNT and MAX_CRON_FIELD_COUNT bound the field count before the expression is handed to CronExpressionParser, so a parser-specific error is replaced with a clear field-count message. The check runs on .trim().split(/\\s+/) length.","triggerScenarios":"An expression like '* * * *' (4 fields), '* * * * * * *' (7 fields), an empty string (1 field after split on '' - actually yields ['']), or an expression using tabs/multiple spaces (split on /\\s+/ normalizes those).","commonSituations":"User pastes a 7-field cron with year field (croniter-style) into a 5/6-field scheduler; a missing field typo; an expression built by concatenation that dropped a field; locale confusion between 5-field unix cron and quartz 6/7-field cron.","solutions":["Use a 5-field unix cron ('m h dom mon dow') or 6-field with leading seconds ('s m h dom mon dow').","Drop the year field if you copied a 7-field quartz expression.","If you have a 7-field expression, strip the trailing year field programmatically.","Validate field count in the UI before submission."],"exampleFix":"// before\nvalidateCron({ kind: 'cron', cronExpression: '0 0 * * * * *', timezone: 'UTC' }); // 7 fields -> throws\n\n// after - drop the year field for quartz-style input\nfunction toUnixCron(expr: string): string {\n  const fields = expr.trim().split(/\\s+/);\n  return fields.length === 7 ? fields.slice(0, 6).join(' ') : expr;\n}\nvalidateCron({ kind: 'cron', cronExpression: toUnixCron(userInput), timezone: 'UTC' });","handlingStrategy":"validation","validationCode":"function normalizeCronFields(expr: string): string {\n  const fields = expr.trim().split(/\\s+/);\n  if (fields.length === 7) return fields.slice(0, 6).join(' '); // drop quartz year\n  if (fields.length === 5 || fields.length === 6) return expr;\n  throw new Error(`Cron expression must have 5 or 6 fields, got ${fields.length}`);\n}","typeGuard":"function hasValidFieldCount(expr: string): boolean {\n  const n = expr.trim().split(/\\s+/).length;\n  return n === 5 || n === 6;\n}","tryCatchPattern":null,"preventionTips":["Validate field count in the UI before submission with the same 5/6 rule.","If accepting quartz input, strip the trailing year field programmatically.","Pre-test expressions with a tool like crontab.guru before persisting.","Document the supported cron dialect (unix 5-field, optional seconds) in the user-facing docs."],"tags":["scheduler","cron","validation"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}