Short answer
One tag embeds a hosted SCORM module. Covers link vs iframe, HTTPS, CSP frame-ancestors and the build gotchas that blank your iframe.
No WordPress. No page builder. Just a folder of HTML you push to a static host, or a docs portal your team hand-maintains, and a SCORM module that needs to live on one of its pages. Good news: this is the easiest embed there is. You own every line of markup, Training OS runs the package, and the whole job is one tag. The traps are not in the HTML — they’re in the headers and the build step.
I’ve wired hosted modules into Jekyll sites, Hugo docs, a hand-rolled intranet, and one terrifying 2009-era SharePoint page. The snippet was identical every time. What broke was always the same short list: a Content-Security-Policy header, a markdown processor quietly escaping the iframe, or someone testing while logged in. Let’s skip those afternoons.
Get the launch URL working in a bare browser tab first. If it doesn’t play there, no amount of HTML will fix it.
/01Get the launch URL working first.
Before you write a single tag, paste your Training OS URL straight into the address bar and run the module. Debugging is dramatically faster when the player is known-good in isolation — you want to know the course works before you add your own page, your own headers, and your own build pipeline to the list of suspects. Training OS extracts the package and serves the runtime over HTTPS: the module plays on your public portfolio at /portfolio/<handle>, and Pro adds a direct embed URL at /embed/<courseId> and private share links. Your page’s only job is to send people to it.
If you’re still deciding where the package should live in the first place, start with where to host a SCORM file and come back once you have a URL that plays.
/02Two things to check first.
There are exactly two prerequisites, and skipping either is the cause of most “it works on my machine” tickets.
1. Serve the parent page over HTTPS.
If your Training OS launch URL is HTTPS (it is), the page embedding it must be HTTPS too. An HTTP page loading an HTTPS resource trips mixed-content protection — browsers block the script or the frame in predictable, silent ways. Most static hosts give you HTTPS for free; turn it on before you embed.
2. Know whether your deployment target injects CSP.
Plenty of internal portals and corporate hosts add a Content-Security-Policy header that restricts which origins an iframe may load. If yours does, an iframe to Training OS will render as nothing until someone allows the domain. On a public static host this is usually a non-issue; on an intranet, find out early — you may need IT to allowlist the Training OS origin, and that’s a request with a lead time.
/03The two snippets, ranked.
Once the URL plays in a bare tab, you have two ways to put it on the page. Ranked by how rarely they break:
Option 01
A direct link that opens in a new tab.
Best for: static sites, docs portals, anything behind a CSP you don’t control
The simplest integration is an anchor. Set target="_blank" so it opens in a dedicated window — the same way most LMSes launch content — and always pair it with rel="noopener noreferrer". The noopener half closes a tab-nabbing hole that any _blank link leaves open; noreferrer adds stricter referrer privacy. Write real CTA copy on the link, not a naked URL. On Training OS Free, point the link at your public portfolio (https://www.trainingos.com/portfolio/your-handle); the direct module URL below (/embed/<courseId>) is Pro.
- Works well
- No iframe height tuning and no CSP framing fight
- Full-window playback — how Storyline and Rise expect to run
- Survives markdown processors that strip raw HTML
- Watch out for
- Sends the learner to a new tab rather than inline
- Not the embedded-on-the-page feel a long article wants
Direct link · paste anywhere in your HTML
<a
href="https://www.trainingos.com/embed/your-course-id?creator=your-handle"
target="_blank"
rel="noopener noreferrer"
>Launch the module</a>Option 02
A standard iframe.
Best for: long-form docs pages where the module should sit inline
Use a normal <iframe> with src set to your launch URL and width="100%". Give it a fixed height tall enough for the player — usually 600–800px depending on chrome and aspect ratio. Add allow="fullscreen" when your authoring tool uses the fullscreen API, and a descriptive title for accessibility. Then test under the exact same domain policy you’ll ship to production — not just localhost. On Training OS, iframe the embed URL (/embed/<courseId>, a Pro feature): it is the page built to be framed on other sites.
- Works well
- Module feels like part of the page
- Good for documentation pages with an inline demo
- Watch out for
- CSP and X-Frame-Options headers love to blank it out
- Markdown processors escape raw iframe tags at build time
- Cramped on narrow mobile layouts if you under-size the height
iframe · paste into a raw-HTML block
<iframe
src="https://www.trainingos.com/embed/your-course-id?creator=your-handle"
width="100%" height="760"
allow="fullscreen"
title="Onboarding module — interactive"
></iframe>/04CSP and the blank-box problem.
When an iframe renders as an empty rectangle, it is almost never the module. It’s the page around it. Three culprits, in the order I actually hit them:
1. A frame-blocking header.
Open the console on the live page; the error tells you which side refused. A frame-src (or child-src / default-src) violation means your own page’s Content-Security-Policy doesn’t allow the player — add the Training OS origin to it. A frame-ancestors or legacy X-Frame-Options refusal comes from the page being framed: Training OS’s /embed/<courseId> route allows framing from any site, but its other pages (your portfolio included) only allow same-origin framing, so iframe the embed URL and link to the portfolio. If you can’t edit headers, use the link and move on.
2. The build step ate your iframe.
Static site generators run your content through a markdown processor that escapes or strips raw HTML by default. Your iframe looks perfect in the source file and is simply gone in the built page. Wrap it in whatever raw-HTML escape hatch your generator provides — an MDX block, a shortcode, a passthrough fence — so it survives the build.
3. Strict privacy settings break the session.
Test in Chrome and Safari with third-party cookies restricted. Some learners run hardened privacy settings that interfere with session state inside a cross-site iframe — playback starts, but resume or completion quietly fails. If your audience skews privacy-conscious, the link pattern is the safer default.
/05Before you commit and deploy.
Validate in staging, then run this once from a logged-out incognito window on production:
- The link or iframe loads the running module — not a download prompt or a blank box.
- The parent page is HTTPS, with no mixed-content warning in the console.
- If you used an iframe, your generator did not escape it — view source and confirm the raw tag survived.
- Completion, score, and resume behave the way the package was published to behave.
- It looks right on a phone — narrow layouts crush short iframes, so bump the height or fall back to the link.
That’s the whole job. For the CMS-flavored version of this, see embedding SCORM in WordPress; if completion is the thing misbehaving, the completion-status field is where to look next.
/06Frequently asked questions.
Do I need anything besides an anchor tag to launch a SCORM module?
Why is my embedded module a blank box on the live site but fine locally?
My static site generator keeps eating the iframe. What do I do?
Completion is not reporting inside the iframe. Is that a hosting bug?
See what Allure Training OS includes
About the author
Jonathan Borgwing
Jonathan Borgwing founded Allure Training OS. He writes about hosting SCORM, xAPI and sharing work clients can open.