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.
<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:
<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?