{"record":{"id":"6dc03cd9a58f19ce","repo":"cube-js/cube","slug":"granularity-timedimension-granularity-not-fou","errorCode":null,"errorMessage":"Granularity \"${timeDimension.granularity}\" not found in time dimension \"${timeDimension.dimension}\"","messagePattern":"Granularity \"(.+?)\" not found in time dimension \"(.+?)\"","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cubejs-client-core/src/ResultSet.ts","lineNumber":488,"sourceCode":"    if (!dateRange) {\n      return null;\n    }\n\n    const padToDay = timeDimension.dateRange ?\n      (timeDimension.dateRange as string[]).find(d => d.match(DateRegex)) :\n      !['hour', 'minute', 'second'].includes(timeDimension.granularity);\n\n    const [start, end] = dateRange;\n    const range = dayRange(start, end, annotations);\n\n    if (isPredefinedGranularity(timeDimension.granularity)) {\n      return TIME_SERIES[timeDimension.granularity](\n        padToDay ? range.snapTo('d') : range\n      );\n    }\n\n    if (!annotations?.[`${timeDimension.dimension}.${timeDimension.granularity}`]) {\n      throw new Error(`Granularity \"${timeDimension.granularity}\" not found in time dimension \"${timeDimension.dimension}\"`);\n    }\n\n    return timeSeriesFromCustomInterval(\n      start, end, annotations[`${timeDimension.dimension}.${timeDimension.granularity}`].granularity!\n    );\n  }\n\n  /**\n   * Base method for pivoting [ResultSet](#result-set) data.\n   * Most of the time shouldn't be used directly and [chartPivot](#result-set-chart-pivot)\n   * or [tablePivot](#table-pivot) should be used instead.\n   *\n   * You can find the examples of using the `pivotConfig` [here](#types-pivot-config)\n   * ```js\n   * // For query\n   * {\n   *   measures: ['Stories.count'],\n   *   timeDimensions: [{","sourceCodeStart":470,"sourceCodeEnd":506,"githubUrl":"https://github.com/cube-js/cube/blob/7d981676b36392fec34088b9afab6bdcad40207c/packages/cubejs-client-core/src/ResultSet.ts#L470-L506","documentation":"ResultSet.timeSeries() builds the x-axis date buckets for a time-series result. When a time dimension uses a custom granularity defined in the data model viagranularities annotations, the resultset annotation must contain an entry named `dimension.granularity`. This error is thrown when the requested granularity is neither a built-in interval nor present in the annotations, so no series can be generated.","triggerScenarios":"Calling chart-generating methods (seriesNumericColumns/series → timeSeries) on a ResultSet whose query used a timeDimension with a custom (non-builtin) granularity while the loadResponse annotation lacks a `<dimension>.<granularity>` key — e.g. annotation omitted, annotation stripped by a proxy, or a typo/mismatch between the granularity name in the query and the granularity name declared in the data model's granularities list.","commonSituations":"Custom granularities declared in the cube schema (e.g. granularities: [{name:'quarter_hour'}]) but queried with a different casing/spelling; caching or mock loadResponses built without annotations; backend versions older than custom-granularity support returning annotations without granularity entries; passing a ResultSet produced by decompose/comparison handling where the wrong sub-result's annotations are consulted.","solutions":["Verify the granularity name in the query timeDimensions exactly matches a granularity name declared in the cube's granularities (or a builtin: day/week/month/quarter/year/hour/minute/second)","Inspect resultSet.loadResponses[0].annotation for a key `<dimension>.<granularity>`; if missing, ensure the backend returns full annotations (check proxy/caching code that may strip annotation)","Update @cubejs-client/core to a version supporting custom granularities and confirm the backend supports them","As a fallback, use built-in granularities or build the series manually from rawData()"],"exampleFix":"// before\ngranularity: 'Qtr' // typo, not defined in the cube\n// after\n// cube schema\ndimension XYZ { timeDimension { granularities: [quarter, quarterOfDay] } }\n// query\ngranularity: 'quarterOfDay' // must match the schema-declared name and appear in annotations","handlingStrategy":"validation","validationCode":"function canBuildTimeSeries(resultSet) {\n  const td = resultSet.loadResponse?.query?.timeDimensions?.[0];\n  if (!td || !td.granularity) return true; // builtin path\n  const key = `${td.dimension}.${td.granularity}`;\n  return Boolean(resultSet.loadResponse?.annotation?.[key]);\n}\nif (!canBuildTimeSeries(rs)) { console.warn('custom granularity missing from annotations', td); return; }","typeGuard":"function hasGranularityAnnotation(rs: ResultSet): boolean {\n  const td = rs.loadResponse?.query?.timeDimensions?.[0];\n  if (!td?.granularity) return true;\n  return rs.loadResponse?.annotation?.[`${td.dimension}.${td.granularity}`] != null;\n}","tryCatchPattern":"let series;\ntry { series = rs.seriesNames(); } catch (e) {\n  if (String(e?.message).includes('not found in time dimension')) {\n    series = []; // render empty chart and surface schema/query mismatch\n  } else throw e;\n}","preventionTips":["Keep granularity names in queries generated from the same source as the schema (codegen/constants)","Assert annotation completeness in tests for any custom-granularity dashboards","Never strip or partially copy loadResponse annotation in proxies/caches","Prefer built-in granularities unless custom intervals are truly needed"],"tags":["query","annotations","custom-granularity","timeseries"],"backgroundTag":"granularity-not-in-annotations","analyzedSha":"7d981676b36392fec34088b9afab6bdcad40207c","analyzedAt":"2026-09-02T03:45:10.400Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}