---
title: "Reusable Content"
description: "Configure saved blocks, synced sections, premade blocks, custom blocks, and which block types are available in the Landing Page Editor."
url: https://docs.topol.io/landing-page-editor/guide/content-blocks.html
---

# Reusable Content

**Five options control what users can insert into a page and reuse across pages:** `savedBlocks` gives each user a personal section library, `syncedSectionsEnabled` keeps shared sections identical across pages, `premadeBlocks` and `premadeTemplates` supply ready-made designs, and `customBlocks` registers block types of your own. A sixth option, `contentBlocks`, trims which built-in block types appear at all.

## `savedBlocks`

The `savedBlocks` option (`boolean`, default `false`) enables the user's personal block library:

```typescript
{
  savedBlocks: true
}
```

With it enabled, users save custom sections (hero headers, footers, content modules) into a personal library and reuse them across landing pages. The library is backed by your [`api.SAVED_SECTIONS` endpoint](#api-integration-for-saved-blocks); **there is no local, in-memory variant**, so the option only works together with the endpoint.

## `syncedSectionsEnabled`

The `syncedSectionsEnabled` option (`boolean`, default `false`) enables sections that stay consistent across multiple pages:

```typescript
{
  syncedSectionsEnabled: true
}
```

A synced section is linked, not copied: **updating it updates every page that uses it**, which suits headers, footers, disclaimers, and promotional banners. Without the option, sections are edited independently per page. Synced sections are stored through the same [`api.SAVED_SECTIONS` endpoint](#api-integration-for-saved-blocks) as saved blocks.

> **Re-rendering on your backend**
>
> When you convert a saved page to HTML yourself through the [JSON to HTML](https://docs.topol.io/landing-page-editor/guide/operations/convertPageToHtml.html) endpoint, pass the current content of every synced section the page uses in the `syncedSections` request field. The endpoint swaps each referenced section (`syncedId`) for the supplied definition before rendering, so pages pick up the latest version of a shared header or footer even if they were saved before it changed. Without the field, the page renders with the section content embedded in the saved template JSON.

## `premadeBlocks`

The `premadeBlocks` option (`array | object | false`, optional) controls the library of ready-to-use design sections that users can insert with a single click.

When it is not set, the editor shows its **built-in premade block library** with predefined sections such as hero banners, feature lists, testimonials, and pricing tables.

To supply your own blocks, pass an array of named groups. Each group holds blocks with a `name`, a preview image `img`, and the section `definition` JSON. A plain array **replaces** the built-in library:

```typescript
premadeBlocks: [
  {
    name: "Heroes",
    blocks: [
      {
        name: "Hero 1",
        img: "https://example.com/previews/hero-1.png",
        definition: { /* section JSON, e.g. exported from a saved template */ }
      }
    ]
  }
]
```

To **extend** the built-in set instead of replacing it, use the wrapper form with `override: false`; your groups are added alongside the default library (`override: true` behaves like the plain array and replaces it):

```typescript
premadeBlocks: {
  override: false,
  blocks: [ /* same array of groups as above */ ]
}
```

To disable the premade blocks library completely, pass `false`:

```typescript
premadeBlocks: false
```

## `premadeTemplates`

The `premadeTemplates` option (`boolean`, default `false`) enables the full-page template library:

```typescript
{
  premadeTemplates: true
}
```

With it enabled, users browse and start from complete premade templates (product pages, lead-generation pages, event signup pages) instead of a blank page or whatever template you load. The picker can also be opened from your own UI:

```js
LPE.openPremadeTemplatesSelection();
```

By default the library is served by Topol from `app.topol.io`. To serve your own library instead, point the [`PREMADE_TEMPLATES`, `PREMADE_TEMPLATE_CATEGORIES`, and `PREMADE_TEMPLATES_KEYWORDS` endpoints](https://docs.topol.io/landing-page-editor/guide/api.html#available-endpoint-keys) at your backend; see [Premade Templates API](#premade-templates-api) below.

A companion option, `premadeTemplatesOptions` (`object`, optional), tunes the picker's UI:

```typescript
{
  premadeTemplates: true,
  premadeTemplatesOptions: {
    hideSearch: true
  }
}
```

-   `hideSearch` (`boolean`): removes the search field from the template picker, which suits a small curated library where search adds nothing.
-   `showDelete` (`boolean`): accepted by the schema but not currently wired to any UI, so it has no effect.

## `customBlocks`

The `customBlocks` option (`array`, default `[]`) registers your own block types, which users then add and configure like native blocks. This fits unique components such as dynamic CTAs, custom layouts, or platform-specific integrations.

**Basic custom block:**

```typescript
customBlocks: [
  {
    key: "custom-text-key",
    name: "My Customised Text",
    icon: "box",
    disabled: false,
    type: "text",
    attributes: {
      align: "right",
      padding: "10px 10px"
    },
    content: "Custom Text block"
  }
]
```

The fields every custom block accepts:

-   `key` (`string`, required): your identifier for the block. It is written into the template JSON, so keep it stable.
-   `name` (`string`, required): the label shown in the Elements panel.
-   `type` (`string`, optional): `"text"`, `"image"`, `"button"`, `"video"`, or `"mix"` for a block that combines several elements.
-   `icon` (`string`, optional): `"box"` or `"rss"` for the two built-in icons, an `https://` image URL, or an inline `<svg>…</svg>` string. Anything else falls back to the `"box"` icon.
-   `attributes` (`object`, optional): the block's default attributes, with `mdAttributes`, `smAttributes`, and `xsAttributes` overriding them at the tablet, mobile-landscape, and mobile breakpoints.
-   `content` (`string`, optional): the block's initial content.
-   `disabled` (`boolean`, optional) and `disabledBadge` (`string`, optional): grey the block out in the Elements panel and label it, the same way [`contentBlocks`](#contentblocks) does for built-in blocks.

**Mixed block with multiple elements:**

```typescript
customBlocks: [
  {
    key: "custom-mix",
    name: "Custom mixed block",
    icon: "box",
    disabled: false,
    type: "mix",
    blocks: [
      {
        type: "text",
        attributes: { align: "left" },
        content: "My custom content"
      },
      {
        type: "button",
        attributes: {
          align: "center",
          "background-color": "#417505"
        },
        content: "Click me!"
      }
    ]
  }
]
```

**Custom HTML with dialog:**

```typescript
customBlocks: [
  {
    key: "custom-block-key",
    name: "My Custom Block",
    dialog: true,
    icon: "box",
    content: "<p>initial content</p>",
    dialogButtonText: "Open Custom Dialog",
    disabled: false
  }
]
```

A dialog-style custom block needs the `onOpenCustomBlockDialog` callback. Like all callbacks, it is passed as a top-level property next to `config`:

```typescript
const LPE = LandingPageEditor({
  config: TOPOL_OPTIONS,
  onOpenCustomBlockDialog({ block }) {
    // open your own dialog UI here
    console.log("Custom block dialog requested:", block);
  },
});
```

> **WARNING**
>
> There is currently no public method to push edited content back into the custom block from your dialog.

## `contentBlocks`

The `contentBlocks` option (`object`, optional) controls which block types appear in the Elements panel, and whether they are disabled. It is an object keyed by block type; valid keys are `text`, `image`, `gif`, `button`, `divider`, `spacer`, `social`, `video`, `form`, `html`, `raw`, and `custom-raw`. Each entry accepts:

-   `hidden` (`boolean`): removes the block from the Elements panel entirely.
-   `disabled` (`boolean`): keeps the block visible but not usable.
-   `disabledText` (`string`): tooltip text shown on a disabled block.
-   `disabledBadge` (`string`): badge label shown on a disabled block (e.g. `"PRO"`).

```typescript
{
  contentBlocks: {
    html: { hidden: true },
    form: { disabled: true, disabledText: "Upgrade to use forms", disabledBadge: "PRO" }
  }
}
```

When `contentBlocks` is not set, all block types are available. The `disabled` variant is useful for upsell flows, where a block advertises a higher plan instead of vanishing.

## API Integration for Saved Blocks

Saved blocks and synced sections are stored on your server, with support for folders, search, pagination, and preview images. Enable the features and point `api.SAVED_SECTIONS` at your backend:

```typescript
const TOPOL_OPTIONS = {
  savedBlocks: true,
  syncedSectionsEnabled: true,
  api: {
    SAVED_SECTIONS: "https://your-domain.com/saved-blocks"
  }
};
```

Both features share the single `api.SAVED_SECTIONS` endpoint and are differentiated by the `type` field in requests and responses, which is one of `saved_section`, `synced_section`, or `folder`. In the URLs below, `{API.SAVED_SECTIONS}` stands for the base URL you configured.

> **WARNING**
>
> Before implementing the endpoints, check [how to work with API endpoints](https://docs.topol.io/landing-page-editor/guide/api.html).

### List Saved Blocks, Synced Sections and Folders

-   URL: `/{API.SAVED_SECTIONS}`
-   Method: `GET`
-   Content-Type: `application/json`

**Params:**

| key | value | required |
| --- | --- | --- |
| key | authorization api key | true |
| hostname | hostname | true |
| entity\_id | entity\_id corresponds to userId in Options | true |
| per\_page | number of items per page | false |
| current\_page | number of page to return | false |
| sort\_by | "name" or "date" or "type" | false |
| desc | "true" | false |
| folder\_id | id of folder to return | false |
| search | string to search | false |
| type | "saved\_section" or "synced\_section" to filter by type | false |

This endpoint is called when the editor lists all saved blocks/synced sections, changes sort, page or folder, searches by string, or whenever `LPE.refreshSyncedSections()` is called on the editor instance.

**Response:**

```json
{
  "success": true,
  "data": {
    "data": [
      {
        "id": 1,
        "name": "name of saved block, synced section, or folder",
        "type": "saved_section",
        "created_at": "ISO 8601 time",
        "definition": { "tagName": "mj-section", "children": ["..."] },
        "image": "https://url-to-preview-image.jpg",
        "folder_id": 2
      }
    ],
    "lastPage": 5,
    "parentFolderId": null
  }
}
```

In all saved-sections payloads, `definition` is the section JSON as an object (or an array of section objects), not a serialized string.

### Create Saved Block, Synced Section or Folder

-   URL: `/{API.SAVED_SECTIONS}`
-   Method: `POST`

This endpoint is called when a new saved block, synced section, or folder is created.

**Request:**

```json
{
  "entity_id": "entity user_id",
  "hostname": "origin",
  "key": "api key",
  "type": "saved_section",
  "name": "name of new item",
  "definition": { "tagName": "mj-section", "children": ["..."] },
  "folder_id": 1
}
```

**Response:**

```json
{
  "success": true,
  "data": {
    "id": 2,
    "name": "name of saved block, synced section, or folder",
    "type": "saved_section",
    "created_at": "ISO 8601 time",
    "definition": { "tagName": "mj-section", "children": ["..."] },
    "folder_id": 1
  }
}
```

### Get Synced Section Detail

-   URL: `/{API.SAVED_SECTIONS}/{id}`
-   Method: `GET`
-   Params: `key`, `hostname`, `entity_id`

This endpoint is called only for **synced sections**: when a synced section is inserted into a template, and when a template containing synced sections is refreshed so the latest content is displayed. Regular saved blocks are inserted from the listing data and never hit this endpoint.

**Response:**

```json
{
  "success": true,
  "data": {
    "id": 1,
    "name": "name of the synced section",
    "type": "synced_section",
    "created_at": "ISO 8601 time",
    "definition": { "tagName": "mj-section", "children": ["..."] },
    "folder_id": 2
  }
}
```

The response's `type` must be exactly `"synced_section"`, and `success` must be `true`. If either is missing, the editor treats the section as missing and drops it from the page instead of reporting an error, so this is worth checking first when synced sections disappear on load.

### Edit Saved Block, Synced Section or Folder

-   URL: `/{API.SAVED_SECTIONS}/{id}`
-   Method: `PATCH`

This endpoint is called when editing the name, content, or folder location of a saved block, synced section, or folder.

**Request:**

```json
{
  "entity_id": "entity user_id",
  "hostname": "origin",
  "key": "api key",
  "folder_id": 1,
  "definition": { "tagName": "mj-section", "children": ["..."] },
  "name": "edited name"
}
```

**Response:**

HTTP `200 OK`

### Delete Saved Blocks, Synced Sections or Folders

-   URL: `/{API.SAVED_SECTIONS}/delete`
-   Method: `POST`

This endpoint is called when the user deletes saved blocks, synced sections, or folders. For synced sections, the last loaded version remains in templates where it was used.

**Request:**

```json
{
  "entity_id": "entity user_id",
  "hostname": "origin",
  "key": "api key",
  "blocksToDelete": [1, 2, 3]
}
```

**Response:**

HTTP `200 OK`

### Refresh Synced Sections

Call `refreshSyncedSections` on the editor instance to update the synced sections listing and reload the template with the latest changes:

```js
const LPE = LandingPageEditor({ config: TOPOL_OPTIONS });
LPE.render("#landing-page-editor");

// later, e.g. when another user changed a synced section:
LPE.refreshSyncedSections();
```

This matters when multiple users work in the editor simultaneously: when someone else creates, edits, or deletes a synced section, calling this function keeps the current view up to date.

## Premade Templates API

The premade template library is served by three endpoints, which default to Topol's own collection on `app.topol.io` and can be repointed at your backend through the [`api` option](https://docs.topol.io/landing-page-editor/guide/api.html):

| Endpoint key | Serves |
| --- | --- |
| `PREMADE_TEMPLATES` | The template listing, and one template's full detail at `{PREMADE_TEMPLATES}/{id}` |
| `PREMADE_TEMPLATE_CATEGORIES` | The categories offered as filters |
| `PREMADE_TEMPLATES_KEYWORDS` | The keywords offered as filters |

The listing is a `GET` with `per_page` and `current_page` query parameters, plus `search`, `categories`, and `keywords` when the user filters. Both the listing and the detail must answer with a `{ "success": true, "data": ... }` envelope; a missing or falsy `success` is reported to the user as a fetch failure.

When the URL points at `app.topol.io`, the editor authenticates with your Public API key:

```json
{
  "accept": "application/json",
  "Authorization": "Bearer <api-key>"
}
```

> **WARNING**
>
> That header is added **only** for `app.topol.io` URLs; endpoints on your own domain get no Topol credentials, so secure them with [`apiAuthorizationHeader`](https://docs.topol.io/landing-page-editor/guide/api.html#securing-api-endpoints) instead.
>
> Note that `apiAuthorizationHeader` also reaches `app.topol.io`, and its string form sets `Authorization`, replacing the key the default library expects. If you use the string form and keep Topol's premade templates, host your own template endpoints or use the object form with a custom header name.
