How to fix messaging/invalid-argument in Firebase

FirebaseINTERMEDIATEHIGHUpdated Dec 22, 2025· 235 views

The Firebase Cloud Messaging "messaging/invalid-argument" error occurs when FCM rejects a send request due to invalid parameters, malformed tokens, oversized payloads, or incorrect message structure. This is a critical error that prevents messages from being delivered to devices.

Quick fix

Fixes messaging/invalid-argument

const registrationToken = '...'; // Should be ~152 characters

// Check token format
if (!registrationToken || registrationToken.length < 100) {
  console.error('Token appears invalid - too short or missing');
}

// Token format validation
const fcmTokenRegex = /^[a-zA-Z0-9_-]{100,200}$/;
if (!fcmTokenRegex.test(registrationToken)) {
  console.warn('Token format may be invalid');
}

// Verify token before sending
console.log('Token length:', registrationToken.length); // Should be ~152
console.log('Token start:', registrationToken.substring(0, 20));
  1. Verify the registration token format and validity
  2. Check for batch size limits
  3. Validate message payload size
  4. Fix message priority for Apple devices
firebase — error
const registrationToken = '...'; // Should be ~152 characters
// Check token format
if (!registrationToken || registrationToken.length < 100) {
console.error('Token appears invalid - too short or missing');
}
// Token format validation
const fcmTokenRegex = /^[a-zA-Z0-9_-]{100,200}$/;
if (!fcmTokenRegex.test(registrationToken)) {
console.warn('Token format may be invalid');
}
// Verify token before sending
console.log('Token length:', registrationToken.length); // Should be ~152
console.log('Token start:', registrationToken.substring(0, 20));
messaging/invalid-argument

What this error means

The "messaging/invalid-argument" error in Firebase Cloud Messaging (FCM) is returned by the FCM service when a message request violates one of FCM's validation rules. Unlike client-side parameter errors, this error comes from the FCM API server after validating the complete request. This error can indicate several different problems depending on context: invalid device registration tokens, message payloads that exceed size limits, sending to more than 1000 tokens in a single request, incorrect message structure for the target platform, or invalid priority settings for specific device types. The error code "messaging/invalid-argument" is a broad error category that requires examining the error message details and your request parameters to determine the root cause. Each cause requires a different fix approach.

Typical symptoms

  • FCM send requests fail with "messaging/invalid-argument" error response
  • Error occurs intermittently only for certain device tokens or message types
  • Messages fail to send after device updates or app reinstalls on some devices
  • Bulk send operations fail but single-device sends work
  • Push notifications using specific features (like content-available) fail
  • iOS/Android silent notifications rejected by FCM
  • Error occurs when sending data messages with high priority to Apple devices

Common causes

  1. Registration token is invalid, expired, or malformed (most common)
  2. Batch send exceeds 1000 device tokens per request (FCM maximum)
  3. Message payload exceeds maximum size (4096 bytes total, 4000 bytes for notifications)
  4. Message priority set to "high" for data-only messages or Apple devices (must be "normal")
  5. Invalid APNs token format when sending to iOS devices
  6. Registration token is stale (inactive for 270+ days on Android)
  7. Nested message structure is incorrect or incomplete
  8. Token format validation fails (expected 152 character format)
  9. Sending silent notifications (content-available) with invalid token
  10. Version mismatch between client token and server expectations

How to fix "messaging/invalid-argument"

1Verify the registration token format and validity

The most common cause of this error is an invalid or incorrectly formatted registration token. Check your token format:

javascript
const registrationToken = '...'; // Should be ~152 characters

// Check token format
if (!registrationToken || registrationToken.length < 100) {
  console.error('Token appears invalid - too short or missing');
}

// Token format validation
const fcmTokenRegex = /^[a-zA-Z0-9_-]{100,200}$/;
if (!fcmTokenRegex.test(registrationToken)) {
  console.warn('Token format may be invalid');
}

// Verify token before sending
console.log('Token length:', registrationToken.length); // Should be ~152
console.log('Token start:', registrationToken.substring(0, 20));

If the token is invalid, stale, or malformed:
1. Request a fresh token from the client device
2. Delete the invalid token from your database
3. Ask the user to reinstall the app if the token won't refresh
4. Implement token refresh logic with expiration handling

2Check for batch size limits

FCM has a hard limit of 1000 registration tokens per send request. Split larger batches:

javascript
const registrationTokens = [/* array of tokens */];

// Check batch size
if (registrationTokens.length > 1000) {
  console.warn(`Batch size is ${registrationTokens.length} - exceeds FCM limit of 1000`);
}

// Correct way to send to 1000+ devices
async function sendToLargeDeviceSet(tokens, message) {
  const BATCH_SIZE = 500; // Use smaller batches for reliability

  for (let i = 0; i < tokens.length; i += BATCH_SIZE) {
    const batch = tokens.slice(i, i + BATCH_SIZE);

    try {
      const response = await admin.messaging().sendMulticast({
        tokens: batch,
        notification: message.notification,
        data: message.data,
        android: message.android,
        apns: message.apns,
        webpush: message.webpush
      });

      console.log(`Batch ${Math.floor(i / BATCH_SIZE) + 1}: Success: ${response.successCount}, Failed: ${response.failureCount}`);

      // Handle failures
      response.responses.forEach((resp, index) => {
        if (!resp.success && resp.error) {
          const token = batch[index];
          console.error(`Failed to send to ${token}: ${resp.error.code}`);

          // Delete invalid tokens
          if (resp.error.code === 'messaging/invalid-registration-token' ||
              resp.error.code === 'messaging/invalid-argument') {
            // Delete from your database
            await deleteTokenFromDatabase(token);
          }
        }
      });
    } catch (error) {
      console.error(`Batch ${Math.floor(i / BATCH_SIZE) + 1} failed:`, error);
    }
  }
}

// Send to device set
await sendToLargeDeviceSet(registrationTokens, message);

Always batch requests to 500-1000 tokens to avoid hitting limits.

3Validate message payload size

Firebase has strict payload size limits. Check your message size before sending:

javascript
const message = {
  token: registrationToken,
  notification: {
    title: 'Notification Title',
    body: 'Notification body text'
  },
  data: {
    key1: 'value1',
    key2: 'value2'
  }
};

// Check payload size
function checkMessageSize(message) {
  const serialized = JSON.stringify(message);
  const bytes = new TextEncoder().encode(serialized).length;

  console.log(`Total payload size: ${bytes} bytes`);
  console.log('FCM limits:');
  console.log('  - Notification: 4000 bytes');
  console.log('  - Data object: 4000 bytes');
  console.log('  - Total message: 4096 bytes');

  if (bytes > 4096) {
    console.error('ERROR: Message exceeds 4096 byte limit');
    return false;
  }

  if (message.notification) {
    const notifSize = JSON.stringify(message.notification).length;
    if (notifSize > 4000) {
      console.error('ERROR: Notification payload exceeds 4000 bytes');
      return false;
    }
  }

  if (message.data) {
    const dataSize = JSON.stringify(message.data).length;
    if (dataSize > 4000) {
      console.error('ERROR: Data payload exceeds 4000 bytes');
      return false;
    }
  }

  return true;
}

// Validate before sending
if (!checkMessageSize(message)) {
  console.error('Message is too large - reduce content or split into multiple messages');
  // Truncate or remove non-essential data
  message.data = message.data ? { /* essential fields only */ } : undefined;
}

await admin.messaging().send(message);

Keep payloads small:
- Notification title/body: maximum 240 characters combined
- Data values: keep each value under 1000 characters
- Total message: under 4096 bytes

4Fix message priority for Apple devices

iOS and APNs have strict priority requirements. High priority data messages are rejected:

javascript
const message = {
  token: apnsToken,
  notification: {
    title: 'Title',
    body: 'Body'
  },
  data: {
    customKey: 'customValue'
  }
};

// WRONG: High priority for data-only message on iOS
// await messaging.send({
//   token: apnsToken,
//   data: { key: 'value' },
//   webpush: { headers: { TTL: '3600' } },
//   // Missing apns, causes INVALID_ARGUMENT
// });

// CORRECT: Proper priority handling for different platforms
const correctMessage = {
  token: apnsToken,
  notification: {
    title: 'Push Title',
    body: 'Push Body'
  },
  data: {
    action: 'open_details',
    id: '123'
  },
  // For iOS/APNs: Priority MUST be normal (5) or omitted for silent
  apns: {
    headers: {
      'apns-priority': '10' // 10 for alerts, 5 for background
    },
    payload: {
      aps: {
        alert: {
          title: 'Push Title',
          body: 'Push Body'
        },
        sound: 'default',
        'content-available': 0 // Set to 1 only for silent notifications
      }
    }
  },
  // For Android: Can use high priority
  android: {
    priority: 'high', // Only use high for time-sensitive messages
    notification: {
      title: 'Push Title',
      body: 'Push Body'
    }
  }
};

await admin.messaging().send(correctMessage);

// Platform-specific sending
async function sendPlatformSpecific(token, isIOS) {
  if (isIOS) {
    // iOS: Use normal priority, support content-available
    return messaging.send({
      token,
      notification: { title: 't', body: 'b' },
      apns: {
        headers: { 'apns-priority': '10' },
        payload: { aps: { sound: 'default' } }
      }
    });
  } else {
    // Android: Can use high priority
    return messaging.send({
      token,
      notification: { title: 't', body: 'b' },
      android: { priority: 'high' }
    });
  }
}

Key rules:
- iOS high-priority messages must include a notification
- Data-only messages to iOS must use normal priority (apns-priority: 5)
- Silent notifications (content-available) should not use high priority
- Android can use high priority for any message type

5Check token freshness and implement cleanup

Stale tokens from inactive devices cause INVALID_ARGUMENT errors. Implement token lifecycle management:

javascript
// Track token age
interface TokenRecord {
  token: string;
  userId: string;
  createdAt: Date;
  lastUsedAt: Date;
  platform: 'ios' | 'android' | 'web';
}

// Mark tokens as stale if inactive for 270+ days
async function removeStaleTokens() {
  const STALE_THRESHOLD = 270 * 24 * 60 * 60 * 1000; // 270 days in ms
  const now = new Date();

  const staleTokens = await db.tokens.find({
    lastUsedAt: {
      $lt: new Date(now - STALE_THRESHOLD)
    }
  });

  console.log(`Found ${staleTokens.length} stale tokens`);

  // Delete stale tokens
  for (const token of staleTokens) {
    await db.tokens.deleteOne({ token: token.token });
  }
}

// Handle token errors during send
async function sendWithTokenCleanup(token, message) {
  try {
    return await admin.messaging().send({
      token,
      ...message
    });
  } catch (error) {
    // Delete invalid or unregistered tokens
    if (error.code === 'messaging/invalid-registration-token' ||
        error.code === 'messaging/registration-token-not-registered' ||
        error.code === 'messaging/invalid-argument') {

      console.log(`Deleting invalid token: ${token}`);
      await db.tokens.deleteOne({ token });

      // Notify user or re-request token
      await db.users.updateOne(
        { tokens: token },
        { $pull: { tokens: token } }
      );
    }
    throw error;
  }
}

// Periodic cleanup
setInterval(removeStaleTokens, 24 * 60 * 60 * 1000); // Daily

// On successful send, update lastUsedAt
async function sendAndTrack(token, message) {
  const result = await sendWithTokenCleanup(token, message);

  await db.tokens.updateOne(
    { token },
    { lastUsedAt: new Date() }
  );

  return result;
}

Implement:
- Track token creation and last used dates
- Delete tokens older than 270 days
- Delete tokens immediately after INVALID_ARGUMENT errors
- Request fresh tokens periodically from clients

6Debug using detailed error information

Get full error details to pinpoint the exact cause:

javascript
async function debugInvalidArgument(token, message) {
  try {
    const response = await admin.messaging().send({
      token,
      ...message
    });
    console.log('Send successful:', response);
  } catch (error) {
    console.error('FCM Error Details:');
    console.error('  Code:', error.code); // messaging/invalid-argument
    console.error('  Message:', error.message); // Full error message
    console.error('  Full error:', JSON.stringify(error, null, 2));

    // Check error.errorInfo for FCM-specific details
    if (error.errorInfo) {
      console.error('  Error Info:', error.errorInfo);
    }

    // Response from HTTP v1 API includes details array
    if (error.cause && error.cause.response) {
      console.error('  Response status:', error.cause.response.status);
      console.error('  Response data:', error.cause.response.data);

      const { details } = error.cause.response.data.error;
      if (details && details.length > 0) {
        console.error('  FCM details:', details);
        details.forEach(detail => {
          console.error('    -', detail['@type'], ':', detail);
        });
      }
    }

    // Analyze the error message
    const errorMsg = error.message || '';
    if (errorMsg.includes('registration token')) {
      console.error('ROOT CAUSE: Invalid registration token');
    } else if (errorMsg.includes('data too large')) {
      console.error('ROOT CAUSE: Message payload exceeds limits');
    } else if (errorMsg.includes('1000')) {
      console.error('ROOT CAUSE: Batch size exceeds 1000 tokens');
    } else if (errorMsg.includes('priority') || errorMsg.includes('high')) {
      console.error('ROOT CAUSE: Invalid priority for message type/platform');
    }
  }
}

// Test with sample data
const testToken = 'your-test-token-here';
const testMessage = {
  notification: { title: 'Test', body: 'Message' }
};

await debugInvalidArgument(testToken, testMessage);

The error message often contains hints:
- "registration token" → invalid/expired token
- "data too large" → payload exceeds 4096 bytes
- "1000" → batch too large
- "priority" → incorrect priority for platform
- "APNs" → iOS-specific validation issue

Advanced notes

### Firebase Cloud Messaging Validation Rules

Token Validation:
- Tokens must be 100-152 characters
- Tokens expire after 270 days of inactivity on Android
- iOS tokens can become invalid if app is uninstalled
- Web tokens expire based on service worker registration status
- Testing tokens (if using FCM emulator) have different formats

Payload Limits:
- Total message: 4096 bytes maximum
- Notification object: 4000 bytes maximum
- Data object: 4000 bytes maximum
- Single data value: no hard limit but keep under 1000 characters
- Notification title: 240 characters recommended maximum

Platform-Specific Requirements:
- iOS (APNs): High priority (apns-priority: 10) requires notification, silent notifications use normal priority
- Android: High priority bypasses doze mode, triggers immediate delivery
- Web (Webpush): TTL header controls message retention (in seconds)
- Multiple platforms: Each platform gets its own config to avoid INVALID_ARGUMENT

Message Structure Requirements:
- Must have either token, topic, or condition
- tokens array for sendMulticast (max 500-1000 tokens)
- condition for topic-based or complex logical conditions
- Message must include at least notification OR data (not empty)

### Common Patterns by Platform

Android Specific:

javascript
const androidMessage = {
  token: androidToken,
  notification: {
    title: 'Title',
    body: 'Body',
    tag: 'notification-tag',
    color: '#ff0000'
  },
  android: {
    priority: 'high', // high or normal
    ttl: 3600,
    restrictedPackageName: 'com.your.app'
  }
};

iOS Specific:

javascript
const iosMessage = {
  token: iosToken,
  notification: {
    title: 'Title',
    body: 'Body'
  },
  apns: {
    headers: {
      'apns-priority': '10', // 10 for alert, 5 for background
      'apns-expiration': String(Math.floor(Date.now() / 1000) + 3600)
    },
    payload: {
      aps: {
        alert: {
          title: 'Title',
          body: 'Body'
        },
        sound: 'default',
        'content-available': 0 // 1 for silent notifications
      }
    }
  }
};

Web Specific:

javascript
const webMessage = {
  token: webToken,
  notification: {
    title: 'Title',
    body: 'Body',
    icon: 'https://example.com/icon.png'
  },
  webpush: {
    headers: {
      TTL: '3600' // Seconds to retain message
    },
    data: {
      url: 'https://example.com/page'
    }
  }
};

### Error Recovery Patterns

Implement exponential backoff for transient failures:

javascript
async function sendWithRetry(token, message, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await admin.messaging().send({ token, ...message });
    } catch (error) {
      if (error.code === 'messaging/invalid-argument') {
        // Don't retry invalid argument errors
        throw error;
      }

      const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
      console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms`);
      await new Promise(r => setTimeout(r, delay));
    }
  }
  throw new Error('Max retries exceeded');
}

Monitor and alert on INVALID_ARGUMENT spikes:

javascript
const errorMetrics = {
  'messaging/invalid-argument': 0,
  'messaging/registration-token-not-registered': 0,
  'messaging/third-party-auth-error': 0
};

async function trackSendError(error) {
  const errorCode = error.code;
  if (errorCode in errorMetrics) {
    errorMetrics[errorCode]++;

    // Alert if error rate is too high
    if (errorMetrics[errorCode] > 100) {
      console.warn(`High error rate for ${errorCode}`);
      // Send alert, page on-call, etc.
    }
  }
}

### Testing Strategies

Use Firebase Emulator Suite for local testing without risking real tokens:

bash
firebase emulators:start --only messaging

This helps identify invalid-argument issues before production deployment.

Frequently asked questions

What does "messaging/invalid-argument" mean?
The "messaging/invalid-argument" error in Firebase Cloud Messaging (FCM) is returned by the FCM service when a message request violates one of FCM's validation rules. Unlike client-side parameter errors, this error comes from the FCM API server after validating the complete request. This error can indicate several different problems depending on context: invalid device registration tokens, message payloads that exceed size limits, sending to more than 1000 tokens in a single request, incorrect message structure for the target platform, or invalid priority settings for specific device types.
How do I fix "messaging/invalid-argument"?
The Firebase Cloud Messaging "messaging/invalid-argument" error occurs when FCM rejects a send request due to invalid parameters, malformed tokens, oversized payloads, or incorrect message structure. This is a critical error that prevents messages from being delivered to devices.
What causes "messaging/invalid-argument"?
Registration token is invalid, expired, or malformed (most common) Batch send exceeds 1000 device tokens per request (FCM maximum) Message payload exceeds maximum size (4096 bytes total, 4000 bytes for notifications)
Is the "messaging/invalid-argument" error serious?
Yes. This is a high-severity error that usually blocks work until it is fixed, but the steps above resolve it.

Official resources & further reading