{"record":{"id":"f5472fadc515c604","repo":"actualbudget/actual","slug":"can-t-cast-expr-type-to-date-month","errorCode":null,"errorMessage":"Can't cast ${expr.type} to date-month","messagePattern":"Can't cast (.+?) to date-month","errorType":"exception","errorClass":"CompileError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/aql/compiler.ts","lineNumber":286,"sourceCode":"      } else {\n        throw new CompileError(\n          'Casting string fields to dates is not supported',\n        );\n      }\n    }\n\n    throw new CompileError(`Can't cast ${expr.type} to date`);\n  } else if (type === 'date-month') {\n    let expr2;\n    if (expr.type === 'date') {\n      expr2 = expr;\n    } else if (expr.type === 'string' || expr.type === 'any') {\n      expr2 =\n        parseMonth(expr.value) ||\n        parseDate(expr.value) ||\n        badDateFormat(expr.value, 'date-month');\n    } else {\n      throw new CompileError(`Can't cast ${expr.type} to date-month`);\n    }\n\n    if (expr2.literal) {\n      return typed(\n        dateToInt(expr2.value.toString().slice(0, 6)),\n        'date-month',\n        { literal: true },\n      );\n    } else {\n      return typed(\n        `CAST(SUBSTR(${expr2.value}, 1, 6) AS integer)`,\n        'date-month',\n      );\n    }\n  } else if (type === 'date-year') {\n    let expr2;\n    if (expr.type === 'date' || expr.type === 'date-month') {\n      expr2 = expr;","sourceCodeStart":268,"sourceCodeEnd":304,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/aql/compiler.ts#L268-L304","documentation":"castInput needs a `date-month` value (YYYY-MM). Accepted sources are `date`, `string`, and `any`; anything else (integer, boolean, id, array, etc.) cannot be cast and throws. Unlike `date`, non-literal string fields ARE allowed here via SQL SUBSTR, so the failure is limited to genuinely uncastable types.","triggerScenarios":"Using a date-month function (e.g. month grouping/filters) with an expression typed as something other than date/string/any/param/null — commonly an integer, float, boolean, id, or array value.","commonSituations":"Grouping by month but passing an account id or numeric field instead of `date`; passing a rule value of the wrong type through the API; a schema mismatch after renaming fields.","solutions":["Pass a date-typed field or a string like '2024-01' or '2024-01-15' instead","Verify the field name refers to a date column, not a numeric/id column","Convert the value to a month string ('YYYY-MM') before querying","Drop the invalid expression and rebuild the month filter around the `date` field"],"exampleFix":"// before\nq.groupBy({ $month: { $field: 'account' } })\n// after\nq.groupBy({ $month: { $field: 'date' } })","handlingStrategy":"validation","validationCode":"const MONTH_RE = /^\\d{4}-\\d{2}(-\\d{2})?$/;\nif (!(typeof v === 'string' && MONTH_RE.test(v))) {\n  throw new Error('Expected YYYY-MM or YYYY-MM-DD string');\n}","typeGuard":"function isMonthLike(v) {\n  return typeof v === 'string' && /^\\d{4}-\\d{2}(-\\d{2})?$/.test(v);\n}","tryCatchPattern":"try {\n  runQuery(q.groupBy({ $month: { $field: field } }));\n} catch (e) {\n  if (/Can't cast .* to date-month/.test(e.message)) {\n    throw new Error(`Field '${field}' is not a date; use the date column`, { cause: e });\n  }\n  throw e;\n}","preventionTips":["Only reference the `date` field (or month strings) in $month expressions","Check the schema field type before grouping/filtering by month","Normalize month inputs to 'YYYY-MM' strings"],"tags":["aql","query-compiler","type-cast","dates"],"backgroundTag":"aql-type-cast-error","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}