This guide explains how to create Brizy templates that contain custom placeholders and the process to replace those placeholders dynamically via API. This only applies to reseller partners that are implementing Brizy APIs.
Overview
The Partner Integration flow allows external platforms (Partner X) to:
- Create templates with placeholders in a Brizy Workspace
- Let customers select templates through Partner X's interface
- Clone the Brizy project via API
- Replace placeholders with customer-specific data
- Deliver a fully customized Brizy page
Workflow
Partner X Interface → Select Template → Clone via API → Replace Placeholders → Customized Page
Step 1: Template Creation in Brizy
Partners create templates in their Brizy Workspace with placeholders:
Project Structure:
- Workspace contains multiple projects
- Each project contains multiple pages
- Each page contains placeholders for dynamic content
Step 2: Adding Placeholders
Partners add three types of placeholders:
Text Placeholders
Add placeholders inside Text elements using double square brackets:
[[ placeholder_restaurant_name ]]
[[ placeholder_restaurant_description ]]
[[ placeholder_address ]]
Upload images with specific naming conventions:
placeholder-logo.png
placeholder-featured.png
placeholder-hero-image.png
For Icon/Button components:
- Select the Icon or Button element
- Go to toolbar settings → Link → Custom
- Add placeholder:
[[ link_placeholder_facebook ]]
[[ link_placeholder_instagram ]]
[[ link_placeholder_website ]]
In Partner X Interface:
-
Fetch available templates using Brizy API:
// Get all templates from Brizy const templates = await fetch('https://admin.brizy.io/api/2.0/projects?workspace=${Workspace ID}', { headers: { 'x-auth-user-token': `${BRIZY_API_TOKEN}` } });
-
Customer selects a template
-
Clone the project:
const response = await fetch('https://admin.brizy.io/api/2.0/projects/{project}/duplicates', { method: 'POST', headers: { 'x-auth-user-token': `${BRIZY_API_TOKEN}`, }, body: JSON.stringify({ project: selectedTemplateId, workspaceId: "workspace id" }) }); const { id: newProjectId } = await response.json();
After cloning, get all pages from the cloned project and replace placeholders:
// Get all pages from cloned project
const pagesResponse = await fetch(
`https://admin.brizy.io/api/2.0/projects/${newProjectId}/pages`,
{
headers: {
'x-auth-user-token': `${BRIZY_API_TOKEN}`,
}
}
);
const pages = await pagesResponse.json();
// Process each page
for (const page of pages) {
const updatedPageData = await replacePlaceholders(
page.page_data,
customerData
);
// Update page with new data
await fetch(`https://admin.brizy.io/api/2.0/projects/${newProjectId}/pages/${page.id}/updates`, {
method: 'POST',
headers: {
'x-auth-user-token': `${BRIZY_API_TOKEN}`,
},
body: JSON.stringify({
page_data: updatedPageData
})
});
}
Simple string replacement:
function replaceTextPlaceholders(pageData: string, replacements: Record<string, string>): string {
let result = pageData;
for (const [placeholder, value] of Object.entries(replacements)) {
// Replace all occurrences
result = result.replace(
new RegExp(`\\[\\[\\s*${placeholder}\\s*\\]\\]`, 'g'),
value
);
}
return result;
}
// Usage
let newPageData = replaceTextPlaceholders(pageData, {
'placeholder_restaurant_name': 'Joe\'s Pizza',
'placeholder_restaurant_description': 'Best pizza in town since 1995',
'placeholder_address': '123 Main St, New York, NY'
});
Images require parsing JSON and traversing the component tree:
// Helper Functions
function mapWithStructuralSharing(obj: any, cb: (obj: any) => any): any {
const patched = cb(obj);
let patch: any;
for (let key in obj) {
if (!obj.hasOwnProperty(key)) continue;
if (typeof obj[key] === "object" && obj[key] !== null) {
const r = mapWithStructuralSharing(patched[key], cb);
if (r !== obj[key]) {
patch = patch ?? {};
patch[key] = r;
}
}
}
return patch ? patchImmutable(patch, patched) : patched;
}
function patchImmutable(patch: any, obj: any): any {
if (Array.isArray(obj)) {
let ret: any;
let retCloned = false;
for (const key in patch) {
if (!patch.hasOwnProperty(key)) continue;
const keyNr = Number(key);
if (keyNr >= 0 && keyNr <= obj.length - 1) {
if (!retCloned) {
ret = obj.slice(0);
retCloned = true;
}
ret[key] = patch[key];
}
}
return ret ? ret : obj;
} else {
return { ...obj, ...patch };
}
}
const mapModels = (fn: (model: any) => any, model: any) =>
mapWithStructuralSharing(model, (obj) => {
if (
obj &&
typeof obj === "object" &&
obj.hasOwnProperty("type") &&
obj.hasOwnProperty("value")
) {
return fn(obj);
}
return obj;
});
Replace Image Placeholders:
interface ImageMapping {
placeholderFileName: string;
newImageUrl: string;
}
async function replaceImagePlaceholders(
pageData: string,
imageMappings: ImageMapping[],
brizyApiToken: string
): Promise<string> {
const parsedData = JSON.parse(pageData);
const newData = await mapModels(async (block: any) => {
const { type, value } = block;
if (type === "Image") {
const { imageFileName } = value;
// Find matching placeholder
const mapping = imageMappings.find(
m => m.placeholderFileName === imageFileName
);
if (mapping) {
// Upload new image to Brizy
const uploadedImage = await uploadImageToBrizy(
mapping.newImageUrl,
brizyApiToken
);
return {
...block,
value: {
...value,
imageSrc: uploadedImage.name,
imageFileName: uploadedImage.fileName,
imageWidth: uploadedImage.width,
imageHeight: uploadedImage.height
}
};
}
}
return block;
}, parsedData);
return JSON.stringify(newData);
}
async function uploadImageToBrizy(
imageUrl: string,
projectId: string,
apiToken: string
): Promise<{ name: string; fileName: string; width: number; height: number }> {
// Fetch the image and convert to base64
const response = await fetch(imageUrl);
const blob = await response.blob();
// Convert blob to base64
const base64 = await new Promise<string>((resolve) => {
const reader = new FileReader();
reader.onload = () => {
const result = reader.result as string;
// Remove the data URL prefix (e.g., "data:image/png;base64,")
const base64String = result.split(',')[1];
resolve(base64String);
};
reader.readAsDataURL(blob);
});
const formData = new FormData();
formData.append('attachment', base64);
formData.append('filename', blob.fileName);
const apiResponse = await fetch(`https://admin.brizy.io/api/2.0/projects/${projectId}/media`, {
method: 'POST',
headers: {
'x-auth-user-token': `${apiToken}`,
},
body: formData
});
const { name, filename, metadata } = apiResponse.json();
const { width, height } = JSON.parse(metadata);
// important to remove .ext
const uid = name.replace(/\.[^/.]+$/, "");
return { name: uid, fileName, width, height }
}
Similar to text replacement:
function replaceLinkPlaceholders(pageData: string, links: Record<string, string>): string {
let result = pageData;
for (const [placeholder, url] of Object.entries(links)) {
result = result.replace(
new RegExp(`\\[\\[\\s*${placeholder}\\s*\\]\\]`, 'g'),
url
);
}
return result;
}
// Usage
newPageData = replaceLinkPlaceholders(newPageData, {
'link_placeholder_facebook': 'https://facebook.com/joespizza',
'link_placeholder_instagram': 'https://instagram.com/joespizza',
'link_placeholder_website': 'https://joespizza.com'
});
interface CustomerData {
text: Record<string, string>;
images: ImageMapping[];
links: Record<string, string>;
}
async function integrateTemplate(
templateProjectId: string,
customerData: CustomerData,
brizyApiToken: string
): Promise<string> {
// 1. Clone project
const cloneResponse = await fetch('https://admin.brizy.io/api/2.0/projects/clone', {
method: 'POST',
headers: {
'x-auth-user-token': brizyApiToken,
},
body: JSON.stringify({ projectId: templateProjectId })
});
const { projectId: newProjectId } = await cloneResponse.json();
// 2. Get all pages
const pagesResponse = await fetch(
`https://admin.brizy.io/api/2.0/projects/${newProjectId}/pages`,
{
headers: {
'x-auth-user-token': brizyApiToken,
}
}
);
const pages = await pagesResponse.json();
// 3. Process each page
for (const page of pages) {
let pageData = page.data;
// Replace text placeholders
pageData = replaceTextPlaceholders(pageData, customerData.text);
// Replace link placeholders
pageData = replaceLinkPlaceholders(pageData, customerData.links);
// Replace image placeholders
pageData = await replaceImagePlaceholders(
pageData,
customerData.images,
brizyApiToken
);
// Update page
await fetch(`https://admin.brizy.io/api/2.0/projects/${newProjectId}/pages/${page.id}/updates`, {
method: 'POST',
headers: {
'x-auth-user-token': brizyApiToken,
},
body: JSON.stringify({ data: pageData })
});
}
return newProjectId;
}
// Usage example
const customerData: CustomerData = {
text: {
'placeholder_restaurant_name': 'Joe\'s Pizza',
'placeholder_restaurant_description': 'Best pizza in town since 1995',
'placeholder_address': '123 Main St, New York, NY'
},
images: [
{
placeholderFileName: 'placeholder-logo.png',
newImageUrl: 'https://example.com/joespizza-logo.png'
},
{
placeholderFileName: 'placeholder-featured.png',
newImageUrl: 'https://example.com/joespizza-hero.jpg'
}
],
links: {
'link_placeholder_facebook': 'https://facebook.com/joespizza',
'link_placeholder_instagram': 'https://instagram.com/joespizza',
'link_placeholder_website': 'https://joespizza.com'
}
};
const newProjectId = await integrateTemplate(
'template-project-id-123',
customerData,
'your-brizy-api-token'
);
console.log(`Created project: ${newProjectId}`);
Best Practices
Placeholder Naming Convention
Use descriptive, consistent naming:
-
Text:
placeholder_[category]_[field](e.g.,placeholder_restaurant_name) -
Images:
placeholder-[purpose].png(e.g.,placeholder-logo.png) -
Links:
link_placeholder_[platform](e.g.,link_placeholder_facebook)
1. Placeholder not replaced:
- Check spelling matches exactly (case-sensitive)
- Ensure proper spacing:
[[ placeholder ]]not[[placeholder]] - Verify the placeholder exists in the template
2. Image upload fails:
- Verify API token has upload permissions
- Check image format is supported (PNG, JPG, WebP)
- Ensure image URL is accessible
3. Page update fails:
- Validate JSON structure after modifications
- Check API token permissions
- Verify page exists in cloned project
4. Structural sharing breaks:
- Ensure all helper functions are implemented
- Check that model traversal doesn't mutate original data
- Verify
typeandvalueproperties exist
-
POST https://admin.brizy.io/api/2.0/projects/{project}/duplicates- Clone a project -
GET https://admin.brizy.io/api/2.0/projects/{project}/pages- Get all pages -
PUT https://admin.brizy.io/api/2.0/projects/{project}/pages/{page}/updates- Update page data -
POST https://admin.brizy.io/api/2.0/projects/{project}/media- Upload media files -
GET https://admin.brizy.io/api/2.0/projects- List available templates
All requests require user token authentication:
headers: {
'x-auth-user-token': `${BRIZY_API_TOKEN}`,
}