Technical integration - Media in the grid
The media rules configured in the dashboard can be displayed on your website or app using the Discovery Product and Category search API.
Handle media in the API response
This step requires a developer to integrate and handle the API response of the Product and Category search API. This includes four important steps:
- Pass the feature parameter in the API request to get media.
- Identify the objects containing media between the product results.
- Set the appropriate display (for static types like images) or rendering conditions (for HTML or text assets) based on the media type.
- Create styling for different assets to fit with your product grid styles.
Pass the feature parameter
The content_injection
query parameter in the API request controls the media in the grid feature. It can take boolean values of true
or false
.
If true
, all the active media rules are applied to the search request. The configured assets are returned in the API response at the specified slot positions, along with other search results.
Identify media in the response
Example response
Below is an example of the Search API response with content_injection=true
(here, the first slot contains an image media asset):
{
"response": {
"numFound": 2,
"start": 0,
"docs": [
{
"pid": "16624",
"content": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTZVJYCtcLtMv4lgo9YEUhb3HViPS9aHqjxfA&s",
"contentType": "image_url",
"docType": "CONTENT",
"metaInfo": ""
},
{
"pid": "91800",
"description": "The Kennedy Modular Sectional Corner Chair is an ideal choice for anyone looking to customize their living room seating arrangement.",
"thumb_image": "https://pacific-demo-data.bloomreach.cloud/home/images/gen/webp/91800_0_image.webp",
"inStock": "true",
"onSale": "false",
"variants": [
"var1"
],
"docType": "PRODUCT",
"content": "",
"contentType": "",
"metaInfo": "",
"sale_price": 799.99,
"score": 99.44386,
"brand": "Pacific Style",
"price": 799.99,
"url": "https://pacifichome.bloomreach.com/home/products/91800",
"title": "Kennedy Modular Sectional Corner Chair",
"sale_price_range": [
799.99,
799.99
],
"price_range": [
799.99,
799.99
]
}
]
},
"facet_counts": {},
"category_map": {},
"stats": {},
"did_you_mean": [],
"metadata": {}
}
In the above sample response, note the following fields in the first item of the docs
array:
docType
: This field helps distinguish between PRODUCT and CONTENT doc types in the response.contentType
: For slots containing media, this field mentions the type of asset (image_url, html, text).content
: This field contains the raw content data of the asset, which can be rendered based on your needs. It is the same value as you see through the Show API response button in the dashboard.metaInfo
: The metadata that is added along with the asset through the dashboard media rule is available in this field.
The data of the content
and metaInfo
fields is also accessible in the dashboard:

Set display and rendering conditions
You can use the media information from the API response to display and render the assets as required.
Given below is an example of the logic and components to use to display product cards along with content cards.
Rendering logic
if (item.docType === 'content') {
return renderContentContainer({
content: item.content,
type: item.contentType,
pid: key,
});
} else {
return renderProduct(item as BrProduct);
}
{docs.map((item) =>
item.docType === "content"
? <ContentCard key={item.pid} content={item} />
: <ProductCard key={item.pid} product={item} />
)}
<div v-for="item in docs" :key="item.pid">
<ProductCard v-if="item.docType === 'product'" :product="item" />
<ContentCard v-else :content="item" />
</div>
<div *ngFor="let item of docs">
<app-product-card *ngIf="item.docType === 'product'" [product]="item"></app-product-card>
<app-content-card *ngIf="item.docType === 'content'" [content]="item"></app-content-card>
</div>
export default function Home({ docs }: { docs: ApiDoc[] }) {
return (
<div className="grid grid-cols-2 gap-4">
{docs.map((item) =>
item.docType === "content"
? <ContentCard key={item.pid} content={item} />
: <ProductCard key={item.pid} product={item} />
)}
</div>
);
}
export const getServerSideProps = async () => {
const docs = [
{ docType: "product", pid: "123", title: "Running Shoes" },
{ docType: "content", pid: "banner-1", contentType: "html", content: "<h2>Big Sale!</h2>" }
];
return { props: { docs } };
};
export default function Home({ docs }) {
return (
<div className="grid grid-cols-2 gap-4">
{docs.map((item) =>
item.docType === "content"
? <ContentCard key={item.pid} content={item} />
: <ProductCard key={item.pid} product={item} />
)}
</div>
);
}
export async function getServerSideProps() {
const docs = [
{ docType: "product", pid: "123", title: "Running Shoes" },
{ docType: "content", pid: "banner-1", contentType: "image_url", content: "https://picsum.photos/200/300" }
];
return { props: { docs } };
}
const renderProduct = (product: BrProduct) => {
return (
<ProductCard
key={product.pid}
productContent={product}
productParams={{ template: 'card', hoverEffect: false }}
/>
);
};
const renderContentContainer = (content: any) => {
return (
<ProductCardCampaign
key={content.pid}
type={content.type}
content={content.content}
pid={content.pid}
/>
);
};
Example content card component with media
export const ProductCardCampaign = (props: any) => {
const renderCampaignContent = (type: string) => {
switch (type) {
case 'html':
return <div dangerouslySetInnerHTML={{ __html: props.content }} />;
case 'image_url': {
let imageSrc = props.content;
if (props.content?.trim().startsWith('<img')) {
const match = props.content.match(/src=["']([^"']+)["']/i);
if (match?.[1]) imageSrc = match[1];
}
return (
<img
src={imageSrc}
alt={props.alt || ''}
style={{ width: '200px', maxHeight: '355px', objectFit: 'contain' }}
/>
);
}
default:
return <div>{props.content}</div>;
}
};
return (
<StyledBazaarCard hoverEffect={false} data-type="product-card">
<FlexBox flexDirection="column" sx={{ justifyContent: 'center', textAlign: 'center' }}>
<FlexBox sx={{ height: '355px', overflow: 'hidden' }} justifyContent="center" alignItems="center">
{renderCampaignContent(props.type)}
</FlexBox>
</FlexBox>
</StyledBazaarCard>
);
};
import React from "react";
interface ProductCardCampaignProps {
type: string;
content: string;
alt?: string;
}
export const ProductCardCampaign: React.FC<ProductCardCampaignProps> = ({ type, content, alt }) => {
const renderCampaignContent = (type: string) => {
switch (type) {
case "html":
return <div dangerouslySetInnerHTML={{ __html: content }} />;
case "image_url": {
let imageSrc = content;
if (content?.trim().startsWith("<img")) {
const match = content.match(/src=["']([^"']+)["']/i);
if (match?.[1]) imageSrc = match[1];
}
return <img src={imageSrc} alt={alt || ""} style={{ width: "200px", maxHeight: "355px", objectFit: "contain" }} />;
}
default:
return <div>{content}</div>;
}
};
return (
<div className="bazaar-card" data-type="product-card">
<div style={{ display: "flex", flexDirection: "column", textAlign: "center" }}>
<div style={{ height: "355px", overflow: "hidden", display: "flex", justifyContent: "center", alignItems: "center" }}>
{renderCampaignContent(type)}
</div>
</div>
</div>
);
};
import React from "react";
export const ProductCardCampaign = (props) => {
const renderCampaignContent = (type) => {
switch (type) {
case "html":
return <div dangerouslySetInnerHTML={{ __html: props.content }} />;
case "image_url": {
let imageSrc = props.content;
if (props.content?.trim().startsWith("<img")) {
const match = props.content.match(/src=["']([^"']+)["']/i);
if (match?.[1]) imageSrc = match[1];
}
return (
<img
src={imageSrc}
alt={props.alt || ""}
style={{ width: "200px", maxHeight: "355px", objectFit: "contain" }}
/>
);
}
default:
return <div>{props.content}</div>;
}
};
return (
<div className="bazaar-card" data-type="product-card">
<div style={{ display: "flex", flexDirection: "column", textAlign: "center" }}>
<div style={{ height: "355px", overflow: "hidden", display: "flex", justifyContent: "center", alignItems: "center" }}>
{renderCampaignContent(props.type)}
</div>
</div>
</div>
);
};
<script setup lang="ts">
import { defineProps } from "vue";
const props = defineProps<{
type: string;
content: string;
alt?: string;
}>();
const renderCampaignContent = () => {
switch (props.type) {
case "html":
return props.content; // v-html will render safely
case "image_url": {
let imageSrc = props.content;
if (props.content?.trim().startsWith("<img")) {
const match = props.content.match(/src=["']([^"']+)["']/i);
if (match?.[1]) imageSrc = match[1];
}
return `<img src="${imageSrc}" alt="${props.alt || ""}" style="width:200px;max-height:355px;object-fit:contain"/>`;
}
default:
return props.content;
}
};
</script>
<template>
<div class="bazaar-card" data-type="product-card">
<div style="display:flex;flex-direction:column;text-align:center">
<div style="height:355px;overflow:hidden;display:flex;justify-content:center;align-items:center" v-html="renderCampaignContent()" />
</div>
</div>
</template>
// product-card-campaign.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-product-card-campaign',
templateUrl: './product-card-campaign.component.html',
styleUrls: ['./product-card-campaign.component.css']
})
export class ProductCardCampaignComponent {
@Input() type!: string;
@Input() content!: string;
@Input() alt?: string;
getContent() {
switch (this.type) {
case 'html':
return this.content;
case 'image_url': {
let imageSrc = this.content;
const match = this.content?.match(/src=["']([^"']+)["']/i);
if (match?.[1]) imageSrc = match[1];
return `<img src="${imageSrc}" alt="${this.alt || ''}" style="width:200px;max-height:355px;object-fit:contain"/>`;
}
default:
return this.content;
}
}
}
// product-card-campaign.component.html
<div class="bazaar-card" data-type="product-card">
<div style="display:flex;flex-direction:column;text-align:center">
<div style="height:355px;overflow:hidden;display:flex;justify-content:center;align-items:center"
[innerHTML]="getContent()">
</div>
</div>
</div>
// app/components/ProductCardCampaign.tsx
"use client";
import React from "react";
interface Props {
type: string;
content: string;
alt?: string;
}
export default function ProductCardCampaign({ type, content, alt }: Props) {
const renderCampaignContent = (type: string) => {
switch (type) {
case "html":
return <div dangerouslySetInnerHTML={{ __html: content }} />;
case "image_url": {
let imageSrc = content;
if (content?.trim().startsWith("<img")) {
const match = content.match(/src=["']([^"']+)["']/i);
if (match?.[1]) imageSrc = match[1];
}
return <img src={imageSrc} alt={alt || ""} style={{ width: "200px", maxHeight: "355px", objectFit: "contain" }} />;
}
default:
return <div>{content}</div>;
}
};
return (
<div className="bazaar-card" data-type="product-card">
<div style={{ display: "flex", flexDirection: "column", textAlign: "center" }}>
<div style={{ height: "355px", overflow: "hidden", display: "flex", justifyContent: "center", alignItems: "center" }}>
{renderCampaignContent(type)}
</div>
</div>
</div>
);
}
// Usage inside a Next.js page:
import ProductCardCampaign from "@/components/ProductCardCampaign";
export default function Page() {
const mockContent = { type: "image_url", content: "https://via.placeholder.com/200" };
return <ProductCardCampaign {...mockContent} />;
}
Updated about 9 hours ago