RocketChat/Rocket.Chat · error · Error

invalid view

Error message

invalid view

What it means

`CloudAnnouncementsModule.viewClosed` throws `Error('invalid view')` when neither `payload.payload.view.viewId` nor `payload.payload.view.id` is present. The module accepts both on purpose: `viewId` is preferred, but legacy announcement banners may only carry `id`; when both are absent it cannot resolve which banner to dismiss.

Source

Thrown at apps/meteor/server/modules/core-apps/cloudAnnouncements.module.ts:63

		return this.handlePayload(payload);
	}

	viewSubmit(payload: UiKitCoreAppViewSubmitPayload): Promise<UiKit.ServerInteraction | undefined> {
		return this.handlePayload(payload);
	}

	async viewClosed(payload: UiKitCoreAppViewClosedPayload): Promise<UiKit.ServerInteraction | undefined> {
		const {
			payload: { view: { viewId, id } = {} },
			user: { _id: userId } = {},
		} = payload;

		if (!userId) {
			throw new Error('invalid user');
		}

		if (!id && !viewId) {
			throw new Error('invalid view');
		}

		if (!payload.triggerId) {
			throw new Error('invalid triggerId');
		}

		// For backwards compatibility: we prefer to use viewId, but some legacy banners
		// may only have id. We fetch all matching banners and prioritize viewId match.
		const bannerIds = [viewId, id].filter((bannerId) => isTruthy(bannerId));
		const banners = await Banners.findByIds(bannerIds).toArray();
		const announcement = banners.find((b) => b._id === viewId) || banners.find((b) => b._id === id);
		if (!announcement) {
			throw new Error('Banner not found');
		}

		await Banner.dismiss(userId, announcement._id);

		const type = announcement.surface === 'banner' ? 'banner.close' : 'modal.close';

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Send the announcement banner's identifier as `payload: { view: { viewId: bannerId } }` (use `id` only for legacy banners).
  2. Re-fetch the current banners for the user and dismiss using the live banner `_id`.
  3. For programmatic dismissal, call `Banner.dismiss(userId, bannerId)` directly.

Example fix

// before
payload: { view: {}, triggerId }

// after
payload: { view: { viewId: bannerId }, triggerId }
Defensive patterns

Strategy: validation

Validate before calling

const { view } = payload.payload ?? {};
if (!view?.viewId && !view?.id) {
  throw new Error('view.viewId (or legacy view.id) is required');
}
await announcementsModule.viewClosed(payload);

Type guard

const hasViewIdentifier = (
  payload: UiKitCoreAppViewClosedPayload,
): payload is UiKitCoreAppViewClosedPayload & { payload: { view: { viewId?: string; id?: string } } } =>
  Boolean(payload.payload?.view && (payload.payload.view.viewId || payload.payload.view.id));

Try / catch

try {
  await announcementsModule.viewClosed(payload);
} catch (error) {
  if (error instanceof Error && error.message === 'invalid view') {
    // refetch banners and rebuild the payload with the live banner id
  }
}

Prevention

When it happens

Trigger: A viewClosed interaction for `cloud-announcements-core` whose `view` object is undefined or contains neither `viewId` nor `id` — e.g. an empty `view: {}`, a malformed modal payload, or fields lost during serialization.

Common situations: Reusing a modal-close payload shape that nests the id elsewhere; test fixtures with only a triggerId; payload transformations that strip the view object.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/4a1c20cc4e329795. Report an issue: GitHub.