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() in PermissionService.js:81 throws this when principalType is anything other than PUBLIC and principalId is missing/null. PUBLIC is the only principal that legitimately has no id (it represents everyone); user, group, and role principals must carry an identifier.

Source

Thrown at api/server/services/PermissionService.js:81

 * @param {mongoose.ClientSession} [params.session] - Optional MongoDB session for transactions
 * @returns {Promise<Object>} The created or updated ACL entry
 */
const grantPermission = async ({
  principalType,
  principalId,
  resourceType,
  resourceId,
  accessRoleId,
  grantedBy,
  session,
}) => {
  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 &&
      !mongoose.Types.ObjectId.isValid(principalId)
    ) {
      // User and Group IDs must be valid ObjectIds
      throw new Error(`Invalid principal ID: ${principalId}`);
    }

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

View on GitHub (pinned to 5ff282f900)

Solutions

  1. For USER/GROUP, supply a valid ObjectId; for ROLE, supply the role name string.
  2. If you intended 'everyone', use PrincipalType.PUBLIC with no principalId instead.
  3. Validate on the client that a principal is selected before enabling the submit action.

Example fix

// before
grantPermission({ principalType: PrincipalType.USER, principalId: null, ... });
// after
grantPermission({ principalType: PrincipalType.USER, principalId: selectedUserId, ... });
Defensive patterns

Strategy: validation

Validate before calling

function assertPrincipalId(principalType, principalId) {
  if (principalType !== PrincipalType.PUBLIC && !principalId) {
    throw new Error(`principalId required for ${principalType}`);
  }
}

Type guard

const hasRequiredPrincipalId = (type, id) => type === PrincipalType.PUBLIC ? true : !!id;

Prevention

When it happens

Trigger: A caller sets principalType to USER/GROUP/ROLE but omits principalId or passes null/undefined. Fires after the principalType enum check passes, so it specifically catches the 'right type, no id' mistake.

Common situations: UI bug where the selected user/group did not get bound to the request body. A refactor that decoupled principalType from principalId population. Confusing ROLE (id is the role name string) with a missing id.

Related errors


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