danny-avila/LibreChat · error · Error

Principal ID is required for user, group, and role principal

Error message

Principal ID is required for user, group, and role principals

What it means

grantPermission requires a non-empty principalId for every principalType except PUBLIC. The check fires when principalType is USER, GROUP, or ROLE but principalId is null, undefined, or otherwise falsy. PUBLIC is the only principal that may omit it.

Source

Thrown at packages/api/src/acl/accessControlService.ts:66

    expiredAt?: Date;
  }): Promise<IAclEntry | null> {
    const {
      principalType,
      principalId,
      resourceType,
      resourceId,
      accessRoleId,
      grantedBy,
      session,
      expiredAt,
    } = args;
    try {
      if (!Object.values(PrincipalType).includes(principalType)) {
        throw new Error(`Invalid principal type: ${principalType}`);
      }

      if (principalType !== PrincipalType.PUBLIC && !principalId) {
        throw new Error('Principal ID is required for user, group, and role principals');
      }

      // Validate principalId based on type
      if (principalId && principalType === PrincipalType.ROLE) {
        // Role IDs are strings (role names)
        if (typeof principalId !== 'string' || principalId.trim().length === 0) {
          throw new Error(`Invalid role ID: ${principalId}`);
        }
      } else if (
        principalType &&
        principalType !== PrincipalType.PUBLIC &&
        (!principalId || !Types.ObjectId.isValid(principalId))
      ) {
        // User and Group IDs must be valid ObjectIds
        throw new Error(`Invalid principal ID: ${principalId}`);
      }

      if (!resourceId || !Types.ObjectId.isValid(resourceId)) {

View on GitHub (pinned to 5ff282f900)

Solutions

  1. Ensure the caller resolves and passes principalId for USER/GROUP/ROLE grants.
  2. If you genuinely want everyone, switch principalType to PrincipalType.PUBLIC (no id).
  3. Add a precondition check in the route handler so the request returns 400 before reaching the service.

Example fix

// before
await grantPermission({ principalType: PrincipalType.USER, principalId: null, ... });

// after
await grantPermission({ principalType: PrincipalType.PUBLIC, ... });
// or
await grantPermission({ principalType: PrincipalType.USER, principalId: resolvedUserId, ... });
Defensive patterns

Strategy: validation

Validate before calling

import { PrincipalType } from 'librechat-data-provider';

function resolvePrincipalArgs(p: { principalType: PrincipalType; principalId?: string | null }) {
  if (p.principalType !== PrincipalType.PUBLIC && !p.principalId) {
    throw new Error('principalId is required for non-PUBLIC principals');
  }
  return p;
}

Type guard

import { PrincipalType } from 'librechat-data-provider';
const needsPrincipalId = (t: PrincipalType): boolean => t !== PrincipalType.PUBLIC;

Prevention

When it happens

Trigger: grantPermission called with principalType: PrincipalType.USER and principalId omitted/null/undefined; passing PrincipalType.PUBLIC's id-less shape to a USER grant; a form or API payload where the user/group selector returned no selection.

Common situations: UI bug where the user picker was skipped; a migration script that iterates principals but encounters a null foreign key; misusing a PUBLIC grant flow for a specific user.

Related errors


AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12). Data as JSON: /api/errors/aa60a1cf80cabfd7. Report an issue: GitHub.