Zum Inhalt springen

Von Content Cards zu Banner migrieren

Dieser Leitfaden unterstützt Sie bei der Migration von Content Cards zu Bannern für Anwendungsfälle im Bereich des bannerartigen Messaging. Banner eignen sich ideal für Inline-Anzeigen, persistente In-App- und Web-Nachrichten, die an bestimmten Stellen in Ihrer App erscheinen.

Warum auf Banner migrieren?

  • Wenn Ihr Entwicklerteam benutzerdefinierte Content Cards erstellt oder pflegt, kann die Migration auf Banner diesen laufenden Aufwand reduzieren. Banner ermöglichen es Marketern, die UI direkt zu steuern, sodass Entwickler:innen für andere Aufgaben frei werden.
  • Wenn Sie neue Homepage-Nachrichten, Onboarding-Flows oder persistente Ankündigungen einführen, starten Sie mit Bannern statt auf Content Cards aufzubauen. Sie profitieren von Beginn an von Realtime-Personalisierung, keiner 30-Tage-Ablaufbegrenzung, keiner Größenbeschränkung und nativer Priorisierung.
  • Wenn Sie die 30-Tage-Ablaufbegrenzung umgehen, komplexe Logik für erneute Berechtigung verwalten oder mit veralteter Personalisierung kämpfen – Banner löst diese Probleme nativ.

Banner bieten gegenüber Content Cards für bannerartige Nachrichten mehrere Vorteile:

Beschleunigte Produktion

  • Weniger laufender Engineering-Aufwand erforderlich: Marketer können benutzerdefinierte Nachrichten mit einem Drag-and-Drop-Editor und benutzerdefiniertem HTML erstellen, ohne dass Entwicklerunterstützung für Anpassungen erforderlich ist.
  • Flexible Anpassungsmöglichkeiten: Gestalten Sie direkt im Editor, verwenden Sie HTML oder nutzen Sie bestehende Datenmodelle mit benutzerdefinierten Eigenschaften.

Bessere UX

  • Dynamische Content-Aktualisierungen: Banner aktualisieren die Liquid-Logik und die Berechtigung bei jedem Refresh, sodass Nutzer:innen immer den relevantesten Content sehen.
  • Native Platzierungsunterstützung: Nachrichten erscheinen in bestimmten Kontexten statt in einem Feed und bieten so eine bessere kontextuelle Relevanz.
  • Native Priorisierung: Kontrolle über die Anzeigereihenfolge ohne benutzerdefinierte Logik, was die Verwaltung der Nachrichtenhierarchie erleichtert.

Persistenz

  • Kein Ablauflimit: Banner-Campaigns haben kein 30-Tage-Ablauflimit wie Content Cards, was eine echte Persistenz von Nachrichten ermöglicht.

Wann Sie migrieren sollten

Ziehen Sie eine Migration zu Banner in Betracht, wenn Sie Content Cards verwenden für:

  • Startseiten-Heroes, Produktseiten-Aktionen, Checkout-Angebote
  • Persistente Navigationsankündigungen oder Sidebar-Nachrichten
  • Always-on-Nachrichten, die länger als 30 Tage laufen
  • Nachrichten, bei denen Sie Realtime-Personalisierung und -Berechtigung wünschen

Wann Sie Content Cards beibehalten sollten

Verwenden Sie weiterhin Content Cards, wenn Sie Folgendes benötigen:

  • Feed-Erlebnisse: Alle Anwendungsfälle, die mehrere scrollbare Nachrichten oder einen kartenbasierten „Posteingang“ umfassen.
  • Spezifische Features: Nachrichten, die Aktionscodes erfordern, da Banner diese nicht nativ unterstützen. Banner unterstützen Connected-Content im Early Access.
  • Getriggerte Zustellung: Anwendungsfälle, die zwingend API-getriggerte oder aktionsbasierte Zustellung erfordern. Banner unterstützen zwar keine API-getriggerte oder aktionsbasierte Zustellung, aber die Echtzeit-Berechtigungsprüfung bedeutet, dass Nutzer:innen bei jeder Aktualisierung basierend auf der Segmentzugehörigkeit sofort qualifiziert oder disqualifiziert werden.

Migrationsleitfaden

Einige Beispiele zeigen zwei Abonnement-Muster. Verwenden Sie das Muster, das mit Available in version auf Web SDK 7.0.0, Android SDK 44.0.0 oder Swift SDK 19.0.0 und höher gekennzeichnet ist. Das andere Muster, subscribeTo…Updates (subscribeToUpdates bei Swift), ist ab diesen Versionen veraltet. Weitere Informationen finden Sie unter Von Update-Abonnements migrieren.

Voraussetzungen

Stellen Sie vor der Migration sicher, dass Ihr Braze SDK die Mindestversionsanforderungen erfüllt:

Für Schließungen und erneute Berechtigung sind folgende SDK-Mindestversionen erforderlich:

Updates und Ereignisse abonnieren

Content-Cards-Ansatz

import * as braze from "@braze/web-sdk";

// - Available in version 7.0.0+
braze.subscribeToContentCardsEvents((event) => {
  switch (event.type) {
    case braze.ChannelEventType.CACHE_REPLAY:
    case braze.ChannelEventType.CACHE_LOAD:
    case braze.ChannelEventType.DATA_UPDATED:
      // Handle array of cards
      event.cacheSnapshot.contentCards.cards.forEach(card => {
        console.log("Card:", card.id);
      });
      break;
    case braze.ChannelEventType.ERROR:
      if (event.reason === braze.ChannelErrorReason.FEATURE_DISABLED) {
        console.log("Content Cards is disabled for this workspace.");
      }
      break;
  }
});

braze.subscribeToContentCardsUpdates((updates) => {
  // Handle array of cards
  updates.cards.forEach(card => {
    console.log("Card:", card.id);
  });
});
// - Available in version 44.0.0+
Braze.getInstance(context).subscribeToContentCardsEvents { event ->
  when (event) {
    is ContentCardsEvent.CacheReplay -> {
      event.cacheSnapshot.cards.forEach { card ->
        Log.d(TAG, "Card: ${card.id}")
      }
    }
    is ContentCardsEvent.ErrorEvent -> {
      if (event.reason is ChannelErrorReason.FeatureDisabled) {
        Log.d(TAG, "Content Cards is disabled for this app or user.")
      }
    }
    else -> {}
  }
}

Braze.getInstance(context).subscribeToContentCardsUpdates { event ->
  event.allCards.forEach { card ->
    Log.d(TAG, "Card: ${card.id}")
  }
}
// - Available in version 19.0.0+
let cancellable = braze.contentCards.subscribeToEvents { event in
  switch event {
  case .cacheReplay(let cacheSnapshot), .cacheLoad(let cacheSnapshot):
    // Handle array of cards
    for card in cacheSnapshot.cards {
      print("Card: \(card.id)")
    }
  case .dataUpdated(let cacheSnapshot, _):
    // Handle array of cards
    for card in cacheSnapshot.cards {
      print("Card: \(card.id)")
    }
  default:
    break
  }
}

let cancellable = braze.contentCards.subscribeToUpdates { cards in
  // Handle array of cards
  for card in cards {
    print("Card: \(card.id)")
  }
}
Braze.addListener(Braze.Events.CONTENT_CARDS_UPDATED, (update) => {
  const cards = update.cards;
  // Handle array of cards
  cards.forEach(card => {
    console.log("Card:", card.id);
  });
});
StreamSubscription contentCardsStreamSubscription = braze.subscribeToContentCards((List<BrazeContentCard> contentCards) {
  // Handle array of cards
  for (final card in contentCards) {
    print("Card: ${card.id}");
  }
});

Banner-Ansatz

import * as braze from "@braze/web-sdk";

// - Available in version 7.0.0+
braze.subscribeToBannersEvents((event) => {
  switch (event.type) {
    case braze.ChannelEventType.CACHE_REPLAY:
    case braze.ChannelEventType.CACHE_LOAD:
    case braze.ChannelEventType.DATA_UPDATED: {
      // Get banner for specific placement
      const banner = event.cacheSnapshot.banners["sample_placement_id"];
      if (banner) {
        console.log("Banner received for placement:", banner.placementId);
      }
      break;
    }
    case braze.ChannelEventType.ERROR:
      if (event.reason === braze.ChannelErrorReason.FEATURE_DISABLED) {
        console.log("Banners is disabled for this workspace.");
      }
      break;
  }
});

braze.subscribeToBannersUpdates((banners) => {
  // Get banner for specific placement
  const banner = braze.getBanner("sample_placement_id");
  if (banner) {
    console.log("Banner received for placement:", banner.placementId);
  }
});
// - Available in version 44.0.0+
Braze.getInstance(context).subscribeToBannersEvents { event ->
  when (event) {
    is BannersEvent.CacheReplay, is BannersEvent.CacheLoad, is BannersEvent.DataUpdated -> {
      val banner = Braze.getInstance(context).getBanner("sample_placement_id")
      if (banner != null) {
        Log.d(TAG, "Banner received for placement: ${banner.placementId}")
      }
    }
    is BannersEvent.ErrorEvent -> {
      if (event.reason is ChannelErrorReason.FeatureDisabled) {
        Log.d(TAG, "Banners is disabled for this app or user.")
      }
    }
    else -> {}
  }
}

Braze.getInstance(context).subscribeToBannersUpdates {
  val banner = Braze.getInstance(context).getBanner("sample_placement_id")
  if (banner != null) {
    Log.d(TAG, "Banner received for placement: ${banner.placementId}")
  }
}
// - Available in version 19.0.0+
let cancellable = braze.banners.subscribeToEvents { event in
  switch event {
  case .cacheReplay, .cacheLoad, .dataUpdated:
    // Get banner for specific placement
    braze.banners.getBanner(for: "sample_placement_id") { banner in
      guard let banner = banner else { return }

      print("Banner received for placement: \(banner.placementId)")
    }
  default:
    break
  }
}

let cancellable = braze.banners.subscribeToUpdates { banners in
  // Get banner for specific placement
  braze.banners.getBanner(for: "sample_placement_id") { banner in
    guard let banner = banner else { return }

    print("Banner received for placement: \(banner.placementId)")
  }
}
Braze.addListener(Braze.Events.BANNER_CARDS_UPDATED, (data) => {
  const banners = data.banners;
  // Get banner for specific placement
  Braze.getBanner("sample_placement_id").then(banner => {
    if (banner) {
      console.log("Banner received for placement:", banner.placementId);
    }
  });
});
StreamSubscription bannerStreamSubscription = braze.subscribeToBanners((List<BrazeBanner> banners) {
  // Get banner for specific placement
  braze.getBanner("sample_placement_id").then((banner) {
    if (banner != null) {
      print("Banner received for placement: ${banner.placementId}");
    }
  });
});

Content anzeigen

Content-Cards-Ansatz

// Show default feed UI
braze.showContentCards(document.getElementById("feed"));

// Or manually render cards
const cards = braze.getCachedContentCards();
cards.forEach(card => {
  // Custom rendering logic
  if (card instanceof braze.ClassicCard) {
    // Render classic card
  }
});
// Using default fragment
val fragment = ContentCardsFragment()
supportFragmentManager.beginTransaction()
  .replace(R.id.content_cards_container, fragment)
  .commit()

// Or manually render cards
val cards = Braze.getInstance(context).getCachedContentCards()
cards.forEach { card ->
  when (card) {
    is ClassicCard -> {
      // Render classic card
    }
  }
}
// Using default view controller
let contentCardsController = BrazeContentCardUI.ViewController(braze: braze)
navigationController?.pushViewController(contentCardsController, animated: true)

// Or manually render cards
let cards = braze.contentCards.cards
for card in cards {
  switch card {
  case let card as Braze.ContentCard.Classic:
    // Render classic card
  default:
    break
  }
}
// Launch default feed
Braze.launchContentCards();

// Or manually render cards
const cards = await Braze.getCachedContentCards();
cards.forEach(card => {
  if (card.type === 'CLASSIC') {
    // Render classic card
  }
});
// Launch default feed
braze.launchContentCards();

// Or manually render cards
final cards = await braze.getContentCards();
for (final card in cards) {
  if (card.type == 'CLASSIC') {
    // Render classic card
  }
}
// - Available in version 7.0.0+
braze.subscribeToBannersEvents((event) => {
  if (
    event.type !== braze.ChannelEventType.CACHE_REPLAY &&
    event.type !== braze.ChannelEventType.CACHE_LOAD &&
    event.type !== braze.ChannelEventType.DATA_UPDATED
  ) {
    return;
  }

  const banner = event.cacheSnapshot.banners["sample_placement_id"];
  if (!banner) {
    return;
  }

  const container = document.getElementById("global-banner-container");
  braze.insertBanner(banner, container);

  if (banner.isControl) {
    container.style.display = "none";
  }
});

braze.subscribeToBannersUpdates((banners) => {
  const banner = braze.getBanner("sample_placement_id");
  if (!banner) {
    return;
  }

  const container = document.getElementById("global-banner-container");
  braze.insertBanner(banner, container);

  if (banner.isControl) {
    container.style.display = "none";
  }
});

braze.requestBannersRefresh(["sample_placement_id"]);
// Using BannerView in XML
// <com.braze.ui.banners.BannerView
//     android:id="@+id/banner_view"
//     android:layout_width="match_parent"
//     android:layout_height="wrap_content"
//     app:placementId="sample_placement_id" />

// Or programmatically
val bannerView = BannerView(context).apply {
  placementId = "sample_placement_id"
}
container.addView(bannerView)

Braze.getInstance(context).requestBannersRefresh(listOf("sample_placement_id"))
// Using BannerUIView
let bannerView = BrazeBannerUI.BannerUIView(
  placementId: "sample_placement_id",
  braze: braze,
  processContentUpdates: { result in
    switch result {
    case .success(let updates):
      if let height = updates.height {
        // Update height constraint
      }
    case .failure:
      break
    }
  }
)
view.addSubview(bannerView)

braze.banners.requestBannersRefresh(placementIds: ["sample_placement_id"])
// Using BrazeBannerView component
<Braze.BrazeBannerView
  placementId='sample_placement_id'
/>

// Or get banner data
const banner = await Braze.getBanner("sample_placement_id");
if (banner) {
  // Render custom banner UI
}

Braze.requestBannersRefresh(["sample_placement_id"]);
// Using BrazeBannerView widget
BrazeBannerView(
  placementId: "sample_placement_id",
)

// Or get banner data
final banner = await braze.getBanner("sample_placement_id");
if (banner != null) {
  // Render custom banner UI
}

braze.requestBannersRefresh(["sample_placement_id"]);

Analytics protokollieren (benutzerdefinierte Implementierungen)

Content-Cards-Ansatz

// Manual impression logging required for custom implementations
cards.forEach(card => {
  braze.logContentCardImpressions([card]);
});

// Manual click logging required for custom implementations
card.logClick();
// Manual impression logging required for custom implementations
cards.forEach { card ->
  card.logImpression()
}

// Manual click logging required for custom implementations
card.logClick()
// Manual impression logging required for custom implementations
for card in cards {
  card.context?.logImpression()
}

// Manual click logging required for custom implementations
card.context?.logClick()
// Manual impression logging required for custom implementations
cards.forEach(card => {
  Braze.logContentCardImpression(card.id);
});

// Manual click logging required for custom implementations
Braze.logContentCardClicked(card.id);
// Manual impression logging required for custom implementations
for (final card in cards) {
  braze.logContentCardImpression(card);
}

// Manual click logging required for custom implementations
braze.logContentCardClicked(card);
// Analytics are automatically tracked when using insertBanner()
// Manual logging should not be used when using insertBanner()

// For custom implementations, use manual logging methods:
// Log impression
braze.logBannerImpressions([banner]);

// Log click (with optional buttonId)
braze.logBannerClick("sample_placement_id", buttonId);
// Analytics are automatically tracked when using BannerView
// Manual logging should not be used for default BannerView

// For custom implementations, use manual logging methods:
// Log impression
Braze.getInstance(context).logBannerImpression("sample_placement_id");

// Log click (with optional buttonId)
Braze.getInstance(context).logBannerClick("sample_placement_id", buttonId);
// Analytics are automatically tracked when using BannerUIView
// Manual logging should not be used for default BannerUIView

// For custom implementations, use manual logging methods:
// Get banner for specific placement
braze.banners.getBanner(for: "sample_placement_id") { banner in
  guard let banner = banner else { return }

  // Log impression
  banner.context?.logImpression()

  // Log click (with optional buttonId)
  banner.context?.logClick(buttonId: buttonId)
}

// Control groups are automatically handled by BannerUIView
// Analytics are automatically tracked when using BrazeBannerView
// No manual logging required

// Note: Manual logging methods for Banners are not yet supported in React Native
// Control groups are automatically handled by BrazeBannerView
// Analytics are automatically tracked when using BrazeBannerView
// No manual logging required

// Note: Manual logging methods for Banners are not yet supported in Flutter
// Control groups are automatically handled by BrazeBannerView

Eigenschaften abrufen

Content-Cards-Ansatz

cards.forEach(card => {
  console.log("Card id:", card.id, "Extras:", card.extras);
});
cards.forEach { card ->
  Log.d(TAG, "Card id: ${card.id} Extras: ${card.extras}")
}
for card in cards {
  print("Card id: \(card.id) Extras: \(card.extras)")
}
cards.forEach(card => {
  console.log("Card id:", card.id, "Extras:", card.extras);
});
for (final card in cards) {
  print("Card id: ${card.id} Extras: ${card.extras}");
}
const banner = braze.getBanner("sample_placement_id");
if (!banner) {
  return;
}

console.log("Banner placement:", banner.placementId, "Properties:", banner.properties);
val banner = Braze.getInstance(context).getBanner("sample_placement_id")
if (banner != null) {
  Log.d(TAG, "Banner placement: ${banner.placementId} Properties: ${banner.properties}")
}
braze.banners.getBanner(for: "sample_placement_id") { banner in
  guard let banner = banner else { return }

  print("Banner placement: \(banner.placementId) Properties: \(banner.properties)")
}
const banner = await Braze.getBanner("sample_placement_id");
if (banner) {
  console.log("Banner placement:", banner.placementId, "Properties:", banner.properties);
}
final banner = await braze.getBanner("sample_placement_id");
if (banner != null) {
  print("Banner placement: ${banner.placementId} Properties: ${banner.properties}");
}

Kontrollgruppen behandeln

Content-Cards-Ansatz

cards.forEach(card => {
  if (card.isControl) {
    // Logic for control cards ie. don't display but log analytics
  } else {
    // Logic for cards ie. render card
  }
});
cards.forEach { card ->
  if (card.isControl) {
    // Logic for control cards ie. don't display but log analytics
  } else {
    // Logic for cards ie. render card
  }
}
for card in cards {
  if card.isControl {
    // Logic for control cards ie. don't display but log analytics
  } else {
    // Logic for cards ie. render card
  }
}
cards.forEach(card => {
  if (card.isControl) {
    // Logic for control cards ie. don't display but log analytics
  } else {
    // Logic for cards ie. render card
  }
});
for (final card in cards) {
  if (card.isControl) {
    // Logic for control cards ie. don't display but log analytics
  } else {
    // Logic for cards ie. render card
  }
}
// - Available in version 7.0.0+
braze.subscribeToBannersEvents((event) => {
  if (
    event.type !== braze.ChannelEventType.CACHE_REPLAY &&
    event.type !== braze.ChannelEventType.CACHE_LOAD &&
    event.type !== braze.ChannelEventType.DATA_UPDATED
  ) {
    return;
  }

  const banner = event.cacheSnapshot.banners["sample_placement_id"];
  if (!banner) {
    return;
  }

  const container = document.getElementById("global-banner-container");

  // Always call insertBanner to track impression (including control)
  braze.insertBanner(banner, container);

  // Hide if control group
  if (banner.isControl) {
    container.style.display = "none";
  }
});

braze.subscribeToBannersUpdates((banners) => {
  const banner = braze.getBanner("sample_placement_id");
  if (!banner) {
    return;
  }

  const container = document.getElementById("global-banner-container");

  // Always call insertBanner to track impression (including control)
  braze.insertBanner(banner, container);

  // Hide if control group
  if (banner.isControl) {
    container.style.display = "none";
  }
});
// BannerView automatically handles control groups
// No additional code needed
val bannerView = BannerView(context).apply {
  placementId = "sample_placement_id"
}
// BannerUIView automatically handles control groups
// No additional code needed
let bannerView = BrazeBannerUI.BannerUIView(
  placementId: "sample_placement_id",
  braze: braze
)
// BrazeBannerView automatically handles control groups
// No additional code needed
<Braze.BrazeBannerView
  placementId='sample_placement_id'
/>
// BrazeBannerView automatically handles control groups
// No additional code needed
BrazeBannerView(
  placementId: "sample_placement_id",
)

Einschränkungen

Beachten Sie bei der Migration von Content Cards zu Banner die folgenden Einschränkungen:

Migration getriggerter Nachrichten

Banner unterstützen nur Campaigns mit zeitplanbasierter Zustellung. Um eine Nachricht zu migrieren, die zuvor API-getriggert oder aktionsbasiert war, konvertieren Sie sie in segmentbasiertes Targeting:

  • Beispiel: Anstatt eine „Profil vervollständigen“-Karte über die API zu triggern, erstellen Sie ein Segment für Nutzer:innen, die sich in den letzten 7 Tagen angemeldet, aber ihr Profil noch nicht vervollständigt haben.
  • Echtzeit-Qualifizierung: Nutzer:innen qualifizieren oder disqualifizieren sich bei jeder Aktualisierung sofort für das Banner, basierend auf ihrer Segment-Zugehörigkeit.

Feature-Unterschiede

Feature Content Cards Banner
Content-Struktur    
Mehrere Karten im Feed ✅ Unterstützt ✅ Mehrere Placements können erstellt werden, um eine Karussell-ähnliche Implementierung zu erreichen. Pro Placement wird nur ein Banner zurückgegeben.
Mehrere Placements N/A ✅ Mehrere Placements unterstützt
Kartentypen (Classic, Captioned, Image Only) ✅ Mehrere vordefinierte Typen ✅ Einzelnes HTML-basiertes Banner (flexibler)
Content-Verwaltung    
Drag-and-Drop-Editor ❌ Erfordert Entwickler:in für Anpassung ✅ Marketer können ohne Entwicklerteam erstellen/aktualisieren
Benutzerdefiniertes HTML/CSS ❌ Auf Kartenstruktur beschränkt ✅ Volle HTML/CSS-Unterstützung
Schlüssel-Wert-Paare für Anpassung ✅ Erforderlich für erweiterte Anpassung ✅ Stark typisierte Schlüssel-Wert-Paare, sogenannte „Properties“, für erweiterte Anpassung
Message Extras ✅ Unterstützt ❌ Derzeit nicht unterstützt
Persistenz und Ablauf    
Kartenablauf ✅ Unterstützt (30-Tage-Limit) ✅ Unterstützt (kein Ablauflimit)
Echte Persistenz ❌ Maximal 30 Tage ✅ Unbegrenzte Persistenz
Anzeige und Targeting    
Feed-UI ✅ Standard-Feed verfügbar ❌ Nur Placement-basiert
Kontextspezifisches Placement ❌ Feed-basiert ✅ Native Placement-Unterstützung
Priorisierung ❌ Erfordert benutzerdefinierte Logik ✅ Native Priorisierung
Nutzer:innen-Interaktion    
Manuelles Ausblenden ✅ Unterstützt ✅ Unterstützt
Erneute Qualifizierung nach Ausblenden ❌ Erfordert benutzerdefinierte Filter oder Campaign-Logik ✅ Standard-Wartezeit
Gepinnte Karten ✅ Unterstützt N/A
Analytics    
Automatische Analytics (Standard-UI) ✅ Unterstützt ✅ Unterstützt
Prioritäts-Sortierung ❌ Nicht unterstützt ✅ Unterstützt
Content-Aktualisierungen    
Liquid-Templating-Aktualisierung ❌ Einmalig pro Karte beim Senden/Start ✅ Aktualisiert bei jeder Aktualisierung
Qualifizierungsaktualisierung ❌ Einmalig pro Karte beim Senden/Start ✅ Aktualisiert bei jeder Sitzung

Produkteinschränkungen

  • Bis zu 25 aktive Nachrichten pro Placement.
  • Bis zu 10 Placement-IDs pro Aktualisierungsanfrage; darüber hinausgehende Anfragen werden abgeschnitten.

SDK-Einschränkungen

  • Banner werden derzeit auf .NET MAUI (Xamarin), Cordova, Unity, Vega oder TV-Plattformen nicht unterstützt.
  • Stellen Sie sicher, dass Sie die in den Voraussetzungen aufgeführten Mindest-SDK-Versionen verwenden.
New Stuff!