Navigation

Integrations

Embed the editor in your app

Learn how to put the Bannerify editor in your product, so your users design with your templates.

Use the embed when your users need to make banners, posts, labels or documents inside your product. Your page shows the Bannerify editor in an iframe. Each of your users gets their own designs, and can start from the templates of your workspace.

How it works

  1. Your server signs a short token for the user with the signing secret of the embed.
  2. Your page opens https://embed.bannerify.co/<embed id>?token=<token> in an iframe.
  3. Bannerify checks the token and the domain of the page, then opens the editor for that user.
  4. The designs of the user are templates of your workspace. You render them with the API, like any other template.

Prerequisites

  • A workspace on the Scale or Enterprise plan.
  • A server that can sign a JWT. The signing secret must stay on your server.
Scale Enterprise
Allowed domains 3 8
Editor users 100 500
Downloads, permissions, name, logo and accent color Yes Yes
SDK events and commands Yes Yes
Webhooks Yes Yes
Hide the Bannerify branding No Yes
Asset picker from your app No Yes
Analytics No Yes

Set up the embed

  1. Open Embed in the dashboard sidebar.
  2. Click Set up the embed, then turn on Turn on the embed.
  3. Under Allowed domains, add the origin of each page that shows the editor, such as https://app.example.com. Use https://*.example.com for every subdomain, and http://localhost:3000 while you develop.
  4. Copy the Embed id and the Signing secret.

Sign a token on your server

The token is an HS256 JWT. Set sub to the id of the user in your app. The same sub always opens the same designs.

import { SignJWT } from "jose"

const token = await new SignJWT({ sub: user.id, name: user.name, email: user.email })
  .setProtectedHeader({ alg: "HS256" })
  .setExpirationTime("1h")
  .sign(new TextEncoder().encode(process.env.BANNERIFY_EMBED_SECRET))
Claim Required Description
sub No The user id in your app. Letters, numbers, _, -, @ and ., up to 256 characters. Without it, the editor edits the templates of your workspace.
name No The name of the user.
email No The email of the user.
exp Yes When the token expires. At most 24 hours from now.

Show the editor

<iframe
  src="https://embed.bannerify.co/emb_xxx?token=TOKEN"
  style="width: 100%; height: 800px; border: 0"
  allow="clipboard-write"
></iframe>

Add &templateId=tpl_xxx to the URL to open one design directly.

Use the SDK

The SDK adds the iframe for you, tells your page what the user does, and lets your page control the editor.

npm install @bannerify/embed
import { createBannerifyEmbed } from "@bannerify/embed"

const embed = createBannerifyEmbed({
  container: document.getElementById("editor"),
  embedId: "emb_xxx",
  token, // signed by your server
})

embed.on("template:update", ({ template }) => {
  console.log("Saved", template.id)
})

In React, install @bannerify/embed-react and use the component. The ref controls the editor.

npm install @bannerify/embed-react
import { BannerifyEmbed, type BannerifyEmbedHandle } from "@bannerify/embed-react"

const editor = useRef<BannerifyEmbedHandle>(null)

<BannerifyEmbed
  ref={editor}
  embedId="emb_xxx"
  token={token}
  style={{ height: 800 }}
  onDownload={({ fileName, content }) => upload(fileName, content)}
/>

Events

Event React prop Data
ready onReady { user }. The editor accepts commands from now on.
template:open onTemplateOpen { template, canEdit }. canEdit is false for a template of your workspace.
template:close onTemplateClose { templateId }
template:create onTemplateCreate { template }, for a new design or a copy.
template:update onTemplateUpdate { template }, after a save or a rename.
template:delete onTemplateDelete { templateId }
download onDownload { templateId, format, fileName, content }. content is a data URL of the file.
error onError { message }, when the editor could not open.
asset:request onAssetRequest { requestId, type }. See Asset picker.

A template has id, name, width, height and updatedAt.

Commands

Each command waits for ready and returns a promise.

Command Result
openTemplate(templateId) { template }. Opens a design of the user or a template of your workspace.
closeTemplate() Goes back to the list of designs.
setModifications([{ name, text, color, src, qrcode, barcode, visible }]) { changed }. Changes the layers of the open design by their names, like the modifications of the API.
save() { template }. Saves the open design.
requestContent(format) { templateId, content }. Renders the open design as png, jpeg, webp or pdf.

A template of your workspace is read-only, so setModifications and save fail with “Make a copy of this template to change it”.

Each requestContent is one generation of your workspace, like a download in the editor or a render with the API. When the workspace used all its generations, a download shows the error in the editor and requestContent fails with it.

Asset picker

On the Enterprise plan, your page can show its own library when a user adds an image or picks a color. Turn on Images from your app or Colors from your app under Asset picker. The Media panel then shows Choose from with the name of your app, and the color picker gets a swatch button. Users can still upload their own files.

const embed = createBannerifyEmbed({
  container,
  embedId: "emb_xxx",
  token,
  onAssetRequest: async ({ type }) => {
    if (type === "image") {
      const photo = await openMyMediaLibrary() // your own UI
      return photo ? { url: photo.url, fileName: photo.name } : null
    }
    const hex = await openMyBrandColors()
    return hex ? { value: hex } : null
  },
})

Return null when the user closes your picker. Bannerify downloads the image from the https URL and copies it into the uploads of the user, so the URL can expire after that. An image is PNG, JPEG, WebP or SVG, up to 5 MB. In React, pass the same function as onAssetRequest.

Without the SDK

The editor talks to your page with postMessage. It sends { source: "bannerify-embed", type, data } to the page that frames it, and accepts { source: "bannerify-host", type, requestId, data } only from that page. It answers each command with { source: "bannerify-embed", type: "response", requestId, data }, or with error in place of data. The command types are open-template, close-template, set-modifications, save, request-content and asset-response. Answer each asset:request event with asset-response and the data { requestId, asset }, where asset is { url, fileName }, { value } or null.

What your users can do

  • Create designs in common sizes, or copy a template of your workspace.
  • Edit text, images, shapes, QR codes and the other layers of the editor.
  • Upload their own images.
  • Download a design as PNG, JPEG, WebP or PDF. Each download is one generation of your workspace.

Turn each of these on or off under Permissions. Under Appearance, set the name and logo at the top of the editor, and the accent color of its main buttons.

Render the designs of a user

List the designs of one user with the embedUserId parameter of List templates, then render them like any template.

curl "https://api.bannerify.co/v1/templates?apiKey=bnfy_xxx&embedUserId=user_123"

The templates of your users do not show in the template list of the dashboard.

Webhooks

Add a URL under Webhook, and your server gets a POST when a user creates, changes or deletes a design. Use them to keep your own records, because the events of the SDK only reach the open page.

{
  "id": "eev_01J9X...",
  "type": "template.create",
  "createdAt": "2026-10-19T08:00:00.000Z",
  "embedId": "emb_xxx",
  "user": { "id": "user_123" },
  "template": { "id": "tpl_xxx", "name": "Summer sale", "width": 1080, "height": 1080 }
}
type When
template.create A user starts a design or copies a template.
template.update A user saves or renames a design.
template.delete A user deletes a design.
test You click Send test event. user and template are null.

user is null for a token without sub. Answer with a 2xx status within 10 seconds. A delivery that fails is tried again after 1, 2, 4, 8, 16 and 32 minutes. The same event always has the same id, so skip an id that you already handled.

Check the signature

Each request has a Bannerify-Signature header: t=<unix seconds>,v1=<signature>. The signature is the hex HMAC-SHA256 of <t>.<raw body> with the webhook secret.

import { createHmac, timingSafeEqual } from "node:crypto"

const verify = (rawBody: string, header: string, secret: string) => {
  const { t, v1 } = Object.fromEntries(header.split(",").map((part) => part.split("=")))
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex")
  return v1.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
}

Read the raw body before you parse it: a parsed and serialized body can have other bytes.

Analytics

On the Enterprise plan, the Analytics section of the Embed page shows how your users use the editor in the last 7, 30 or 90 days: users, sessions, designs created and downloads, the sessions per day, the sites that opened the editor, and the most active users. A session is one load of the editor. Days are in UTC.

Rotate the signing secret

Click Rotate secret when the secret leaks. Tokens signed with the old secret stop working at once, so update your server first.

Troubleshooting

Message What to do
The embed token is not valid Sign the token with the current signing secret, with alg HS256 and an exp claim.
This embed is turned off Turn on the embed in the dashboard.
This app reached its limit of editor users Each sub counts as one user. Upgrade to Enterprise for more users.
This app used all its generations for this month Downloads count as generations of your workspace. Wait for the reset on the first day of the month, or change your plan.
The page does not show the editor Add the origin of the page under Allowed domains.