Skip to content

Sandbox restrictions

Apps run in a blob-URL iframe sandboxed with allow-scripts allow-forms only. Same-origin and top-navigation are off, so several browser APIs throw or silently fail:

  • Storage APIs blocked. localStorage, sessionStorage, IndexedDB, and cookies all throw — there is no allow-same-origin. Don't reach for localStorage to survive refresh; it won't work. Persist per-user state in a privateOfUser(self) table and shared state in a public table.
  • No top-level URL navigation. window.location.href = …, location.assign, top-level window.open, target="_top", and cross-origin history.pushState all fail. To switch apps, call Poe.open({ typeId, instanceId, openProps? }) from poe-tiles-sdk. Outbound links work via <a target="_blank" rel="noopener">.
  • window.location.origin is "null". Use Poe.topOrigin for an absolute host URL.
  • No cross-frame DOM access. Reading the parent document or any other frame is blocked. Talk to the host via the SDK's postMessage wrappers.
  • Web Workers work, but only from a blob or data URL. A worker script has to be embedded in your code, not loaded by path — there is no origin or server behind the iframe, so new Worker("./worker.js") and new Worker(new URL("./worker.ts", import.meta.url)) both fail (the latter with TypeError: Invalid URL, and poeTile() rejects it at build time). Bundled apps: import MyWorker from "./worker.ts?worker&inline" — the &inline matters, since a plain ?worker import compiles to a path nothing serves here (poeTile() rejects that at build time too). No-build apps: blob your worker source yourself, or new Worker(await Poe.getBundleAssetUrl("worker.js")). Recipes for both are in Vite Plugin → Web Workers. Use classic worker syntax; a module script cannot be fetched from a blob URL here. SharedWorker is denied to the opaque origin outright, and navigator.serviceWorker.register is blocked. A worker inherits the sandbox, so it has no storage or network of its own.