# Plugin Pages

> Add your own screens to Broadcast: a menu page, a subscriber panel or a dashboard widget. Learn how the sandbox and the bridge work.

Source: https://sendbroadcast.net/docs/plugins-pages

> Coming in Broadcast 3.0. Until then, this page applies only to the Upcoming (edge) build.

A plugin can show its own screens inside Broadcast. You write plain HTML, CSS and JavaScript in a folder named `ui`.

## Three places for a page

| Slot | Where it shows | Size |
|---|---|---|
| `nav` | A full page, listed under **Plugins** in the sidebar | Fills the main area, edge to edge. Your page sets its own padding |
| `subscriberPanel` | A small box on each subscriber's page | 16rem high |
| `dashboardWidget` | A small box on the dashboard | 16rem high |

## Full example: a hello page

Build pages with [Signal](https://sendbroadcast.net/docs/signal), the UI kit that Broadcast serves. It gives you ready-made components, and your page looks like part of Broadcast.

This plugin has one menu page. It shows a greeting, and it counts how many times the page opened.

```text
hello-page/
  manifest.json
  dist/main.js
  ui/index.html
  ui/app.js
```

`manifest.json`:

```json
{
  "id": "local.hello-page",
  "name": "Hello page",
  "version": "1.0.0",
  "requires": { "broadcast": ">=3.0.0 <4.0.0", "pluginApi": "1" },
  "ui": {
    "pages": [
      { "slot": "nav", "title": "Hello", "path": "ui/index.html" }
    ]
  },
  "settings": {
    "greeting": { "type": "string", "label": "Greeting" }
  }
}
```

`dist/main.js`:

```javascript
export default definePlugin({
  async pageData(request, ctx) {
    const opens = await ctx.store.increment("opens");

    return {
      greeting: ctx.settings.get("greeting") || "Hello",
      channel: ctx.channel.name,
      opens,
    };
  },
});
```

`ui/index.html`:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello</title>
    <link rel="stylesheet" href="/plugin-kit/v1/kit.css">
    <script type="module" src="/plugin-kit/v1/kit.js"></script>
    <script type="module" src="app.js"></script>
  </head>
  <body>
    <sg-page title="Hello" description="A first page, built with Signal.">
      <sg-banner id="problem" tone="danger" hidden>The page could not load.</sg-banner>
      <sg-card title="This channel">
        <p id="greeting">Loading...</p>
        <p class="sg-help" id="opens"></p>
      </sg-card>
    </sg-page>
  </body>
</html>
```

`ui/app.js`:

```javascript
try {
  const data = await sg.pageData();
  document.getElementById("greeting").textContent = `${data.greeting} from ${data.channel}.`;
  document.getElementById("opens").textContent = `This page opened ${data.opens} times.`;
} catch {
  document.getElementById("problem").hidden = false;
}
```

Pack it with the `ui` folder:

```bash
zip -r -X ../hello-page-1.0.0.broadcast-plugin manifest.json dist ui
```

After you deploy it and turn it on, open **Plugins** in the sidebar, and click **Hello**.

For a full page with a form and a table, follow [Signal Quick Start](https://sendbroadcast.net/docs/signal-quick-start).

## The sandbox

Your page runs in a sandboxed frame. Think of it as a page from another website, placed inside Broadcast. This protects the people who use Broadcast: a plugin page cannot act as them on its own.

Here is what that means for your code:

| Not allowed | Do this instead |
|---|---|
| Inline `
