RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-date

error-invalid-date

Error message

Invalid expiresAt date string

What it means

Thrown by POST users.setStatus when the 'expiresAt' body field is present but cannot be parsed by the Date constructor into a valid timestamp (new Date(expiresAt).getTime() is NaN). This is the malformed-date case, distinct from the past-date case (609).

Source

Thrown at apps/meteor/server/api/v1/users.ts:2023

					return getUserFromParams(this.bodyParams);
				}
			})();

			if (!user) {
				return API.v1.forbidden();
			}

			const { status, message, expiresAt } = this.bodyParams;

			if (message && !settings.get('Accounts_AllowUserStatusMessageChange')) {
				throw new Meteor.Error('error-not-allowed', 'Change status is not allowed', {
					method: 'users.setStatus',
				});
			}

			const statusExpiresAt = expiresAt ? new Date(expiresAt) : undefined;
			if (statusExpiresAt && Number.isNaN(statusExpiresAt.getTime())) {
				throw new Meteor.Error('error-invalid-date', 'Invalid expiresAt date string', {
					method: 'users.setStatus',
				});
			}

			if (statusExpiresAt && statusExpiresAt.getTime() <= Date.now()) {
				throw new Meteor.Error('error-invalid-date', 'expiresAt must be a future date', {
					method: 'users.setStatus',
				});
			}

			// If status is missing (message-only update), keep the user's chosen status (statusDefault),
			// not the computed status — otherwise a transient auto-away/offline gets pinned as a manual claim.
			const effectiveStatus = status || user.statusDefault || ('online' as UserStatus);

			if (effectiveStatus === 'offline' && !settings.get('Accounts_AllowInvisibleStatusOption')) {
				throw new Meteor.Error('error-status-not-allowed', 'Invisible status is disabled', {
					method: 'users.setStatus',
				});

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Send expiresAt as an ISO 8601 string (e.g. new Date().toISOString()) or a millisecond timestamp.
  2. Validate the date client-side before submission: ensure !Number.isNaN(new Date(value).getTime()).
  3. Omit expiresAt entirely when no expiry is intended rather than sending an empty or placeholder string.

Example fix

// before
POST('/api/v1/users.setStatus', { status:'away', expiresAt: 'tomorrow at 5' })

// after
const d = new Date('tomorrow at 5')
if (Number.isNaN(d.getTime())) throw new ClientError('bad date')
POST('/api/v1/users.setStatus', { status:'away', expiresAt: d.toISOString() })
Defensive patterns

Strategy: validation

Validate before calling

function validExpiresAt(v) {
  if (v == null) return true;
  const d = new Date(v);
  return !Number.isNaN(d.getTime());
}
if (!validExpiresAt(body.expiresAt)) delete body.expiresAt;

Type guard

function isParsableDate(v) {
  if (v == null) return true;
  const d = new Date(v);
  return d instanceof Date && !Number.isNaN(d.getTime());
}

Try / catch

try { await POST('/api/v1/users.setStatus', body); }
catch (e) {
  if (e?.error === 'error-invalid-date') { delete body.expiresAt; await POST('/api/v1/users.setStatus', body); return; }
  throw e;
}

Prevention

When it happens

Trigger: POST /api/v1/users.setStatus with expiresAt set to a non-ISO string like 'tomorrow', '2024-13-45', an empty string after trimming, or a locale-formatted date the constructor cannot parse.

Common situations: Client formats the date with a locale-specific string instead of ISO 8601. Time-zone offset missing. Empty string slips through from an unfilled form field that is still included in the payload.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/9c3b1fb2d5e85510. Report an issue: GitHub.