Skip to content

Promotions on the map

Mapsted CMS lets a property author promotion surfaces — banners, deal cards, sponsored POI tiles — that render alongside entities on the indoor map. The Mapsted Maps JavaScript API surfaces two events you can subscribe to from the host page:

EventWhen it firesTypical use
promotionClickThe user clicks or taps a promotion surface inside the iframe.Analytics, deep-link routing, custom coupon UI overlay.
promotionDetailsThe iframe opens its built-in promotion-detail view.Suppress the iframe-native panel and render your own host-page chrome.

Both events deliver the same PromotionData payload shape so a single handler can route by event name.

What's in the payload

ts
interface PromotionData {
  /** Stable Mapsted entity bound to this promotion (POI, building, floor). */
  entity?: EntityData;
  /** CMS-authored metadata forwarded verbatim. */
  [key: string]: unknown;
}

Common keys observed in production payloads (the CMS schema is authoritative — narrow at the call site):

  • imageUrl?: string — promotion artwork (CMS-uploaded)
  • headline?: string / subheadline?: string — display text
  • ctaText?: string / ctaUrl?: string — call-to-action
  • validFrom?: string / validTo?: string — ISO 8601 window
  • tenantId?: string — multi-tenant property scoping

This pattern listens for promotionClick, opens a host-page banner anchored to the click target, and lets you (the integrator) own the deal/coupon UI without fighting the iframe.

ts
import { init, on, off, navigateToFloorById } from '@mapsted/maps-js-api';
import type { PromotionData } from '@mapsted/maps-js-api';

const map = await init({
  element: '#map',
  propertyId: 603,
});

const promotionBanner = document.getElementById('promo-banner')!;

const handlePromotionClick = (promo: PromotionData) => {
  // 1. Forward to your analytics layer.
  // EntityData fields: buildingId, floorId, entityId — there is no .id or .name on EntityData
  if (promo.entity) {
    analytics.track('promo_click', {
      entity_id: promo.entity.entityId,
      tenant_id: promo.tenantId,
    });
  }

  // 2. Render your own banner with the CMS-authored copy.
  promotionBanner.innerHTML = `
    <img src="${promo.imageUrl ?? ''}" alt="" />
    <h3>${promo.headline ?? ''}</h3>
    <p>${promo.subheadline ?? ''}</p>
    <a href="${promo.ctaUrl ?? '#'}">${promo.ctaText ?? 'View deal'}</a>
  `;
  promotionBanner.style.display = 'block';

  // 3. (Optional) Pan the map to the promotion's floor.
  if (promo.entity?.floorId) {
    navigateToFloorById(promo.entity.floorId).catch(() => {
      /* graceful fallback — promo still rendered */
    });
  }
};

on('promotionClick', handlePromotionClick);

// Don't forget to clean up on unmount (React effect, Vue onUnmounted, etc.).
// off('promotionClick', handlePromotionClick);

Sanitise CMS HTML

The CMS metadata is forwarded verbatim. If your tenants author HTML that you re-render, sanitise it before injecting into the DOM (e.g. DOMPurify.sanitize). The [key: string]: unknown index signature is intentional — narrow each field at the call site.

Suppressing the iframe-native detail view

If you'd rather render the entire promotion experience yourself (consistent with your app chrome, accessibility audit, etc.) listen for promotionDetails instead and prevent the user from opening the iframe-native UI by handling the click on the host page first.

ts
on('promotionDetails', (promo) => {
  // The iframe is about to open its detail view — open yours and
  // dismiss any iframe state via a no-op selectEntity call.
  openHostDetailPanel(promo);
});

Multi-tenant retail patterns

Property tenants commonly want their promotions to follow tenant-scoped rules. Use the tenantId metadata field plus the CMS audience targeting to gate which promotions surface where:

ts
on('promotionClick', (promo) => {
  if (promo.tenantId !== currentTenantId) return; // out of scope
  showLoyaltyOffer(promo);
});

Common questions

Do promotion events fire on hover? No — only on click/tap. The iframe runtime debounces sub-50ms repeats per entity to prevent double-fires from accidental double-tap on touch devices.

Can I author promotions client-side? No. Promotions are authored in the Mapsted CMS and surfaced server-side with property-scoped audience targeting. The Mapsted Maps JavaScript API is the consumption layer.

Does the CMS schema vary? Yes — different property tenants may author different metadata fields. Treat the index signature as unknown and feature-detect at the call site. The entity field is the only stable contract.

See also