How to fix auth/claims-too-large: Custom claims payload exceeds 1000-byte limit in Firebase
This error occurs when attempting to set custom claims on a Firebase user that exceed the 1000-byte size limit. Firebase restricts custom claims to keep authentication tokens lightweight and performant.
Quick fix
Fixes auth/claims-too-large
import * as admin from 'firebase-admin';
async function checkClaimsSize(uid: string) {
const user = await admin.auth().getUser(uid);
const claims = user.customClaims || {};
const claimsString = JSON.stringify(claims);
const byteSize = Buffer.byteLength(claimsString, 'utf8');
console.log('Claims:', claims);
console.log('Size:', byteSize, 'bytes');
console.log('Remaining:', 1000 - byteSize, 'bytes');
return byteSize;
}- Measure your current claims size
- Reduce claims to minimal identifiers
- Move large data to Firestore
- Optimize claim keys and values
import * as admin from 'firebase-admin';async function checkClaimsSize(uid: string) {const user = await admin.auth().getUser(uid);const claims = user.customClaims || {};const claimsString = JSON.stringify(claims);const byteSize = Buffer.byteLength(claimsString, 'utf8');console.log('Claims:', claims);console.log('Size:', byteSize, 'bytes');console.log('Remaining:', 1000 - byteSize, 'bytes');return byteSize;}auth/claims-too-large: Custom claims payload exceeds 1000-byte limit
What this error means
This error is thrown by Firebase Authentication when you try to set custom claims on a user account that exceed the 1000-byte limit. Custom claims are additional key-value pairs added to a user's ID token to implement role-based access control or store authorization metadata. Firebase enforces this limit because custom claims are embedded in the JWT (JSON Web Token) that gets sent with every authenticated request. Since tokens are transmitted with each API call to Firebase services (Firestore, Storage, Functions, etc.), larger tokens increase network overhead and can degrade application performance. The 1000-byte limit applies to the entire serialized JSON object of custom claims, not individual claim values. This includes the JSON structure itself (braces, quotes, commas), so the actual data you can store is slightly less than 1000 bytes.
Typical symptoms
- Error thrown when calling admin.auth().setCustomUserClaims() with large claim objects
- Authentication works but fails when trying to update user claims
- Application logs show "auth/claims-too-large" error messages
- Cloud Functions fail when attempting to set user roles or permissions
- Claims successfully set for some users but fail for others with more data
Common causes
- Custom claims object contains too much data (arrays, nested objects, long strings)
- Storing entire user profile data or configurations in custom claims instead of just identifiers
- Including verbose permission lists or multiple large role definitions
- Adding redundant data that could be stored in Firestore or Realtime Database
- Accumulating claims over time without removing obsolete ones
- Using long descriptive keys or values that could be shortened or encoded
How to fix "auth/claims-too-large: Custom claims payload exceeds 1000-byte limit"
Calculate how many bytes your claims object is using:
import * as admin from 'firebase-admin';
async function checkClaimsSize(uid: string) {
const user = await admin.auth().getUser(uid);
const claims = user.customClaims || {};
const claimsString = JSON.stringify(claims);
const byteSize = Buffer.byteLength(claimsString, 'utf8');
console.log('Claims:', claims);
console.log('Size:', byteSize, 'bytes');
console.log('Remaining:', 1000 - byteSize, 'bytes');
return byteSize;
}This helps you understand how close you are to the limit and identify which claims consume the most space.
Keep only essential authorization data in custom claims. Use simple role names or IDs instead of full objects:
Bad approach (too large):
await admin.auth().setCustomUserClaims(uid, {
role: 'admin',
permissions: ['read', 'write', 'delete', 'manage-users', 'manage-roles'],
metadata: {
department: 'Engineering',
team: 'Backend',
location: 'San Francisco',
manager: 'john@example.com'
},
features: {
betaAccess: true,
premiumFeatures: ['feature1', 'feature2', 'feature3']
}
});Good approach (minimal):
await admin.auth().setCustomUserClaims(uid, {
role: 'admin',
dept: 'eng',
premium: true
});Store detailed information in Firestore and reference it when needed.
Store comprehensive user data in Firestore and use custom claims only for access control:
// Set minimal claims for authorization
await admin.auth().setCustomUserClaims(uid, {
role: 'admin',
orgId: 'org_123'
});
// Store detailed user data in Firestore
await admin.firestore().collection('userProfiles').doc(uid).set({
permissions: ['read', 'write', 'delete', 'manage-users'],
metadata: {
department: 'Engineering',
team: 'Backend',
location: 'San Francisco',
manager: 'john@example.com'
},
features: {
betaAccess: true,
premiumFeatures: ['feature1', 'feature2', 'feature3']
}
});In your client code, fetch additional data when needed:
import { getAuth } from 'firebase/auth';
import { doc, getDoc } from 'firebase/firestore';
async function getUserData() {
const user = getAuth().currentUser;
const token = await user?.getIdTokenResult();
// Access minimal claims from token
const role = token?.claims.role;
const orgId = token?.claims.orgId;
// Fetch detailed data from Firestore
const profileDoc = await getDoc(doc(db, 'userProfiles', user.uid));
const profile = profileDoc.data();
return { role, orgId, ...profile };
}Use shorter key names and abbreviations to save space:
// Before (74 bytes)
{
"isAdministrator": true,
"organizationIdentifier": "org_12345",
"subscriptionLevel": "premium"
}
// After (43 bytes) - saves 31 bytes
{
"admin": true,
"org": "org_12345",
"sub": "premium"
}For arrays, use numeric IDs or codes instead of descriptive strings:
// Before (85 bytes)
{
"permissions": ["read-users", "write-users", "delete-users"]
}
// After (32 bytes) - saves 53 bytes
{
"perms": [1, 2, 3] // Map codes to permissions in your app
}Create a helper function that validates claims size before setting them:
import * as admin from 'firebase-admin';
const MAX_CLAIMS_SIZE = 1000;
async function setCustomClaimsSafely(
uid: string,
claims: Record<string, any>
): Promise<void> {
const claimsString = JSON.stringify(claims);
const byteSize = Buffer.byteLength(claimsString, 'utf8');
if (byteSize > MAX_CLAIMS_SIZE) {
throw new Error(
'Claims size (' + byteSize + ' bytes) exceeds limit (' + MAX_CLAIMS_SIZE + ' bytes). ' +
'Reduce claims by ' + (byteSize - MAX_CLAIMS_SIZE) + ' bytes.'
);
}
await admin.auth().setCustomUserClaims(uid, claims);
console.log('Claims set successfully (' + byteSize + '/' + MAX_CLAIMS_SIZE + ' bytes)');
}
// Usage
try {
await setCustomClaimsSafely(uid, { role: 'admin', org: 'org_123' });
} catch (error) {
console.error('Failed to set claims:', error.message);
// Fall back to storing in Firestore
}Advanced notes
Token Propagation Delay
Custom claims are embedded in the user's ID token when it's issued. After updating claims, the user must refresh their token to see the changes:
// Force token refresh on client
const auth = getAuth();
if (auth.currentUser) {
await auth.currentUser.getIdToken(true); // Force refresh
const token = await auth.currentUser.getIdTokenResult();
console.log('Updated claims:', token.claims);
}Tokens are automatically refreshed every hour, but you may need to force a refresh immediately after updating claims for real-time permission changes.
Hybrid Approach: Claims + Firestore
A common pattern is to use custom claims for coarse-grained access control (admin/user roles) and Firestore for fine-grained permissions:
// Custom claims (fast, available in Security Rules)
{ "role": "admin", "orgId": "org_123" }
// Firestore security rules
allow read, write: if request.auth.token.role == "admin"
&& resource.data.orgId == request.auth.token.orgId;
// Detailed permissions in Firestore (flexible, no size limits)
/userProfiles/{uid}/permissions: {
canManageUsers: true,
canDeleteData: false,
allowedFeatures: [...],
restrictions: [...]
}Character Encoding Considerations
The 1000-byte limit is based on UTF-8 encoding. Multi-byte characters (emojis, special characters) consume more bytes than ASCII characters. For example, an emoji can take 3-4 bytes.
Monitoring Claims Usage
Set up monitoring to track claims size across your user base:
async function auditClaimsUsage() {
const { users } = await admin.auth().listUsers(1000);
const stats = users
.map(user => ({
uid: user.uid,
size: Buffer.byteLength(JSON.stringify(user.customClaims || {}), 'utf8')
}))
.filter(s => s.size > 800); // Flag users near limit
console.log('Users approaching claims limit:', stats);
}Frequently asked questions
- What does "auth/claims-too-large" mean?
- This error is thrown by Firebase Authentication when you try to set custom claims on a user account that exceed the 1000-byte limit. Custom claims are additional key-value pairs added to a user's ID token to implement role-based access control or store authorization metadata. Firebase enforces this limit because custom claims are embedded in the JWT (JSON Web Token) that gets sent with every authenticated request.
- How do I fix "auth/claims-too-large"?
- This error occurs when attempting to set custom claims on a Firebase user that exceed the 1000-byte size limit. Firebase restricts custom claims to keep authentication tokens lightweight and performant.
- What causes "auth/claims-too-large"?
- Custom claims object contains too much data (arrays, nested objects, long strings) Storing entire user profile data or configurations in custom claims instead of just identifiers Including verbose permission lists or multiple large role definitions
- Is the "auth/claims-too-large" error serious?
- It is a medium-severity error โ more of a blocker than a risk. The steps above resolve it.