billing.example.com, an editor on editor.example.com, a report
builder from a separate deployment. The browser keeps that frame’s page closed to
your main page, so on its own the guide could neither read it nor point into it.
Sub-apps in frames closes that gap. Your main page keeps the guide as you
installed it; each sub-app adds a small child snippet. The child has no button,
panel or card of its own and holds no key: it answers the guide on your main
page, which stays the only thing your visitor sees and talks to.
Off until you allow an origin. The guide never contacts an iframe unless an owner or admin added its exact origin under Dashboard → Install → Sub-apps in frames and the sub-app runs the child snippet. Frames from any other origin, and frames nested inside a sub-app, are ignored.
Release preview: sub-apps in frames are implemented and tested in the repository and reach the hosted scripts with their next release.
What works inside a sub-app
Where actions are off, the guide never offers to act in the sub-app: it points at the
control and tells the visitor to do it themselves. Answers, pointing and tours work
the same either way.
While no sub-app of your workspace has actions on (the default), the guide knows it
can’t act in any of them before it answers: it never mentions an action it would then
take back, and the visitor (or their screen reader) hears only that it pointed the
control out. Once you turn actions on for one sub-app, the guide may offer to act in
another one whose actions are off; that sub-app refuses, and the card switches to
the pointer at once.
A plan that fills several fields at once doesn’t run inside a frame yet: the guide
says so instead of filling part of it.
Set it up
-
Allow the sub-app’s origin. Open Dashboard → Install → Sub-apps in
frames and add the exact origin, like
https://billing.example.com:https, no path, on a domain you verified (or a subdomain of one). Up to 10. Adding one asks for your second factor, like adding a domain; removing one never does. If you remove that domain, or it stops being verified, the sub-app stops connecting at once. Every origin added or removed, and who did it, shows in Settings → Audit log under Workspace settings, like your other settings. -
Add the child snippet to the sub-app. Paste it into every page that loads
inside the frame:
data-parent-originlists the exact origins of the pages allowed to guide inside this sub-app (space separated): your main site. Without it the child stays silent.data-frame-nameis optional; it names the frame in your targets (see below). The child accepts the guide only with a pass Puntego signs for each connection (see Security), and it reads Puntego’s public keys to check it. If the sub-app sends a strict Content Security Policy, allowconnect-src https://worker.puntego.com(see CSP). From a bundler, use the typed package instead:stopPuntegoFrame()stops the child (for example when a component unmounts); callingstartPuntegoFrameagain starts it again. - Open your page. Once a visitor opens the guide, the sub-app connects and shows as Connected in Install → Sub-apps in frames, with the time and the child’s version.
- Decide whether the guide may act there. Each allowed sub-app has an Allow actions in this frame switch, off by default. Off, the guide reads, points and runs tours in the sub-app but never presses, types, chooses or submits anything there. Turn it on only for a sub-app whose embedding pages run scripts you trust (see Security). Turning it on asks for your second factor; turning it off never does, and both show in Settings → Audit log. Sub-apps you allowed before this switch existed start with actions off: if you relied on the guide acting in one, turn it on for that sub-app.
Name a control inside a frame
The guide names an element inside a frameframe:<name>:<id>, where <name> is
the frame’s data-frame-name and <id> is the element’s data-gp-id inside the
sub-app. To declare a sub-app control in Guides → Targets and actions, give it a
stable data-gp-id in the sub-app and use the selector
[data-gp-id="frame:billing:export-payouts"].
Security
- Both sides check each other. The guide on your page talks only to iframes
whose origin you allowed and opens each connection with a message posted to that
exact origin. The child accepts it only from its direct parent, only when the
parent’s origin is one it lists in
data-parent-origin. Neither side ever posts to"*". - A pass for every connection. Before the guide on your page connects to a frame, it asks Puntego for a pass that lasts a minute, signed for that frame, your page and that one connection. The child checks the signature with Puntego’s public keys, and each frame page accepts a pass once. A script on your page that just sends the frame its own connection request, first or after the guide left, gets nothing, and its attempt doesn’t stop the guide from connecting. No secret is in your pages or the sub-app’s: Puntego keeps the signing key.
- A private connection. That first message hands the child a private channel, and everything after it travels there, carrying the fresh code of that connection. Nothing goes through your page’s window, so other scripts on your page (another AI agent, a third-party tag) can’t read the frame’s content or the guide’s requests, or send requests of their own into the guide’s connection.
- The child never drives your page. It only answers and reports changes. Your page treats what it says as data about the frame, never as an instruction.
- Actions only where you allow them. Whether the guide may act inside a sub-app is part of the pass Puntego signs for each connection, from your Allow actions in this frame switch. Where it is off, the child refuses every press, typing, choice, submit and approval sent over that connection, whoever sent it, and the guide only points. Reading the frame for answers, pointing and tours keep working.
- The visitor approves every action on your page. The child acts only on a request your page first prepared with it and then confirmed after the visitor approved it on the guide’s card. A request it did not prepare, an expired or already used one, or one for a control that changed since (or on a page the sub-app has since moved away from), is refused. So is one whose control now pays, signs or deletes when it didn’t as the visitor was asked (the child checks again right before it acts). If the frame is hidden or covered on your page by the time the visitor approves, the guide doesn’t act.
- No secret crosses. No visitor token, key or session is ever sent to the frame, and no field value comes back from it. The pass is signed, names one connection and expires within a minute.
- One connection at a time. While the guide’s connection is open, the child refuses a second one. It ends when the visitor’s consent ends, when the guide is turned off, or when either page navigates.
- Frame it only from your site. Have the sub-app send
Content-Security-Policy: frame-ancestors https://www.example.comso no other site can frame it. The child trusts the pages you list indata-parent-origin. The pass proves that Puntego’s servers issued the connection to a visitor session on your page, not which script on your page asked for it. A script on those pages that starts a Puntego visitor session of its own (with your public app id), or that tampers with the page before the guide loads, can connect to the sub-app as the guide does. With actions on for that sub-app, such a script can ask it to act, without the visitor’s approval: turning actions on means trusting every script on the pages that embed it. With actions off, it can still read the sub-app’s page by the same privacy rules the guide uses and point at its controls, but it can’t make the sub-app press, type, choose or submit anything. That read access is the price of guiding inside the sub-app at all: the sub-app is your own, and what the child shares follows the privacy rules below (no field values, personal data replaced). List only pages you control and keep a strict CSP on them. - Older snippets. Child snippets from before the pass (version 1.0.x) accept
any connection from your page, so they don’t connect unless an owner or admin
turns on Let older sub-app snippets connect in Install → Sub-apps in frames
(it asks for your second factor, and shows in Settings → Audit log). They
can’t enforce Allow actions in this frame either: the guide never asks one to
act while actions are off for it, but another script on your page could. The
hosted
frame.jsis always current; update an older@puntego/embed/frameto version 1.2.0 or later, then turn the setting off.
Privacy
The child reads the frame’s page with exactly the rules of your main page: no field values, contact and password fields named by their label only, payment and one-time-code fields left out, anything underdata-puntego-ignore left out, and
personal data replaced by placeholders before it leaves the frame. It reads nothing
until the visitor’s consent lets the guide read the page, and stops when it ends.
On your main page, data-puntego-ignore on an iframe (or on anything around it)
keeps the guide away from that frame entirely: it doesn’t connect to it.
Page vision screenshots never include a cross-origin frame. The connected-frames
list stores only the origin, the child’s version, when it connected and whether it
was live or in test mode.