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 ​