docs

Embed a Form

Put a survey on your own site with an iframe. No SDK, no DNS, no keys in the page.

A survey Form has a public page. Drop it in an iframe and it belongs to your site — the address bar says your domain, because the page is yours.

anywhere in your HTML
<iframe
  src="https://noonmark.dev/f/satisfaction"
  title="Tell us how we did"
  allow="tools"
  style="width:100%;height:560px;border:0"
></iframe>

That is the whole integration. Nothing is installed, no API key appears in the page, and submissions land in the Studio exactly as they do from the hosted page.

allow="tools" is for an in-browser agent: a cross-origin iframe only registers the Form's WebMCP tools when the embed delegates the tools policy. Leave it out and the Form still works; an agent just has no tools to call in it.

Which Forms have a page

Only survey. A feedback Form has no public screen on purpose — its screen lives in your app, and you post to the ingest from there.

The handle is the Form's slug, which you set in the Studio. The API key also works in its place (/f/frm_…), which is useful before you have given the Form a slug.

Height

The Form reports its own height to the page around it, so the iframe can follow its content instead of guessing. Listen for the message and set the height:

the whole integration, with resizing
<iframe
  id="noonmark"
  src="https://noonmark.dev/f/satisfaction"
  title="Tell us how we did"
  allow="tools"
  style="width:100%;height:420px;border:0"
></iframe>

<script>
  addEventListener("message", (event) => {
    // Only this origin may resize the frame.
    if (event.origin !== "https://noonmark.dev") return
    if (event.data?.type !== "noonmark:height") return
    document.getElementById("noonmark").style.height = event.data.height + "px"
  })
</script>

The height in the style attribute still matters: it is what the frame shows between first paint and the first message. Pick something close — around 420px for a few fields — and the resize will be invisible.

The message is { type: "noonmark:height", height: 512 } and carries nothing else. It fires when the Form first renders, whenever a field appears or an error message pushes the layout down, and once more when webfonts finish loading.

Check event.origin. Any page in any frame can post a message; the check is what stops another one resizing your iframe. The Form posts to * because it cannot know your origin in advance — yours is the side that can verify.

Without the script the iframe simply stays at the height you set, and a long Form scrolls inside it. Nothing breaks.

Testing locally

Open your test page over http://, not by double-clicking the file. A Form permits framing with frame-ancestors *, and in CSP that wildcard covers network origins only — a page on file:// is refused and the frame stays blank. Any local server will do.

Appearance

The page renders in Noonmark's dark register and does not currently take a theme parameter. If that clashes with your site, the alternative is to build the form yourself and post to the ingest — you get the fields from GET /api/v1/form and own the markup completely. See the SDK.

Why this is allowed

Most apps send X-Frame-Options: DENY, which stops a page being framed at all. Noonmark sends Content-Security-Policy: frame-ancestors * on /f/ specifically, so any site may embed a Form — and frame-ancestors 'none' everywhere else, so the Studio cannot be put in a frame by anybody.

That split is deliberate. A Form is meant to be embedded; an account is not.

What this is not

Embedding is not a custom domain. The iframe's src stays on noonmark.dev, which nobody sees, but a link you send by email would show it. Serving a Form from forms.acme.com is a separate feature and is not live yet — the Studio lets you reserve the name, nothing more.

Prefill through the src

The query string travels with the src. /f/satisfaction?source=pricing-page fills the source Field the way the link would, hidden or not — see Prefill from the link.

What did you think of this content?

On this page