cube-js/cube · error · UserError

compareDateRange can only exist for one timeDimension

Error message

compareDateRange can only exist for one timeDimension

What it means

The `compareDateRange` option is a convenience that expands one query into multiple date-range variants, so it may be set on at most one timeDimension. Before converting the query, the gateway scans query.timeDimensions and throws this UserError if more than one timeDimension has a non-null compareDateRange.

Source

Thrown at packages/cubejs-api-gateway/src/gateway.ts:2937

        type: 'Outgoing network usage',
        service: 'api-http',
        bytes: Number(res.get('content-length')) || 0,
        path: req.path,
      }, req.context);
    });
    if (next) {
      next();
    }
  };

  protected compareDateRangeTransformer(query) {
    let queryCompareDateRange;
    let compareDateRangeTDIndex;

    (query.timeDimensions || []).forEach((td, index) => {
      if (td.compareDateRange != null) {
        if (queryCompareDateRange != null) {
          throw new UserError('compareDateRange can only exist for one timeDimension');
        }

        queryCompareDateRange = td.compareDateRange;
        compareDateRangeTDIndex = index;
      }
    });

    if (queryCompareDateRange == null) {
      return query;
    }

    return queryCompareDateRange.map((dateRange) => ({
      ...R.clone(query),
      timeDimensions: query.timeDimensions.map((td, index) => {
        if (compareDateRangeTDIndex === index) {
          // eslint-disable-next-line @typescript-eslint/no-unused-vars
          const { compareDateRange, ...timeDimension } = td;
          return {

View on GitHub (pinned to 7d981676b3)

Solutions

  1. Keep compareDateRange on only one timeDimension; remove it from the others.
  2. If you need comparisons across multiple dimensions, split into separate API requests and merge results client-side.
  3. Use explicit dateRange arrays per timeDimension plus separate queries instead of the compareDateRange shortcut when comparing multiple dimensions.

Example fix

// before
timeDimensions: [
  { dimension: 'Events.date', compareDateRange: ['2024-01-01','2024-01-31'] },
  { dimension: 'Users.created', compareDateRange: ['2023-01-01','2023-01-31'] }
]
// after
timeDimensions: [
  { dimension: 'Events.date', compareDateRange: ['2024-01-01','2024-01-31'] },
  { dimension: 'Users.created', dateRange: ['2023-01-01','2023-01-31'] }
]
Defensive patterns

Strategy: validation

Validate before calling

function validateCompareDateRange(query) {
  const count = (query.timeDimensions || []).filter(td => td.compareDateRange != null).length;
  if (count > 1) throw new Error('compareDateRange allowed on at most one timeDimension');
}

Type guard

function hasAtMostOneCompareDateRange(query) {
  return (query.timeDimensions || []).filter(td => td.compareDateRange != null).length <= 1;
}

Try / catch

try {
  await cubeApi.load(query);
} catch (e) {
  if (e instanceof Error && e.message.includes('compareDateRange')) {
    // strip compareDateRange from all but one timeDimension and retry
  }
  throw e;
}

Prevention

When it happens

Trigger: Submitting a /cubejs-api/v1/load query whose timeDimensions array contains two or more entries each with `compareDateRange` set — e.g. comparing two different date ranges for two different dimensions in one request.

Common situations: Programmatic query builders accumulating compareDateRange across dimensions; UI clients letting users pick a comparison range per dimension; hand-editing a JSON query copied from another example.

Related errors


AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02). Data as JSON: /api/errors/aecca313c26202d4. Report an issue: GitHub.