Add a Genuin placement to your website
Render a placement with Web SDK 2.0.6 so each page gets the videos its context calls for, in the layout you manage from Brand Control Center.
A placement is a content slot. Where an embed shows one feed you picked, a placement resolves its videos when the page loads, from the placement's own rules and the context your page sends. Its layout comes from a separate style, so you can change what plays and how it looks from Brand Control Center without touching your site.
Placements use the same script and the same init call as embeds. This page covers what's different; for loading the script in each framework, the full option list, events, and browser requirements, see Embed the Genuin Web SDK on your website.
How a placement differs from an embed
| Embed | Placement | |
|---|---|---|
| Identifiers | embed_id and api_key | placement_id, style_id, and api_key |
| Content | One feed you chose: brand, communities, or groups | A feed type plus communities, groups, and network brands, filtered by the style's rules and ranked per section |
| Layout | Part of the embed | Set by the style: carousel, feed, grid, or dynamic, with optional titled sections |
| Page context | Ranks the feed when the embed has no fixed community or group | Declared per placement under Implement; sent as contextual_params |
| Change later | Edit the embed | Edit the placement or swap the style; the ids stay the same |
When a container carries both a placement and an embed id, the placement wins and the embed id is ignored.
Before you begin
| Requirement | Detail |
|---|---|
| A placement and a style | Created under Grow > On Site > Placement. See Create a placement and map it to the shopper journey. |
| Placement ID and style ID | Both 24-character ids, shown on the placement's Implement tab and in its generated code. |
| API key | Your brand's browser key, the same one embeds use. |
| Script and container | The SDK script pinned to 2.0.6 and a sized container, as in Embed the Genuin Web SDK on your website. |
How the integration flows
You write the first three items. The SDK does the rest on every page load.
- A container
<div>with the classgen-sdk-classand a uniqueid. - The script tag for
https://media.begenuin.com/sdk/2.0.6/gen_sdk.min.js. - One
window.genuin.initcall with yourapi_key, and the ids either in the call or asdata-*attributes. - The loader queues the call, imports the SDK bundle, and scans the page for containers.
- For each container it reads the data attributes;
placement_idplusstyle_idselects the placement path. - It fetches your brand by
api_key, then the placement byplacement_id, and picks the style bystyle_id. - It attaches a Shadow DOM, shows a skeleton, and renders the player when the container is within 200 px of the viewport.
- It requests the feed with your
contextual_params, renders the style's layout, and sets the container height to fit.
Step 1: Copy the placement ID and style ID
- In Brand Control Center, go to Grow > On Site, click Placement, and open your placement.
- Open the style you want to render and click Configuration.
- On the right, click Implement. The tab lists the Style ID and each context signal the placement expects, with its parameter name.
- Click View Embed Code. The Web tab carries
placement_id,style_id,api_key, and acontextual_paramsblock with a placeholder for each signal you ticked. Copy the ids from there.
Step 2: Add the container and the script
<div id="gen-sdk" class="gen-sdk-class" style="width: 100%; height: 600px;"></div>
<script src="https://media.begenuin.com/sdk/2.0.6/gen_sdk.min.js"></script>Set the width you want. The style's Placement Size in Brand Control Center decides the player's own dimensions, and the SDK sets the container height to the rendered content.
Step 3: Initialize the placement
Pass both ids with the key. Setting only one of placement_id and style_id falls back to the embed path and fails.
<script>
window.genuin.init({
placement_id: "{{PLACEMENT_ID}}",
style_id: "{{STYLE_ID}}",
api_key: "<<YOUR_API_KEY>>",
});
</script>For several placements on one page, put the ids on each container and call init once with the key.
<div id="gen-sdk-1" class="gen-sdk-class" data-placement-id="{{PLACEMENT_ID_1}}"
data-style-id="{{STYLE_ID_1}}" style="width: 100%; height: 600px;"></div>
<div id="gen-sdk-2" class="gen-sdk-class" data-placement-id="{{PLACEMENT_ID_2}}"
data-style-id="{{STYLE_ID_2}}" style="width: 320px; height: 600px;"></div>
<script src="https://media.begenuin.com/sdk/2.0.6/gen_sdk.min.js"></script>
<script>
window.genuin.init({ api_key: "<<YOUR_API_KEY>>" });
</script>Step 4: Send the signals the placement expects
Each signal ticked on the Implement tab maps to a key in contextual_params. Send the ones your page knows; the SDK adds the page URL itself. On a single-page app, call update with the container id when the route changes.
window.genuin.init({
placement_id: "{{PLACEMENT_ID}}",
style_id: "{{STYLE_ID}}",
api_key: "<<YOUR_API_KEY>>",
contextual_params: {
page_context: "running shoes, trail, waterproof",
geo: { lat: 47.6062, long: -122.3321 },
user_segments: { gender: "F", min_age: 25, max_age: 34 },
},
});Each key also has a data attribute for the multi-container pattern; objects and arrays go in as JSON strings.
| Implement signal | contextual_params key | Data attribute |
|---|---|---|
| Custom Context | page_context, user_context | data-page-context, data-user-context |
| Previous Page Context | previous_page_context | data-previous-page-context |
| Location | geo with lat, long, radius_limit | data-lat and data-long, or data-geo as JSON |
| Place | place with country, state, city, zipcode | data-place as JSON |
| Time | time | data-time |
| User Segmentation | user_segments with age, min_age, max_age, segment, gender, race, interests | data-user-segments as JSON |
| User Interests | user_interests, an array of strings | data-user-interests as JSON |
| Product Detail Page URL | url; defaults to the current page URL | data-url |
| Brand ID | brands_ids, an array of numbers | data-brands-ids as JSON |
| Community & Group ID | community_ids, loop_ids (group ids), arrays of strings | data-community-ids, data-loop-ids as JSON |
| Posted By User | posted_by_user_ids, an array of strings | data-posted-by-user-ids as JSON |
What each signal does to the ranking is in Deliver contextual feeds with page, user, and location context and How contextual feeds choose videos.
Step 5: Override the style's titles and sections (Optional)
A style carries a title, a subtitle, and, for sectioned styles, a list of sections with their own titles and descriptions. The configuration option replaces those values for one container at render time. One placement can then serve a show page, a campaign page, or a category page, each with its own headings. Section descriptions also steer which videos each section gets.
window.genuin.init({
placement_id: "{{PLACEMENT_ID}}",
style_id: "{{STYLE_ID}}",
api_key: "<<YOUR_API_KEY>>",
configuration: {
style_title: "Trail running",
style_subtitle: "Picked for this week's routes",
sections: [
{ title: "Shoe reviews", description: "trail running shoe reviews and fit tests" },
{ title: "Technique", description: "downhill form, cadence, and pacing drills" },
],
},
});Sections are matched by position, so pass them in the style's order. Sections whose description in Brand Control Center starts with @ take the description you pass; the others keep their own.
Implement in a framework
Load the script as shown for embeds in Embed the Genuin Web SDK on your website; only the container changes. Each snippet below is the placement container for that framework.
Plain HTML
<div id="gen-sdk" class="gen-sdk-class"
data-placement-id="{{PLACEMENT_ID}}" data-style-id="{{STYLE_ID}}"
data-api-key="<<YOUR_API_KEY>>" data-page-context="running shoes, trail"
style="width: 100%; height: 600px;"></div>
<script src="https://media.begenuin.com/sdk/2.0.6/gen_sdk.min.js"></script>
<script>window.genuin.init({});</script>React
import { useEffect } from "react";
const SDK_URL = "https://media.begenuin.com/sdk/2.0.6/gen_sdk.min.js";
export function GenuinPlacement({ pageContext }: { pageContext: string }) {
useEffect(() => {
if (document.querySelector(`script[src="${SDK_URL}"]`)) {
window.genuin?.init({});
return;
}
const script = document.createElement("script");
script.src = SDK_URL;
script.async = true;
script.onload = () => window.genuin?.init({});
document.body.appendChild(script);
}, []);
useEffect(() => {
window.genuin?.update({ container_id: "gen-sdk", contextual_params: { page_context: pageContext } });
}, [pageContext]);
return (
<div id="gen-sdk" className="gen-sdk-class" data-placement-id="{{PLACEMENT_ID}}"
data-style-id="{{STYLE_ID}}" data-api-key="<<YOUR_API_KEY>>"
data-page-context={pageContext} style={{ width: "100%", height: 600 }} />
);
}The second effect pushes a new page_context to the running placement when the prop changes. The window.genuin type declaration from the embed page applies here too.
Next.js
"use client";
import Script from "next/script";
export function GenuinPlacement({ pageContext }: { pageContext: string }) {
return (
<>
<div id="gen-sdk" className="gen-sdk-class" data-placement-id="{{PLACEMENT_ID}}"
data-style-id="{{STYLE_ID}}" data-api-key="<<YOUR_API_KEY>>"
data-page-context={pageContext} style={{ width: "100%", height: 600 }} />
<Script
src="https://media.begenuin.com/sdk/2.0.6/gen_sdk.min.js"
strategy="afterInteractive"
onLoad={() => window.genuin?.init({})}
/>
</>
);
}Render the component from a server component and pass the page's context as a prop. On client-side navigation, onLoad doesn't fire again, so also call window.genuin?.init({}) from a useEffect.
Angular
import { AfterViewInit, Component, Input } from "@angular/core";
@Component({
selector: "genuin-placement",
standalone: true,
template: `<div id="gen-sdk" class="gen-sdk-class"
data-placement-id="{{PLACEMENT_ID}}" data-style-id="{{STYLE_ID}}"
data-api-key="<<YOUR_API_KEY>>" [attr.data-page-context]="pageContext"
style="width: 100%; height: 600px"></div>`,
})
export class GenuinPlacementComponent implements AfterViewInit {
@Input() pageContext = "";
ngAfterViewInit(): void {
const src = "https://media.begenuin.com/sdk/2.0.6/gen_sdk.min.js";
const genuin = () => (window as any).genuin;
if (document.querySelector(`script[src="${src}"]`)) { genuin()?.init({}); return; }
const script = document.createElement("script");
script.src = src;
script.async = true;
script.onload = () => genuin()?.init({});
document.body.appendChild(script);
}
}Layout styles
The placement's layout is set in Brand Control Center, not in code; the SDK renders whatever the style says.
| Layout | What visitors see | Set in Brand Control Center |
|---|---|---|
| Carousel | A horizontal row of tiles to swipe through, in the default or focus style | Placement layout, Carousel Style |
| Feed | A vertical, swipeable column of videos | Placement layout, Scroll Behavior of free scroll or paging |
| Grid | Rows and columns of tiles, up to 3 by 3, with optional auto-adjust | Placement layout, Grid layout |
| Dynamic | Sections derived from the page, each a row of videos; a single "Latest Highlight" row until the sections are ready | Placement layout; the dynamic algorithm is enabled per placement by Genuin |
Any layout can have sections: titled groups of videos, each filled by its own search. A section shows its title, subtitle, cover, thumbnail, and clip count as the style's Section Details allow.
Customize the placement
These options are specific to placements or behave differently from embeds. Everything else, including token, params, start_video_slug, targeting, and error_handler, is shared and listed in Embed the Genuin Web SDK on your website.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| placement_id | string | Yes | none | The placement to render. Data attribute: data-placement-id. Takes precedence over embed_id on the same container. |
| style_id | string | Yes | none | The style whose layout and sections to use. Data attribute: data-style-id. |
| contextual_params | object | No | none | The signals declared on the Implement tab; see Step 4. url defaults to the current page URL. |
| configuration | object | No | the style's own values | style_title, style_subtitle, and sections with title, subtitle, description, cover_url, thumbnail_url. See Step 5. |
| theme | "light" or "dark" | No | "light" | Colour theme of the player. Data attribute: data-theme. |
| Container width | CSS | No | the style's Placement Size width | Set width on the container. The style's width and height, and Auto-fit Height, size the player inside it; the SDK then sets the container height to the rendered content. |
| Font | — | — | Inter, from Google Fonts | Not configurable in 2.0.6. The player renders in a Shadow DOM, so page CSS doesn't reach it. |
| sponsorship_id | string[] | No | the placement's own sponsorship | Overrides the sponsorship attached to the placement for ad delivery. |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Console error Placement ID or Style ID is missing, and no embed ID provided | Only one of placement_id and style_id was set | Pass both, in init or as data-placement-id and data-style-id. |
| No embed details found for placement configuration. | The placement id doesn't exist, or the style id isn't one of this placement's styles | Copy both ids again from the Implement tab. |
| The embed renders instead of the placement | A data attribute is misspelt, so the SDK saw an embed_id only | Check data-placement-id and data-style-id on the container. |
| Sections show the style's titles, not yours | configuration.sections is in a different order, or the section's description in Brand Control Center doesn't start with @ | Match the style's section order; mark overridable descriptions with @. |
| Context changes have no effect | update was called without container_id | Pass the container's id. |
| A dynamic placement shows one "Latest Highlight" row | Sections for this page are still being generated, or the dynamic algorithm isn't enabled for the placement | Reload after a minute; ask Genuin to enable the algorithm for the placement. |
Limitations
- Both ids, always: A placement needs
placement_idandstyle_id; there's no default style. - Layout lives in Brand Control Center: Code can't switch a placement between carousel, feed, grid, and dynamic.
- Grid size: Up to 3 columns and 3 rows.
- Dynamic layout: Sections are generated per page URL and context and cached for 15 minutes; a new page shows the default row first.
- Section overrides by position:
configuration.sectionsis matched to the style's sections in order, and only descriptions marked@are replaced. - One brand per page: As with embeds, all placements on a page belong to one brand.
- Font and CSS: The player's styles are isolated; the font is fixed to Inter in 2.0.6.

