cube-js/cube · error · Error

Access policy cannot have both 'group' and 'groups'…

Error message

Access policy cannot have both 'group' and 'groups' properties.
Policy in cube '${cube.name}' has group '${groupDisplay}' and groups '${groupsDisplay}'.
Use either 'group' or 'groups', not both.

What it means

Access policies accept either the singular 'group' or plural 'groups' property to declare which groups a policy applies to. A policy defining both is ambiguous, so getApplicablePolicies throws with the offending values and cube name.

Solutions

  1. Remove the legacy 'group' key and keep only 'groups' (preferred plural array form)
  2. Or keep only 'group' if a single group is intended
  3. Search your schema for accessPolicy blocks containing both keys in the named cube

Example fix

// before
accessPolicy: [{ group: 'admin', groups: ['admin', 'analyst'] }]
// after
accessPolicy: [{ groups: ['admin', 'analyst'] }]
Defensive patterns

Strategy: validation

Validate before calling

function validatePolicy(p) {
  if (p.group && p.groups) throw new Error(`accessPolicy has both 'group' and 'groups': ${JSON.stringify(p)}`);
}

Type guard

const isPolicyGroups = (p: AccessPolicyDefinition): p is AccessPolicyDefinition & { group?: never } => !('group' in p) || !('groups' in p);

Try / catch

try { const policies = getApplicablePolicies(cubes, context, compilers); } catch (e) { if (/both 'group' and 'groups'/.test(e.message)) { /* flag offending cube */ } else throw e; }

Prevention

When it happens

Trigger: A cube's accessPolicy entry includes both keys, typically after migrating config from the old singular form to the plural array form without removing 'group'.

Common situations: Copy-pasted policy definitions, incremental migration where one policy still has the legacy 'group' alongside new 'groups', or code-generated schemas emitting both keys.

Related errors


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

Appendix: source

Thrown at packages/cubejs-server-core/src/core/CompilerApi.ts:433

  protected hashRequestContext(context: Context): string {
    if (!context.__hash) {
      context.__hash = crypto.createHash('md5').update(JSON.stringify(context)).digest('hex');
    }
    return context.__hash;
  }

  protected async getApplicablePolicies(cube: EvaluatedCube, context: Context, compilers: Compiler): Promise<any[]> {
    const cache = compilers.compilerCache.getRbacCacheInstance();
    const cacheKey = `${cube.name}_${this.hashRequestContext(context)}`;
    if (!cache.has(cacheKey)) {
      const userGroups = await this.getGroupsFromContext(context);
      const policies = cube.accessPolicy.filter((policy: AccessPolicyDefinition) => {
        // Validate that policy doesn't have both group and groups
        if (policy.group && policy.groups) {
          const groupDisplay = Array.isArray(policy.group) ? policy.group.join(', ') : policy.group;
          const groupsDisplay = Array.isArray(policy.groups) ? policy.groups.join(', ') : policy.groups;
          throw new Error(
            `Access policy cannot have both 'group' and 'groups' properties.\nPolicy in cube '${cube.name}' has group '${groupDisplay}' and groups '${groupsDisplay}'.\nUse either 'group' or 'groups', not both.`
          );
        }

        const evaluatedConditions = (policy.conditions || []).map(
          (condition: any) => compilers.cubeEvaluator.evaluateContextFunction(cube, condition.if, context)
        );

        // Check if policy matches by group or groups
        let hasAccess = false;

        if (policy.group) {
          hasAccess = this.userHasGroup(userGroups, policy.group);
        } else if (policy.groups) {
          hasAccess = this.userHasGroup(userGroups, policy.groups);
        } else {
          // A policy without group/groups applies to everyone
          hasAccess = this.userHasGroup(userGroups, '*');

View on GitHub (pinned to 7d981676b3)