# Introduction (https://docs.hub.ki/custom-apps)



A **custom app** is a single, self-contained HTML document that HUB.KI embeds in
a sandboxed iframe. Your app talks to the platform through a small message
bridge — enough to store data, know the current user, react to their theme and
language, publish and receive events, run AI agents, and share content.

Apps are ordinary web pages. Bring any framework, or none.

## How it works
The platform serves your artifact into a sandboxed iframe and exchanges
[JSON-RPC 2.0](https://www.jsonrpc.org/specification) messages with it over
`postMessage`. The [`@hub-ki/app-sdk`](https://docs.hub.ki/custom-apps/quick-start) package
wraps that exchange in plain `async` functions. Through the bridge your app can:

* read and write its **own private workspace** (per user)
* keep app state in a **key/value store** (per user, per version)
* read the signed-in **user's identity** and the shared **workspace documents** (with permission)
* get an **access token** to call HUB.KI APIs from its own backend (with permission)
* **publish and receive events** in real time, and **run AI agents** (with permission)
* show **toasts**, set the tab **title**, and open a **share dialog**

See the [SDK reference](https://docs.hub.ki/custom-apps/sdk) for the full client API.

Everything a custom app can do is gated by the [scopes](https://docs.hub.ki/custom-apps/manifest#scopes)
it declares, so the platform — and the user's organization — stays in control.

## Anatomy
An app is a folder with two required files:

```
my-app/
├── manifest.json   # metadata, scopes, versioning
└── app.html        # the single-file artifact
```

Optionally add an icon image (`icon.svg`, `icon.png`, …) referenced from the
manifest.

## Constraints
* **Single file.** The iframe loads exactly one HTML document of at most 10 MB.
  Inline all CSS and JavaScript — external URLs resolved relative to the
  artifact will not load.
* **Sandboxed.** The app runs with `allow-scripts allow-forms` and no
  same-origin access, so cookies and `localStorage` are unavailable. Persist
  state through the app's [key/value store](https://docs.hub.ki/custom-apps/sdk#store)
  (the `localStorage` replacement) or the [workspace](https://docs.hub.ki/custom-apps/sdk#workspace).
* **Locked-down content policy.** See below — it decides what may be inlined.
* **SDK major version 1.** Declare `sdkVersion` in the manifest; the host
  currently supports major version `1`.

### Content Security Policy
The platform serves every app with this policy:

```
default-src 'none';
script-src 'unsafe-inline';
style-src 'unsafe-inline';
img-src data: blob:;
worker-src blob:;
connect-src 'none';
base-uri 'none';
form-action 'none';
sandbox allow-scripts allow-forms
```

What that means in practice:

| You want to…                    | Works?                                                                                                             |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Inline `<script>` and `<style>` | Yes. Scripts and styles loaded from a URL do not.                                                                  |
| Show images                     | Only as `data:` or `blob:` URLs. Inline SVG markup also works.                                                     |
| Use a web font                  | No — fonts are not allowed from any source, including `data:` URLs. Use system fonts or draw glyphs as SVG.        |
| Play audio or video             | No — media is not allowed from any source.                                                                         |
| Call `eval` / `new Function`    | No. Bundlers and libraries that rely on it fail at runtime.                                                        |
| Run WebAssembly                 | Only with the `capability:wasm` [scope](https://docs.hub.ki/custom-apps/manifest#scopes), which adds `'wasm-unsafe-eval'`.                   |
| Start a Web Worker              | Yes, from a `blob:` URL.                                                                                           |
| Submit a `<form>`               | No navigation — handle `submit` in JavaScript and call `preventDefault()`.                                         |
| `fetch` your backend            | Only origins declared in [`connectOrigins`](https://docs.hub.ki/custom-apps/manifest#network-access) and approved by the organization admin. |

Ready to build one? Start with the [Quick start](https://docs.hub.ki/custom-apps/quick-start).
