danny-avila/LibreChat · error · Error

Invalid principal type: ${principalType}

Error message

Invalid principal type: ${principalType}

What it means

grantPermission() in PermissionService.js:77 throws this as its first guard when principalType is not a value of the PrincipalType enum (user, group, public, role). It prevents persisting ACL records with an unknown principal category, which would be silently invisible to every authorization check.

Source

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

 * @param {string} params.resourceType - Type of resource (e.g., 'agent')
 * @param {string|mongoose.Types.ObjectId} params.resourceId - The ID of the resource
 * @param {string} params.accessRoleId - The ID of the role (e.g., AccessRoleIds.AGENT_VIEWER, AccessRoleIds.AGENT_EDITOR)
 * @param {string|mongoose.Types.ObjectId} params.grantedBy - User ID granting the permission
 * @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

View on GitHub (pinned to 5ff282f900)

Solutions

  1. Pass principalType as one of PrincipalType.USER, .GROUP, .PUBLIC, or .ROLE.
  2. Type the parameter as PrincipalType in TypeScript so the compiler rejects invalid values.
  3. If you genuinely need a new principal category, extend the PrincipalType enum and the ACL schema first.

Example fix

// before
grantPermission({ principalType: 'team', ... });
// after
import { PrincipalType } from 'librechat-data-provider';
grantPermission({ principalType: PrincipalType.GROUP, ... });
Defensive patterns

Strategy: type-guard

Validate before calling

const VALID_PRINCIPAL_TYPES = new Set(Object.values(PrincipalType));
function assertPrincipalType(t) {
  if (!VALID_PRINCIPAL_TYPES.has(t)) throw new Error(`unsupported principalType: ${t}`);
}

Type guard

const isPrincipalType = (t) => Object.values(PrincipalType).includes(t);

Prevention

When it happens

Trigger: A caller passes a principalType not equal to 'user'|'group'|'public'|'role' — e.g. 'team', 'org', an integer, or undefined. Reached before the principalId/resourceId checks, so any malformed principalType surfaces here first.

Common situations: Frontend introduced a new share target type without backend enum support. A serialization bug converts the enum value to its index. A refactored call site forgets to pass principalType at all (undefined).

Related errors


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