Skip to main content

Blocks API

Blocks are reusable design fragments your users save in the editor. They come in two flavors:

  • Shared project blocks — available to everyone in the project.
  • User-saved blocks — saved by one of your end-users and visible only to them (the user id your app passes to the editor at init time).

The Blocks API lets your backend list all of them in one sweep — you don't need to know or enumerate your end-user IDs. Each user-saved block carries the userId it was saved under, so you can:

  • Report usage — which of your end-users rely on saved blocks, and how many each has.
  • Back up or migrate — export every block's design JSON, including blocks saved by users you can no longer identify individually.
Server-side only

This endpoint returns blocks saved by every end-user in the project. Never call it from a browser, and never proxy its unfiltered response to one of your end-users — if you expose blocks to an end-user, filter with the userId parameter to that user's own blocks. See Authentication for token handling guidelines.


List blocks

GET https://api.unlayer.com/v3/blocks

Authenticate with a bearer token — a project API Key is recommended. With a Personal Access Token, also pass projectId (query param or X-Project-Id header).

curl "https://api.unlayer.com/v3/blocks?displayMode=email" \
-H "Authorization: Bearer unlayer_sk_your_api_key"

Query parameters

ParameterTypeDefaultDescription
limitinteger20Blocks per page (1–100).
cursorstringPagination cursor from the previous response.
displayModestringFilter by display mode: email, web, popup, or document. Omit to list blocks across all display modes.
scopestringallshared for project blocks only, user for end-user saved blocks only, all for both.
userIdstringOnly blocks saved by this end-user (exact match on the user id your app passes to the editor).
categorystringFilter by category (case-insensitive search).
includeDatabooleantrueWhether to include each block's design JSON. Pass false for lightweight sweeps such as usage reports.
projectIdstringRequired with a Personal Access Token; omit with an API key (the project is embedded in the key).

Response

{
"data": [
{
"id": "918",
"userId": "u_4f8a12",
"displayMode": "email",
"category": "Headers",
"tags": ["header", "promo"],
"data": { "columns": ["…"] },
"thumbnailUrl": "https://…/thumbnail.png",
"syncId": null,
"isSyncEnabled": false,
"createdAt": "2026-06-14T09:12:33.000Z",
"updatedAt": "2026-07-01T16:40:05.000Z"
},
{
"id": "702",
"userId": null,
"displayMode": "email",
"category": "Footers",
"tags": [],
"data": { "columns": ["…"] },
"thumbnailUrl": null,
"syncId": "s-b41c…",
"isSyncEnabled": true,
"createdAt": "2026-03-02T11:00:00.000Z",
"updatedAt": "2026-03-02T11:00:00.000Z"
}
],
"next_cursor": "aWQ6NzAy",
"has_more": true
}
  • userId is null for shared project blocks and set to the end-user id for user-saved blocks.
  • syncId / isSyncEnabled identify synced blocks; designs reference synced blocks by syncId, so preserve it when migrating.
  • data is the block's design JSON, returned exactly as stored. Pages can be large with includeData=true — keep limit modest when sweeping designs.

Pagination

Follow next_cursor until has_more is false:

curl "https://api.unlayer.com/v3/blocks?limit=100&cursor=aWQ6NzAy" \
-H "Authorization: Bearer unlayer_sk_your_api_key"

The listing is a point-in-time sweep: blocks created while you are paginating may not appear until the next sweep.


Recipe: who uses saved blocks, and how many each?

Sweep with includeData=false (no design payloads — fast and small), then group by userId:

const headers = { Authorization: `Bearer ${process.env.UNLAYER_API_KEY}` };
const counts = {};
let cursor = null;

do {
const url = new URL('https://api.unlayer.com/v3/blocks');
url.searchParams.set('includeData', 'false');
url.searchParams.set('scope', 'user');
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('cursor', cursor);

const page = await fetch(url, { headers }).then((r) => r.json());
for (const block of page.data) {
counts[block.userId] = (counts[block.userId] ?? 0) + 1;
}
cursor = page.next_cursor;
} while (cursor);

// counts = { "u_4f8a12": 12, "u_9c01d7": 3, … }

Drop scope=user to include shared project blocks in the same sweep — they arrive with userId: null.

For a backup, run the same loop without includeData=false and persist each block's data (and syncId, if set).


Notes

  • The API can return more blocks than the editor shows. The editor displays blocks up to your plan's Blocks limit; this endpoint is a faithful dump of everything stored in the project, so counts can differ — for example after a plan downgrade. That is intentional: backups must not silently lose blocks.
  • The API does not modify blocks — creating, updating, and deleting blocks still happens through the editor.