---
url: /en/docs/api/custom-pages.md
description: >-
  Add custom pages to the Sveltia CMS UI with React components, embedded web
  pages or links.
---

# Custom Pages

A custom page lets you add your own page to the CMS, next to the Contents, Assets and Editorial Workflow pages. It can show a dashboard built with a React component, embed another web page, such as your analytics or deployment status, or simply link to an external site.

## Overview

To register a custom page, use the `registerCustomPage` method on the [`CMS` object](/en/docs/api#accessing-the-cms-object):

```js
CMS.registerCustomPage(definition);
```

### Parameters

* `definition` (object, required): The page definition, with the following properties:
  * `id` (string, required): A unique identifier for the page, consisting of alphanumeric characters, hyphens and underscores. The page is opened at `#/pages/{id}`, so you can link to it directly. Registering a page with the same ID again replaces the previous one in place.
  * `label` (string, required): The label of the page, displayed as the page title and as the tooltip of the page’s button.
  * `icon` (string, optional): The name of a [Material Symbols](https://fonts.google.com/icons?icon.set=Material+Symbols) icon displayed on the page’s button, e.g. `monitoring`. Defaults to `extension`.
  * `component` (React component): A [React component](/en/docs/api#writing-react-components) rendering the page content. The component receives no props. You can write it with [HTM](/en/docs/api#using-htm), JSX or `h()` calls.
  * `url` (string): The URL of a web page to show. A relative URL is resolved against the current page URL. Only `http` and `https` URLs are supported.
  * `newTab` (boolean, optional): Whether to open the `url` in a new browser tab instead of embedding it in the CMS. Defaults to `false`. Can only be used with `url`.

Either `component` or `url` must be given, but not both. An invalid definition throws a `TypeError`.

## Where Pages Appear

On desktop, custom pages are listed in the global toolbar after the built-in pages, in the order of registration, and can be opened with the Alt+1–9 keyboard shortcuts along with the built-in pages. A page opening in a new tab is shown as a separate button after the page buttons.

On mobile, the bottom navigation has little room, so custom pages are listed in the Pages section of the Menu page instead.

Pages can be registered before or after the CMS is initialized. A page registered later appears right away, and a link to `#/pages/{id}` shows the page as soon as it’s registered.

## Examples

### React Component

The following page greets the user and counts their clicks, using the `useState` hook. A component can use any [hook](/en/docs/api#using-hooks) taken from `CMS.React`.

::: code-group

```js [HTM]
const { useState } = CMS.React;

const Dashboard = () => {
  const [count, setCount] = useState(0);

  return html`
    <div style="padding: 16px">
      <h3>Welcome back!</h3>
      <p>You’ve clicked the button ${count} times.</p>
      <button type="button" onClick=${() => setCount(count + 1)}>Click Me</button>
    </div>
  `;
};

CMS.registerCustomPage({
  id: 'dashboard',
  label: 'Dashboard',
  icon: 'dashboard',
  component: Dashboard,
});
```

```jsx [JSX]
const { useState } = CMS.React;

const Dashboard = () => {
  const [count, setCount] = useState(0);

  return (
    <div style={{ padding: 16 }}>
      <h3>Welcome back!</h3>
      <p>You’ve clicked the button {count} times.</p>
      <button type="button" onClick={() => setCount(count + 1)}>
        Click Me
      </button>
    </div>
  );
};

CMS.registerCustomPage({
  id: 'dashboard',
  label: 'Dashboard',
  icon: 'dashboard',
  component: Dashboard,
});
```

```js [h()]
const { useState } = CMS.React;

const Dashboard = () => {
  const [count, setCount] = useState(0);

  return h(
    'div',
    { style: { padding: 16 } },
    h('h3', null, 'Welcome back!'),
    h('p', null, `You’ve clicked the button ${count} times.`),
    h('button', { type: 'button', onClick: () => setCount(count + 1) }, 'Click Me'),
  );
};

CMS.registerCustomPage({
  id: 'dashboard',
  label: 'Dashboard',
  icon: 'dashboard',
  component: Dashboard,
});
```

:::

The page area has no padding, so your component can fill it, e.g. with a full-size chart. Add your own padding as needed.

### Embedded Web Page

To show another web page within the CMS, provide its URL. The page is embedded in an iframe that fills the page area.

```js
CMS.registerCustomPage({
  id: 'analytics',
  label: 'Analytics',
  icon: 'monitoring',
  url: 'https://plausible.io/share/example.com?auth=XXXXXXXX',
});
```

A relative URL is resolved against the CMS admin page URL, so a page of your own site can be embedded as well:

```js
CMS.registerCustomPage({
  id: 'style-guide',
  label: 'Style Guide',
  icon: 'palette',
  url: '/style-guide/',
});
```

::: warning Embedding restrictions

Many sites prevent their pages from being embedded in an iframe with the `X-Frame-Options` or `Content-Security-Policy` response header. Such a page appears blank or shows an error in the CMS. Use the `newTab` option for these sites instead.

:::

::: danger Security note

A page embedded from the same origin as the CMS, e.g. a page on your own site when the CMS is served from the same domain, has full access to the CMS, including the signed-in user’s access token stored in the browser. Only embed pages you trust.

:::

### Link Opening in a New Tab

To add a shortcut to an external site, such as your hosting dashboard or a style guide, set the `newTab` option to `true`. The URL is opened in a new browser tab, and the user stays where they are in the CMS.

```js
CMS.registerCustomPage({
  id: 'netlify',
  label: 'Netlify Dashboard',
  icon: 'cloud',
  url: 'https://app.netlify.com/sites/example/overview',
  newTab: true,
});
```

## Migrating from Static CMS

Static CMS provided a similar feature with the `CMS.registerAdditionalLink()` method. As Static CMS has been archived, Sveltia CMS doesn’t support the method. Use `CMS.registerCustomPage()` instead:

* `id` and `title` correspond to `id` and `label`, respectively.
* `data` corresponds to `component` for a React component, or `url` for a link. Add `newTab: true` to open the link in a new tab, or omit it to embed the page in the CMS.
* `options.iconName` corresponds to `icon`, which takes a Material Symbols icon name rather than the name of an icon registered with `CMS.registerIcon()`.
