> ## Documentation Index
> Fetch the complete documentation index at: https://docs.exode.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom pages

> Your school's own pages with mini apps at your own URL

A school can create its own pages: each page gets its own address
(`https://your-school.exode.biz/your-slug`) and embeds a **mini app** in an iframe: a website or
web application hosted anywhere (Vercel, your own server, etc.).

The mini app receives signed user data from the platform ([Init Data](/en/exode-sdk/miniapp/init-data)),
can verify it on its backend and build any logic, from a landing page to a full-fledged
service with its own database.

## Creating a page

1. Open **Control Panel** → **School** → **Customization** → **Apps & pages**.
2. Click **Create app** and fill in:
   * **Title**: shown in the header and the menu (in the school's languages);
   * **App address (slug)**: lowercase Latin letters, digits and hyphens, 2–64 characters.
     The page opens at `/<slug>`. The platform's system addresses are reserved;
   * **App URL (iframe)**: the https address of your mini app (it opens in an iframe);
   * **Window type**: how the app is shown to the user (see below);
   * **Layout**: only for the **Page** type: **Full width** or **Island** (a card with margins);
   * **Available without login**: whether to show the page to unauthenticated visitors. Such a visitor also
     receives signed Init Data, but without user data (`user` = `null`).
3. After creating the page, open the page menu "⋯" → **Show secret**. Your
   server needs this secret to [verify Init Data](/en/exode-sdk/miniapp/init-data). Keep it on the server only.
   The same menu has **Regenerate secret**: the old secret stops working immediately.

<Info>
  Pages can be created and edited by a manager with the **"Apps & Pages Management"** permission (`SchoolManagePages`).
</Info>

<Note>
  The mini app's website must allow embedding in an iframe: do not send the `X-Frame-Options` header, and if
  you use `Content-Security-Policy: frame-ancestors`, include the school's domain in it.
</Note>

<Tip>
  Add a menu item for the new page in **Settings** → **Left site menu** with the link `/<slug>`:
  navigation will be internal, without a reload. You can also open the app with a button
  from lesson content.
</Tip>

## Window type

| Type | What it looks like | When it fits |
| - | - | - |
| **Page** | A regular platform page at `/<slug>` | A landing page, a reference, a full-fledged section |
| **Floating window** | A window on top of the interface that minimizes into a stack | A chat, a calculator, an assistant: things used without interrupting learning |
| **Side panel** | A side panel next to the content | Notes, hints, tools next to a lesson |
| **Fullscreen** | A window covering the whole screen on top of the interface; minimizes into the stack like the other windows | Wizards and editors that need all the space, e.g. a course builder |

At most one window can be expanded at a time: opening a second one minimizes the first.
Minimized windows collect in a tray and expand with a single click.

On a phone, every window type opens as a regular page.

## Page parameters

The page address parameters are passed to the iframe as a query string: the link `/<slug>?courseId=5` opens the
app at `https://<your-app>/…?courseId=5#exodeInitData=…`. If the app URL already has its own query, the parameters
are appended to it, and Init Data still arrives in the hash. The platform's service parameters (`modal`, `popup`,
`appId`) are not passed to the iframe.

This works for every window type. If the app is already open and gets opened with different parameters (for
example, to continue working on another course), the iframe reloads with the new address; with the same
parameters the window just expands.

<Warning>
  Parameters come from an address anyone can craft. Use them only as a hint about what to open, and check access to
  that entity on your server or with an API request made on behalf of the user.
</Warning>

<Note>
  A window restored after the platform reloads opens without parameters.
</Note>

## School home page

In the page list, you can set a **main** page: the selected page (system or custom)
opens at `/`. A custom page set as the main page cannot be deleted or
turned off; set another main page first.

## iframe lifecycle

An open app page is not unloaded when navigating around the platform: the iframe loads
once and stays in memory, so reopening it is instant. The same goes for a minimized window:
minimizing does not reload the app, and its state is preserved.

Two commands are available from the mini app itself ([MiniApp SDK](/en/exode-sdk/miniapp/client)):

* `app.ui.minimize()`: minimize the window (does nothing for the **Page** type, since there is nothing to minimize);
* `app.ui.close()`: close the app and unload the iframe.

<Note>
  The platform keeps a limited number of apps in memory and unloads the oldest
  inactive ones. Do not rely on state surviving a close; save important data
  on your server.
</Note>

## What's next

* [Introduction to MiniApp SDK](/en/exode-sdk/miniapp/introduction): the bridge, context, commands and events;
* [Init Data](/en/exode-sdk/miniapp/init-data): verifying signed data on your server.

***

*Updated: 2026-09-28 05:04 UTC*


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.