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.
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.
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: '[email protected]'
},
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: '[email protected]'
},
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
}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);
}messaging/UNSPECIFIED_ERROR: No additional information available
How to fix "messaging/UNSPECIFIED_ERROR: No additional information available" in Firebase Cloud Messaging
App Check: reCAPTCHA Score Too Low
App Check reCAPTCHA Score Too Low
storage/invalid-url: Invalid URL format for Cloud Storage reference
How to fix invalid URL format in Firebase Cloud Storage
auth/missing-uid: User ID identifier required
How to fix "auth/missing-uid: User ID identifier required" in Firebase
auth/invalid-argument: Invalid parameter passed to method
How to fix "auth/invalid-argument: Invalid parameter passed to method" in Firebase