Skip to content

/22Hosting SCORM · Tutorial

How to embed a SCORM course in plain HTML.

No CMS, no plugin — one tag, hosted playback, and the headers that actually break it.

Jonathan Borgwing · Founder, Allure Training OSPublished · Updated 9 min read

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.
— the rule that saves you a debugging session

/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

Recommended

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

If you’ve validated CSP

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?
No. If Training OS hosts the package, a single link with target=_blank and rel=noopener noreferrer launches it in a full window. That pattern dodges most cross-origin iframe headaches and matches how a real LMS opens content.
Why is my embedded module a blank box on the live site but fine locally?
Almost always one of three things: your page’s Content-Security-Policy doesn’t allow the player’s origin in frame-src; the page you’re framing refuses to be framed (a frame-ancestors or X-Frame-Options header — on Training OS, iframe the /embed/<courseId> URL, which allows any site, not your portfolio page, which doesn’t); or a markdown processor stripped the raw iframe at build time. Open the console, look for a blocked-frame error, and confirm the launch URL works on its own in a new tab.
My static site generator keeps eating the iframe. What do I do?
Markdown processors escape raw HTML by default. Put the iframe inside an explicit raw-HTML block (MDX, a shortcode, or your generator’s passthrough syntax) so it survives the build, or fall back to a plain link.
Completion is not reporting inside the iframe. Is that a hosting bug?
Usually not. Some packages assume a top-level window for completion and bookmarking. Test with strict third-party-cookie settings, and if tracking stays flaky, switch to the link pattern so the player runs as the top window.

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.

Put your SCORM somewhere people can play it.