RocketChat/Rocket.Chat · error · Meteor.Error
error-invalid-date
error-invalid-date
Error message
Invalid expiresAt date string
What it means
Thrown by POST /api/v1/users.setStatus when expiresAt is supplied but is not parseable as a date — new Date(expiresAt) yields NaN. It uses code error-invalid-date with method 'users.setStatus' and only checks this format problem; range (future) is a separate error.
Solutions
- Send an ISO-8601 string: new Date(x).toISOString()
- Validate with !Number.isNaN(Date.parse(expiresAt)) before calling
- Pass milliseconds number instead of an ambiguous string if your client supports it
- Strip/trim user-supplied date inputs
Example fix
// before
await sdk.post('users.setStatus', { status, expiresAt: '31/12/2026' });
// after
await sdk.post('users.setStatus', { status, expiresAt: new Date('2026-12-31T00:00:00Z').toISOString() }); Defensive patterns
Strategy: validation
Validate before calling
if (expiresAt != null && Number.isNaN(Date.parse(expiresAt))) throw new TypeError(`invalid expiresAt: ${expiresAt}`);
await sdk.post('users.setStatus', { status, message, expiresAt: expiresAt ? new Date(expiresAt).toISOString() : undefined }); Type guard
const isValidIsoDate = (v: unknown): v is string => typeof v === 'string' && !Number.isNaN(Date.parse(v));
Try / catch
try { await sdk.post('users.setStatus', body); } catch (e) { if (e.response?.data?.errorType === 'error-invalid-date' && /Invalid expiresAt/.test(e.response.data.message)) { body.expiresAt = new Date().toISOString(); await sdk.post('users.setStatus', body); } else throw e; } Prevention
- Always serialize dates with toISOString()
- Date.parse-validate user input before sending
- Decide epoch-seconds vs milliseconds convention once
When it happens
Trigger: POST users.setStatus {"status":"away","expiresAt":"tomorrow"}; numeric epoch seconds sent instead of an ISO-8601 string where the runtime parses it as invalid; localised date formats like 31/12/2026; trailing whitespace or BOM in the value.
Common situations: Client code formatting dates with locale-specific toString(); passing epoch seconds vs milliseconds confusion; strings from a form input never normalized.
Related errors
- error-roomId-param-invalid
- error-status-not-allowed
- The "end" query parameter must be a valid date.
- The " " parameter must be a valid date.
- The " .end" query parameter must be a valid date.
AI-assisted analysis of RocketChat/Rocket.Chat@e4b8178b20 (2026-08-18).
Data as JSON: /api/errors/9c3b1fb2d5e85510.
Report an issue: GitHub.
Appendix: source
Thrown at apps/meteor/server/api/v1/users.ts:2059
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 e4b8178b20)