Source:
docs/adr/0016-embed-h5p-via-h5p-standalone-for-long-tail-content.md
0016 — Embed H5P content via h5p-standalone for long-tail interaction types
Context and Problem Statement
Research §1.2 documents ~55 H5P content types under a mix of MIT (core) and GPL (some types). The framework's native component library covers the top 15 patterns (research §1.4) and the dominant interactions for technical training, but reimplementing the long tail — Virtual Tour 360, Branching Scenario, Dictation, Speak the Words, Memory Game, Crossword, Find the Hotspot, Image Juxtaposition, Dialog Cards, Documentation Tool, Agamotto, Personality Quiz, Arithmetic Quiz, Essay, Questionnaire, etc. — is a multi-year effort with minimal differentiating value. H5P already ships all of them.
Decision Drivers
- Breadth without reimplementation. Covering ~55 content types natively is ~55 components to design, test, and maintain.
- H5P author tooling maturity. Instructional designers often already have
.h5ppackages from Drupal / Moodle / WordPress workflows; they should be usable as-is. - Interop with existing H5P xAPI events. H5P content types emit xAPI via H5P's
EventDispatcher; those events should route into our Tracker transparently. - License. The chosen embedding library must be MIT-compatible with our MIT core (ADR 0014).
- Print fallback. H5P content renders in an iframe; print / PDF must degrade gracefully.
Considered Options
- A:
<H5P src="content.h5p" />component wrappingh5p-standalone(tunapanda, MIT). - B: Reimplement each needed H5P content type as a native MDX component.
- C: Host H5P via Drupal / Moodle H5P plugin and iframe-embed from there.
- D: Do not support H5P at all.
Decision Outcome
Chosen option: A — Ship a <H5P src="path/to/content.h5p" /> MDX component that wraps h5p-standalone (MIT) to render any .h5p package inside a lesson,
forwarding H5P xAPI events into the framework's Tracker (ADR 0004).
Component contract
<H5P src="/h5p/virtual-tour-intro.h5p"
options={{ frameJs: "/h5p/dist/frame.bundle.js" }}
trackAs="activity-slug" />
src— path to a.h5pzip bundled underpublic/h5p/.options— passthrough toh5p-standalone(frame JS path, CSS, i18n).trackAs— the activity ID used when forwarding H5P xAPI events to the Tracker. Derived from the lesson slug + content name if omitted.
xAPI event forwarding
h5p-standalone exposes an event subscription API. The <H5P> component subscribes to xAPI events from every contained content type and calls
tracker.recordInteraction() / tracker.setScore() / tracker.complete() on our Tracker, translating H5P's statement shape to the framework's
Interaction shape. Credentials stay server-side; H5P statements route through the same /xapi proxy (ADR 0013).
License handling
h5p-standaloneis MIT — compatible with ADR 0014.- Individual H5P content types may be GPL — but authors embed their own
.h5ppackages, so the GPL content lives in customer assets, not in our core shipped bundle. We document this to customers.
Print fallback
Per ADR 0011, every interactive component provides a print snapshot. The <H5P> component's print mode renders:
- A static screenshot (generated at build time via Playwright against the
h5p-standalonerendered output). - A QR code to the live URL.
- A brief text description extracted from the
h5p.jsonmetadata.
SCORM / cmi5 packaging
The .h5p files are static assets — they travel inside the output zip under /h5p/ and load from the package's own origin (no external calls). This works
because H5P content inside h5p-standalone is self-contained after extraction.
Security
- H5P content runs inside an iframe with
sandbox="allow-scripts"(noallow-same-origin) — isolates H5P content JS from the host page. - The
h5p-standaloneruntime is vendored and pinned; upgrades are tracked in the changelog because some H5P content types have had historical XSS reports. - All
.h5pfiles shipped with a course are content-hashed and validated at build time (no untrusted uploads at runtime in the default tier).
Consequences
- Functionality, good: the framework claims breadth equal to H5P's ~55 content types without reimplementing them.
- Functionality, good: instructional designers with existing
.h5plibraries are unblocked on day one. - Portability, good:
.h5pis a zip format — fits naturally inside our SCORM/cmi5 package outputs. - Performance, mixed: every
<H5P>instance ships theh5p-standaloneruntime + the content type JS. A lesson with five different H5P types ships five content-type runtimes. We document this cost; courses heavy on H5P pay for it. - Security, good: iframe sandboxing without
allow-same-originisolates content types. - Security, note: GPL content types ship inside customer zips; customers redistributing modified versions inherit GPL obligations — documented.
- Clarity, good: one component, one
srcattribute, onetrackAsfor xAPI routing. - Testability, good: the xAPI forwarding is unit-testable against a stubbed
h5p-standaloneevent stream.
Pros and Cons of the Options
A — h5p-standalone embed — chosen
- Good: delivers H5P-level breadth without building it.
- Good: authors use existing
.h5pfiles; no new toolchain. - Good: MIT-licensed; compatible with core.
- Bad: iframe sandboxing adds layout complexity; H5P's own responsive behavior must be respected.
B — Reimplement each H5P type
- Bad: multi-year work for low-ROI content types.
- Bad: duplicates well-tested H5P implementations.
C — Drupal / Moodle H5P iframe from external host
- Bad: external dependency; breaks offline SCORM.
- Bad: introduces cross-origin concerns and credentials for a separate service.
D — No H5P support
- Bad: cuts off a large library of existing instructional content and existing authoring workflows.
Validation
- Round-trip xAPI test: an H5P "Multiple Choice" content emits a correct-answer xAPI event; the
<H5P>wrapper forwards it;tracker.recordInteractionis called with the correct shape; the LRS receives one statement. - Packaging test: a course with two H5P embeds builds a SCORM 1.2 zip; launching in SCORM Cloud loads both H5P instances without external network calls.
- Iframe isolation test: a deliberately hostile
.h5pcontent type cannot accesswindow.parentfrom inside the H5P iframe (Playwright test). - Print fallback test: PDF export of a lesson with an H5P embed produces a labeled screenshot + QR code, not a blank area.
More Information
- Research §1.2 "H5P is the reference".
- Research §6.10 "Long-tail content types via H5P embed".
h5p-standalone: https://github.com/tunapanda/h5p-standalone.- H5P core / content types: https://h5p.org/ and https://github.com/h5p/.
- Related ADRs: 0004 (Tracker), 0011 (print fallback), 0013 (xAPI proxy), 0015 (packaging pipeline).
- Open question: do we ship a curated "blessed H5P content-type list" tested in our pipeline, or leave the full ~55 to customers? Recommend the former for Phase 3 — covers Branching Scenario, Interactive Video, Memory Game, Crossword, Documentation Tool.