Content Cardsからバナーへ移行する
このガイドは、バナー形式のメッセージングユースケースにおいて、Content Cardsからバナーへの移行を支援するものです。バナーは、アプリケーション内の特定の配置に表示される、インラインで持続的なアプリ内メッセージおよびWebメッセージに最適です。
Banners に移行する理由
- エンジニアリングチームがカスタムContent Cardsを構築または保守している場合、Banners に移行することでその継続的な投資を削減できます。Banners を使用すると、マーケターがUIを直接制御できるため、開発者は他の作業に集中できます。
- 新しいホームページメッセージ、オンボーディングフロー、または永続的なお知らせを展開する場合は、Content Cardsで構築するのではなく、Banners から始めましょう。リアルタイムのパーソナライゼーション、30日間の有効期限なし、サイズ制限なし、ネイティブの優先順位付けを初日から活用できます。
- 30日間の有効期限の制限を回避したり、複雑な再適格性ロジックを管理したり、古いパーソナライゼーションに不満を感じている場合、Banners はこれらの問題をネイティブに解決します。
Banners は、バナースタイルのメッセージングにおいて、Content Cardsに比べていくつかの利点を提供します。
制作の迅速化
- 継続的なエンジニアリングサポートの削減: マーケターは、カスタマイズに開発者の支援を必要とせずに、ドラッグ&ドロップエディターやカスタムHTMLを使用してカスタムメッセージを作成できます
- 柔軟なカスタマイズオプション: エディターで直接デザインしたり、HTMLを使用したり、カスタムプロパティで既存のデータモデルを活用したりできます
より優れたUX
- 動的なコンテンツ更新: Banners は更新のたびにLiquidロジックと適格性を再評価し、ユーザーが常に最も関連性の高いコンテンツを表示できるようにします
- ネイティブの配置サポート: メッセージはフィードではなく、特定のコンテキストに表示されるため、より適切な文脈的関連性を提供します
- ネイティブの優先順位付け: カスタムロジックなしで表示順序を制御でき、メッセージの階層管理が容易になります
永続性
- 有効期限の制限なし: Banners キャンペーンにはContent Cardsのような30日間の有効期限の制限がなく、メッセージの真の永続性を実現できます
移行のタイミング
次のような用途でContent Cardsを使用している場合は、バナーへの移行を検討してください。
- ホームページのヒーロー、商品ページのプロモーション、チェックアウトオファー
- 永続的なナビゲーションアナウンスやサイドバーメッセージ
- 30日以上にわたって配信される常時オンメッセージ
- リアルタイムのパーソナライゼーションやエリジビリティを活用したいメッセージ
Content Cardsを引き続き使用すべきケース
以下のような要件がある場合は、引き続きContent Cardsを使用してください。
- フィード体験: 複数のスクロール可能なメッセージやカード型の「受信トレイ」を含むユースケース。
- 特定の機能: プロモーションコードを必要とするメッセージ。バナーはこれらをネイティブにサポートしていません。バナーは早期アクセスでConnected Contentをサポートしています。
- トリガー配信: APIトリガーまたはアクションベースの配信を厳密に必要とするユースケース。バナーはAPIトリガーやアクションベースの配信をサポートしていませんが、リアルタイムの適格性評価により、ユーザーは更新のたびにセグメントメンバーシップに基づいて即座に適格または不適格と判定されます。
移行ガイド
一部のサンプルでは、2つのサブスクリプションパターンが示されています。Available in version と記載されたパターンは、Web SDK 7.0.0、Android SDK 44.0.0、または Swift SDK 19.0.0以降で使用してください。もう一方のパターン subscribeTo…Updates(Swiftでは subscribeToUpdates)は、これらのバージョンをもって非推奨となっています。詳細については、更新サブスクリプションからの移行を参照してください。
前提条件
移行する前に、Braze SDKが最小バージョン要件を満たしていることを確認してください。
却下と再適格性には、以下の最小SDKバージョンが必要です。
更新とイベントをサブスクライブする
Content Cardsアプローチ
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}");
}
});
バナーアプローチ
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 Cardsはカスタム UI ロジックで手動レンダリングできますが、バナーはSDKの組み込みメソッドでのみレンダリングできます。
Content Cardsアプローチ
// 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"]);
分析をログに記録する(カスタム実装)

Content Cardsとバナーはどちらも、デフォルトの UI コンポーネントを使用している場合は分析を自動的に追跡します。以下の例は、独自の UI を構築するカスタム実装向けです。
Content Cardsアプローチ
// 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);
バナーアプローチ

insertBanner() を使用する場合、分析は自動的に追跡されます。insertBanner() を使用している場合は、手動ログを使用しないでください。
// 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);

BannerViewを使用する場合、分析は自動的に追跡されます。BannerViewを使用している場合は、手動ログを使用しないでください。
// 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);

BannerUIViewを使用する場合、分析は自動的に追跡されます。デフォルトのBannerUIViewを使用している場合は、手動ログを使用しないでください。
// 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

BrazeBannerViewを使用する場合、分析は自動的に追跡されます。手動ログは不要です。
// 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

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
プロパティを取得する
Content Cardsアプローチ
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}");
}
コントロールグループを処理する
Content Cardsアプローチ
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",
)
制限事項
Content Cardsからバナーへ移行する際には、以下の制限事項に注意してください。
トリガーメッセージの移行
バナーはスケジュール配信キャンペーンのみをサポートしています。以前APIトリガーまたはアクションベースだったメッセージを移行するには、セグメントベースのターゲティングに変換してください。
- 例: APIで「プロフィール完成」カードをトリガーする代わりに、過去7日以内にサインアップしたがプロフィールを完成していないユーザーのセグメントを作成します。
- リアルタイム適格性: ユーザーは、更新のたびにセグメントメンバーシップに基づいて即座にバナーの適格・不適格が判定されます。
機能の違い
| 機能 | Content Cards | バナー |
|---|---|---|
| コンテンツ構造 | ||
| フィード内の複数カード | ✅ サポートあり | ✅ 複数のプレースメントを作成してカルーセルのような実装が可能。プレースメントごとに返されるバナーは1つのみ。 |
| 複数プレースメント | N/A | ✅ 複数プレースメントをサポート |
| カードタイプ(クラシック、キャプション付き、画像のみ) | ✅ 複数の定義済みタイプ | ✅ 単一のHTMLベースのバナー(より柔軟) |
| コンテンツ管理 | ||
| ドラッグ&ドロップエディター | ❌ カスタマイズには開発者が必要 | ✅ マーケターが開発なしで作成・更新可能 |
| カスタムHTML/CSS | ❌ カード構造に限定 | ✅ 完全なHTML/CSSサポート |
| カスタマイズ用のキーと値のペア | ✅ 高度なカスタマイズに必須 | ✅ 「プロパティ」と呼ばれる強い型付けのキーと値のペアで高度なカスタマイズが可能 |
| メッセージエクストラ | ✅ サポートあり | ❌ 現在サポートされていません |
| 永続性と有効期限 | ||
| カードの有効期限 | ✅ サポートあり(30日制限) | ✅ サポートあり(有効期限制限なし) |
| 真の永続性 | ❌ 最大30日 | ✅ 無制限の永続性 |
| 表示とターゲティング | ||
| フィードUI | ✅ デフォルトフィードが利用可能 | ❌ プレースメントベースのみ |
| コンテキスト固有のプレースメント | ❌ フィードベース | ✅ ネイティブプレースメントサポート |
| 優先順位設定 | ❌ カスタムロジックが必要 | ✅ ネイティブ優先順位設定 |
| ユーザーインタラクション | ||
| 手動での非表示 | ✅ サポートあり | ✅ サポートあり |
| 非表示後の再適格性 | ❌ カスタムフィルターまたはキャンペーンロジックが必要 | ✅ デフォルトの待機期間 |
| ピン留めカード | ✅ サポートあり | N/A |
| 分析 | ||
| 自動分析(デフォルトUI) | ✅ サポートあり | ✅ サポートあり |
| 優先度ソート | ❌ サポートなし | ✅ サポートあり |
| コンテンツ更新 | ||
| Liquidテンプレートの更新 | ❌ 送信/起動時にカードごとに1回 | ✅ 更新のたびにリフレッシュ |
| 適格性の更新 | ❌ 送信/起動時にカードごとに1回 | ✅ セッションごとにリフレッシュ |
製品の制限事項
- プレースメントあたり最大25件のアクティブメッセージ。
- 更新リクエストあたり最大10件のプレースメントID。これを超えるリクエストは切り捨てられます。
SDKの制限事項
- バナーは現在、.NET MAUI(Xamarin)、Cordova、Unity、Vega、またはTVプラットフォームではサポートされていません。
- 前提条件に記載されている最小SDKバージョンを使用していることを確認してください。