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 ObjectIdsView on GitHub (pinned to 5ff282f900)
Solutions
- Pass principalType as one of PrincipalType.USER, .GROUP, .PUBLIC, or .ROLE.
- Type the parameter as PrincipalType in TypeScript so the compiler rejects invalid values.
- 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
- Type the parameter as PrincipalType in TS so invalid values fail to compile.
- Drive the principalType dropdown in the UI directly from the enum values.
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
- Invalid resourceType: ${resourceType}. Valid types: ${validT
- User principal not found
- Group principal not found
- Principal ID is required for user, group, and role principal
- Invalid role ID: ${principalId}
AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12).
Data as JSON: /api/errors/ed23a8187437e5d0.
Report an issue: GitHub.