{"record":{"id":"be748213e9b8ba45","repo":"cube-js/cube","slug":"userid-sql-is-not-defined","errorCode":null,"errorMessage":"userId.sql is not defined","messagePattern":"userId\\.sql is not defined","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cubejs-schema-compiler/src/extensions/Funnels.ts","lineNumber":9,"sourceCode":"import inflection from 'inflection';\nimport { AbstractExtension } from './extension.abstract';\n\nexport class Funnels extends AbstractExtension {\n  // TODO check timeToConvert is absent on first step\n  // TODO name can be a title\n  public eventFunnel(funnelDefinition) {\n    if (!funnelDefinition.userId || !funnelDefinition.userId.sql) {\n      throw new Error('userId.sql is not defined'); // TODO schema check\n    }\n\n    if (!funnelDefinition.time || !funnelDefinition.time.sql) {\n      throw new Error('time.sql is not defined'); // TODO schema check\n    }\n\n    if (!funnelDefinition.steps || !funnelDefinition.steps.length) {\n      throw new Error('steps are not defined'); // TODO schema check\n    }\n\n    return this.cubeFactory({\n      sql: () => {\n        const eventJoin =\n          funnelDefinition.steps.map((s, i) => this.eventCubeJoin(funnelDefinition, s, funnelDefinition.steps[i - 1]));\n        const userIdColumnsAndTime =\n          funnelDefinition.steps.map(s => `${this.eventsTableName(s)}.user_id ${this.stepUserIdColumnName(s)}`)\n            .concat([`${this.eventsTableName(funnelDefinition.steps[0])}.t`]).join(',\\n');\n        return `WITH joined_events AS (","sourceCodeStart":1,"sourceCodeEnd":27,"githubUrl":"https://github.com/cube-js/cube/blob/7d981676b36392fec34088b9afab6bdcad40207c/packages/cubejs-schema-compiler/src/extensions/Funnels.ts#L1-L27","documentation":"The Funnels schema extension requires funnelDefinition.userId to be an object with a sql property. It throws when userId is missing entirely or lacks .sql, since the extension cannot build the user-identity join for the funnel SQL without it.","triggerScenarios":"Defining an eventFunnel whose userId is undefined, null, or set to a string/plain value instead of `{ sql: ... }`.","commonSituations":"Copy-pasting funnel examples without filling in userId; passing a raw member name instead of an object with sql; YAML funnels where userId was left empty.","solutions":["Add a userId object with a sql function/property to the funnel definition: userId: { sql: `${CUBE}.user_id` }","Verify the spelling — userId must be camelCase exactly","If userId should come from a shared definition, factor it into the cube and reference it correctly"],"exampleFix":"// before\neventFunnel({\n  userId: 'users.id',\n  ...\n});\n// after\neventFunnel({\n  userId: { sql: `${CUBE}.user_id` },\n  ...\n});","handlingStrategy":"validation","validationCode":"const hasUserIdSql = (f) => !!f && !!f.userId && typeof f.userId.sql !== 'undefined';","typeGuard":"const hasUserIdSql = (f) => typeof f?.userId?.sql === 'string' || typeof f?.userId?.sql === 'function';","tryCatchPattern":"try {\n  funnelCube.eventFunnel(def);\n} catch (e) {\n  if (e.message === 'userId.sql is not defined') {\n    console.error('Add userId: { sql: `${CUBE}.user_id` } to the funnel definition');\n  }\n  throw e;\n}","preventionTips":["Always define userId as an object with sql, never a bare string","Keep a funnel definition template with userId/time/steps pre-filled","Type-check funnel definitions in TypeScript projects"],"tags":["funnel","schema-validation","extension"],"backgroundTag":"missing-required-argument","analyzedSha":"7d981676b36392fec34088b9afab6bdcad40207c","analyzedAt":"2026-09-02T03:45:10.400Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}