Appearance
Embedding
Drop a Krafter survey onto any web page. The embed script is a thin wrapper that renders the survey's public page (/s/:slug) inside an iframe — inline, in a popup, or wherever you mark up a container.
Embed surface stability
The embed script API (data attributes, modes, host override) may evolve before reaching v1. Lock-step semver is not committed yet — pin the script tag to a specific commit if you need byte-stable behavior, or expect that today's data-mode values may grow over time.
The script
html
<script src="https://app.krafter.dev/embed/surveys.js" defer></script>That's the whole installation. The script:
- Waits for
DOMContentLoaded - Finds every element with a
data-krafter-survey="<slug>"attribute - Renders the survey based on
data-mode(inline iframe, popup trigger) - Listens for
postMessagefrom the iframe to auto-resize inline embeds
The script is served from the same origin as the API, with Cache-Control: public, max-age=3600 and CORS-permissive headers, so a single edge cache hop covers most page loads.
Host override
Self-hosting Krafter on a custom domain? Pass data-host on the script tag — the embed will rewrite the iframe URL accordingly:
html
<script
src="https://my-krafter.example.com/embed/surveys.js"
data-host="https://my-krafter.example.com"
defer></script>The default is https://app.krafter.dev. If you don't override, all iframes load from app.krafter.dev.
Inline embed
Drop the script anywhere in the page, then add a container where the survey should render:
html
<!-- Anywhere on the page -->
<div data-krafter-survey="product-feedback"></div>
<!-- Once at the bottom of <body> -->
<script src="https://app.krafter.dev/embed/surveys.js" defer></script>The container is replaced with an iframe pointing at https://app.krafter.dev/s/product-feedback?embed=true. Default styling:
width: 100%border: nonemin-height: 500pxloading="lazy"so the iframe defers loading until it nears the viewport
The embed script auto-resizes the iframe height as the respondent moves through questions, by listening to postMessage events of shape { type: "krafter-survey-resize", height: <px> } from the iframe. You don't have to wire anything — it just works.
data-mode="inline" is the default; you can omit it.
Popup embed
Render a clickable trigger that opens the survey in a centered modal:
html
<button data-krafter-survey="product-feedback" data-mode="popup">
Give us feedback
</button>
<script src="https://app.krafter.dev/embed/surveys.js" defer></script>Clicking the element opens a full-screen overlay with the survey iframe inside a centered modal (max-width 640px, max-height 90vh). The overlay closes when:
- The
×button in the corner is clicked - The dim background outside the modal is clicked
If you want a different element to be the trigger (for example, a div containing your own button styling), nest a data-trigger element inside:
html
<div data-krafter-survey="product-feedback" data-mode="popup">
<button data-trigger>Give us feedback</button>
</div>The whole container becomes clickable by default; data-trigger lets you scope the click handler to a specific child.
Slide-in / other modes
Only inline and popup are wired up today. data-mode="slide-in" is recognised in the script but currently falls through to the no-op branch — treat it as reserved.
Customising the survey UI
The embed script is intentionally thin — it doesn't know about colors, fonts, or button labels. Survey appearance is controlled by survey.settings, which the public /s/:slug page reads. To customise:
- Edit
settingson the survey viaPATCH /surveys/:id. Schema today is free-form (theme, button labels, redirect URL); the dashboard provides the canonical authoring UI - Or use the Surveys dashboard "Appearance" tab on each survey
The same settings apply to:
- The standalone public page at
https://app.krafter.dev/s/:slug - The inline embed
- The popup embed
- Per-respondent invite links at
/s/:slug/:token
Multiple surveys per page
The script scans for all data-krafter-survey containers on DOMContentLoaded. Multiple inline embeds, multiple popups, or a mix — drop as many as you need:
html
<section>
<h2>Quick poll</h2>
<div data-krafter-survey="weekly-pulse"></div>
</section>
<aside>
<button data-krafter-survey="bug-report" data-mode="popup">Report an issue</button>
</aside>Each container is independent — they don't share state and they don't need to be siblings.
Adding extra context to responses
The embed script doesn't expose hooks to inject metadata or respondent_token today. If you need to attach custom metadata (UTM tags, internal user id) or link a response back to an invited respondent, don't use the embed script — render the survey yourself by calling the public response API and passing those fields on start_response. The embed widget is the one-line drop-in path; the API is the full-control path.
For the per-respondent invitation flow (token-based), see Respondents.
Content Security Policy
Hosts running with a strict CSP need to allow:
script-src https://app.krafter.dev— to load the embed scriptframe-src https://app.krafter.dev— to render the iframeimg-src https://app.krafter.dev— for any survey-side imagery
If you self-host, swap app.krafter.dev for your own host.
Caveats
- iframes block password manager autofill for
emailquestions on some browsers. If your survey starts withemailcapture and you depend on autofill, prefer a per-respondent invite link instead of the embed - Mobile keyboards can cover the inline iframe if it sits at the bottom of a tall page. The auto-resize logic responds to question changes but not to viewport changes — design the surrounding page so the iframe has room to grow
- No script-level events fired today — there is no
krafter:survey:completedevent onwindowto listen to. Use theresponse.completedwebhook instead, or poll the response endpoint server-side