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
- Send expiresAt as an ISO 8601 string (e.g. new Date().toISOString()) or a millisecond timestamp.
- Validate the date client-side before submission: ensure !Number.isNaN(new Date(value).getTime()).
- 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
- Always send expiresAt as ISO 8601 via toISOString().
- Validate with new Date(v).getTime() before submitting.
- Omit expiresAt entirely when no expiry is intended.
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
- error-status-not-allowed
- error-lastUpdate-param-invalid
- error-updatedSince-param-invalid
- error-invalid-user
- error-invalid-query
AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12).
Data as JSON: /api/errors/9c3b1fb2d5e85510.
Report an issue: GitHub.