Migrate on Web
Preliminary pre-release documentation, subject to change without notice. The full list of properties on each event isn't published yet.
Updated on September 25, 2026.
The unified schema ships in Web 2.0.x. Widgets and players still render. Analytics listeners have no compile step, so a mismatch keeps running and sees nothing.
This page is written to be followed by a developer or a coding agent. Read Ask before you change these before you change code.
Read first: Migrate to the unified events schema holds the mapping tables this page assumes. This page adds only what's specific to Web. The old value to new value pairs are only on that page, so don't change code from this page alone.
API reference: Web
Before you start
- Wait for the SDK version. The unified schema ships in Web 2.0.x. Don't start the code changes until you're upgrading to that line.
- Accept both schema versions. Your pipeline needs to accept
api_scheme_version"2"and"2.1"while older SDK versions in the field still emit legacy events."2"is the 0.37.x line."2.1"is 2.0.x. - Nothing is aliased. Old names aren't mapped to new ones anywhere in the SDK, so anything you miss goes silent rather than failing.
- The full list of properties on each event isn't published yet. Treat a mapping that isn't listed as unknown, not as unchanged. Don't run this as an unattended automated pass.
Ask before you change these
Don't make these changes without a person. The mapping tables say what the payload does now, not what your app should do about it. Listener remaps in Fix silent listener matches are in scope. Rows below are not. Don't map a removed event to the nearest surviving name to keep a dashboard drawing.
| Change | Why a person decides |
|---|---|
story_completed | It now means one Story finished, not a completed playlist. Code that treated it as playlist completion needs remaining_unread_count = 0, which is a logic change. |
| Removed events with no replacement | Forced play and pause, and cta_dismissed, are gone. Dropping the feature that depended on them is a product decision. search_suggestions_shown is gone. For suggestion click-through, count search_suggestions_clicked against search_opened. On Web, heartbeat is also gone. Reconstruct watch time from video_current_time and seek events. |
| Call to action metrics | cta_shown counts once per start rather than once per appearance, so thresholds and rates built on the old counting need rebasing. A color-only CTA, or one with empty text and url, reports neither cta_shown: true nor cta_config. |
| Search attribution | Search identifiers ride every player event in the session, so counting logic has to move to unique search_session_id. |
Widget containerId values | The label groups your widget metrics, so choosing it is a naming decision, not a fix. Don't generate it per page load, and don't paste a name from the class, the file, or an example. |
| Consent listeners | Every consent event arrives as event_action: "consent_action". Your original action string is in consent.action. Matching the action string as event_action matches nothing. |
banner_ad_click | The count is a floor inferred from focus, not a click callback. Don't treat it as an exact click-through numerator. |
Step 1: Find the affected code
The result of this step is a list of files to change, not a change.
- Search your app for each string in the table below.
- Search each Legacy cell in Migrate to the unified events schema: value remap, exit triggers, start triggers, renamed events, removed events, removed properties, and renamed properties.
- Search your dashboards, saved segments, and warehouse queries for the same strings. Code and reporting break independently, and reporting breaks silently.
Nothing fails at build time. A listener that still matches a legacy event_action, a capitalized event_category, or an unprefixed property key keeps running and silently sees a different value, or nothing. Review those by hand.
| Search for | Why it matters |
|---|---|
onEventTriggered, blaze-event-triggered | Handlers that read the payload |
e.detail.eventData, eventData | Property reads. Unprefixed keys such as story_id are now undefined |
event_category, event_action | Code that branches on event identity |
playback_play, playback_pause | Renamed to user_initiated_playback_play / user_initiated_playback_pause |
screen_change | Renamed to viewing_mode_transition |
share as an event_action | Video share is now share_click |
sdk_init | Same name. event_category moved from widget to global |
origin_widget_id, origin_placement_id, origin_placement_name, labels_expression, story_source | Referring fields that are gone. Join on origin_source_id[0] |
JSON.parse on payload fields | content_extra_info, story_page_content_extra_info, widget_content_list, and follow_state are no longer JSON strings |
screen_state, current_state, next_state | Replaced by a single viewing_mode string |
forced_playback_play, forced_playback_pause | Removed, no replacement. Report the call site. See Ask before you change these. |
cta_visible, cta_dismissed | Removed. cta_dismissed has no replacement. See Ask before. |
audio, share_success, heartbeat | Removed events |
moments_playlist_start, moments_playlist_exit | Removed events |
expand, minimize | Removed. Use viewing_mode_transition |
search_suggestions_shown | Removed. For suggestion click-through, count search_suggestions_clicked against search_opened. See Ask before. |
pushConsentEvent | Wire shape changed. See Ask before. |
WidgetRowView, WidgetGridView, WidgetEmbeddedStory, WidgetEmbeddedVideo | The first argument is widget_id. See Required widget id. |
| Title Case string literals compared against property values | Search the Legacy column of the shared value remap. Don't recase and assume a match. |
Step 2: Fix silent listener matches
There are no deprecation shims and no compile errors. If a search hit matches Ask before you change these, stop and report it.
| Was | Now |
|---|---|
Unprefixed keys on eventData, such as story_id | Plan names including the namespace: story.story_id, tech.device_type, moment.moment_exit_trigger, story.cta_config.text. A read of the unprefixed key is undefined. |
api_scheme_version "2" only | Accept "2" and "2.1" |
event_action === "playback_play" / "playback_pause" | user_initiated_playback_play / user_initiated_playback_pause |
event_action === "screen_change" | viewing_mode_transition |
event_action === "share" on Video | share_click |
sdk_init filtered by category widget or Widget | Category is global |
origin_widget_id, origin_placement_id, origin_placement_name, labels_expression, story_source, referrer_page_type, referrer_page_domain, session_referrer_* | referring.origin_source_id and origin_source_type are index-aligned lists. On Web the chain is one element. Join on origin_source_id[0], and filter origin_source_type[0] when you need the surface kind (widget, embedded, entry_point, inline, moments_tab). Label expressions and content lists moved to data_source_type / data_source_value / data_source_config. |
JSON.parse on content_extra_info, story_page_content_extra_info, widget_content_list, or follow_state | Drop the parse. Extra-info fields are objects. widget_content_list is an ordered array of content IDs. follow_state is an object. |
placement_distance, ad_duration, ad_duration_viewed_percent, skip_time_offset, ad_index, loop_number as strings | Numbers. skippable is a boolean. |
screen_state.current_state / screen_state.next_state | viewing_mode, a single string. Web emits embedded or fullscreen. On viewing_mode_transition it is the destination mode. |
Custom consent action names as event_action | event_action is consent_action. Branch on consent.action. Your payload is in consent.body. |
tech.os / tech.os_version | tech.operating_system / tech.operating_system_version, lowercase values |
user.ab_test_id / user.ab_test_variant | user.experiments, keyed by experiment ID |
interaction content_title | content_name |
Events that used to arrive empty now carry the plan payload when a value exists, including moment.like, moment.unlike, ad_requested, ad_click, the banner events, sdk_init, interaction_view, and interaction_answer. Treat an absent field as not on that event, not as null.
Step 3: Required widget id
The first argument to WidgetRowView, WidgetGridView, WidgetEmbeddedStory, and WidgetEmbeddedVideo is the widget's analytics id. It must match the DOM container's id.
A widget without a stable, host-chosen id still renders and still reports. It reports widget_id: "not_provided" after one console error, so its analytics can't be aggregated across sessions. On the 0.37.x line, a missing id suppressed widget_load entirely.
<div id="<placement-label>" style="width: 100%; height: 500px;"></div>BlazeSDK.WidgetRowView('<placement-label>', options);Don't invent <placement-label>. Report each call site. The string is a naming decision, not a code fix. Don't paste a name from the class, the file, or this page. See Ask before you change these.
Pass a stable label for the placement, not for the page load. Keep it identical across reloads and app versions, and use a different one for each placement, because widget metrics are grouped by this value.
Step 4: Web data changes
These changes are specific to Web. The schema is the same on every platform, and what differs here is how the DOM channel delivers it, and where Web reporting becomes more complete or more precise than it was. Nothing errors, so a report built on the previous behavior keeps running on the new numbers.
Fields and values that report differently
| What changed | What it means for your numbers |
|---|---|
viewing_mode | Web emits embedded or fullscreen. There is no inline_preview or inline_interactive on Web. |
tech.device_type | Includes desktop, alongside phone, tablet, and unknown. |
tech.connection_type | wifi, cellular, wired, or unknown. On most browsers it is unknown. Don't parse it as a speed class. The earlier speed values were never valid. |
referring.origin_source_id | A one-element list on Web, or "not_provided" when the opener has no id. Embedded-hosted player events now carry the embedded element's id. They previously reported "not_provided". |
story_page_type | The CMS page type (content, ad, intro, and so on). It was a homepage or article URL guess. It rides page-level events only. story_start and story_exit don't carry it. |
moment_index and other playlist indexes | Content only. Ads are excluded, so values shift. |
| Live timeline fields | While upcoming: video_duration, video_current_time, and video_time_end are absent. While live: duration is 0. An ended stream also masks the timeline for Viewers who never watched it live in the session. |
video_time_start / video_time_end | Window semantics aren't finalized. Treat a mapping here as unknown, not as a contract. |
next_video_id | Only on video_end. |
quality | Web only. Populated when HLS reports a level. |
widget_fold_position | Web only: above_fold or below_fold, measured at initial load, on load and visible events. |
advertiser_id / campaign_id | GAM line-item IDs on custom-native ads, from ad_view onward. ad_requested can't carry them. Names stay absent: GAM's web API doesn't expose them in the browser. |
ima_ad_tapped | Declared in the tracking plan. Not emitted on Web. The HTML5 IMA SDK reports CLICK only. |
Web-only events
| Event | What to do |
|---|---|
heartbeat | Removed. It was already sending nothing. Reconstruct watch time from video_current_time and seek events. The config keys videoHeartbeatEnabled and videoHeartbeatIntervalSeconds are ignored. |
expand / minimize | Removed. Use viewing_mode_transition. |
share_success | Removed. share_click is the intent. share_select is the destination, with share_destination. |
share_select | Fires when the share modal resolves to a destination. Moment and Video. |
embedded_data_load | Fires once per embedded element per session. A refetch or retry stays silent. |
ad_exit | Fires on every exit path out of a custom-native ad, with ad_exit_trigger. |
ad.playback_initial_start | Fires once per ad, when the creative first renders. |
moment.seek | Fires once per scrub burst. |
banner_ad_click | Fires. Treat the count as a floor. See Ask before. |
consent_action | Consent events that didn't arrive will start arriving. Match consent_action, then consent.action. |
Behaviors that change event volume
| Behavior | What it means for your numbers |
|---|---|
widget_visible dedups once per (widget_id, session) | Repeated viewport enter and exit no longer re-fire. Expect widget_visible counts to drop. |
| Focus loss and regain are paired | Leaving the tab emits an exit with app_background. Returning emits a start with app_foreground. A CTA that covers the player exits with focus_lost and resumes with focus_regained. A CTA that navigates the same tab unloads the page, so that focus_lost has no matching start. |
| Closing the tab | Desktop tab or browser close reports page_unload on the exit. Moments previously emitted nothing on tab close, so expect moment_exit to rise. |
| Seeks are burst-batched | One video.seek or moment.seek per burst, not one per tap. Net-zero scrubs are silent. |
| Share | share_click fires when a dialog opens, not on the button press. Presses that never opened a dialog are no longer counted. |
cc_on / cc_off | Only a real off/on change reports. Re-selecting the active language, or changing language while captions stay on, reports nothing. |
| Story and Moment events on ad pages | Suppressed. The same activity is ad.* events. |
| Prev/next on Video | Closes the outgoing session with video_end (user_skip_next or user_skip_previous). The incoming item starts with video_start_trigger: skip. Session-completion math against 0.37.x isn't comparable. |
| Non-content Story pages | Intro, outro, end-card, and uploaded-content pages now report, with their own story_page_type. Expect story_page_start / story_page_exit to rise. |
| IMA lifecycle events | ima_ad_started, paused, resumed, skipped, and the quartile events read the documented IMA getter surface. Expect these counts to appear or rise. |
back_to_live | Fires when playback catches the live edge from a Viewer action (back_to_live_button or manual_scrub). Automatic catch-up after returning to the tab no longer emits. |
remaining_unread_count / remaining_unread_pages | Forward-only. Playlist end is remaining_unread_count: 0 on the exit. |
Step 5: Verify
- Confirm
api_scheme_versionreads2.1in the payloads your app receives. - Confirm
event_categoryvalues arrive lowercase, and that your handler coverstabandglobal. - Confirm no handler still matches a legacy value or an unprefixed property key. A handler that never fires is the failure mode here, so assert on a match rather than on the absence of errors.
- Confirm each widget
containerIdis the stable placement label you intended, and that it's identical across two page loads. - Confirm the properties you depend on are present on the events you read them from. A missing property means it doesn't apply to that event, not that it's null.
- Confirm consent listeners match
consent_actionand then branch onconsent.action.
Related
- Migrate to the unified events schema: the payload reference for every platform
- Migrate on iOS: the same job on iOS
- Migrate on Android: the same job on Android
- Reporting changes to expect: metrics that move without anything failing
- 2026 events schema - breaking changes ahead: timeline
Updated 5 days ago
