Overview
The Embed code brick can show a folder you upload: an index.html plus its assets, running inside the lesson. By default that package is a closed box. If it also ships a small manifest and talks to the lesson with a few messages, it can:
- tell the lesson that the learner has finished (so the lesson can wait for it);
- report its height, so the frame fits its content;
- hand over its progress, so the learner finds it where they left it;
- report a score and each decision, which turns it into a simulation that counts in the grade, in SCORM and in xAPI;
- expose variables that conditional content can read.
This page is for whoever builds the package. You can follow it without reading any Creator code. For the author's side (the brick's panel, scoring and warnings) see Content Bricks.
Everything is opt-in. A package without a manifest still works: it is shown, counts as completed when seen and never holds the learner back.
How the package runs
The package runs in a sandboxed frame with scripts allowed but no access to the app: no cookies, no storage of the app and no shared origin. The lesson only listens to messages that come from the frame of that very brick, so nothing else on the page can pretend to be your package. A message that is not valid is ignored silently.
An embed that points to an external URL instead of an uploaded folder has no package: its messages are always ignored and it never declares anything.
The manifest: slxd-embed.json
Put a file named slxd-embed.json at the root of the folder, next to index.html:
{
"protocol": "slxd-embed",
"version": 1,
"capabilities": ["complete", "resize", "state", "result", "interaction"],
"variables": { "trust": "number", "reused": "boolean" }
}
| Field | Meaning |
|---|---|
protocol | Always "slxd-embed". |
version | Always 1; it is the only version understood. |
capabilities | What the package will send. Each behavior only works if its capability is declared. Anything unknown is ignored. Up to 32 entries. |
variables | Optional. The variables of state.vars that the package sends, with their type (number or boolean). Up to 64. Conditional content can only name these. |
Without the file, with invalid JSON, another version or more than 16 KB, the package simply declares nothing.
The manifest is read when you upload the folder to the brick. Upload slxd-embed.json together with the rest of the folder. If you change the manifest, upload the folder again: a package uploaded earlier keeps what it declared at that time.
| Capability | What it enables |
|---|---|
complete | The package can say "I'm done". Needed for the brick's blocker to wait for the package. |
resize | The package can set the height of its frame. |
state | The lesson saves the package's progress and hands it back when the learner returns. |
result | The package can report a score. |
interaction | The package can report each decision (SCORM interactions and xAPI). |
Reserved variable names
grade, score, maxScore, ending and kind always read the package's last result (see below), so they cannot also be variables of your state. A package whose variables declares any of them is not accepted when you upload it: the editor shows the error and the folder is not used until you rename them. Inside state.vars, a key with a reserved name is simply ignored.
Variable names start with a letter or _, continue with letters, digits or _, and are at most 64 characters. An entry with another name or type is ignored without rejecting the rest.
Messages
Every message is a plain object sent with postMessage, with the same envelope:
const send = (type, data = {}) =>
window.parent.postMessage({ channel: "slxd-embed", version: 1, type, ...data }, "*");
The target origin is "*" because the frame cannot know the lesson's origin; nothing sensitive travels in these messages.
From the package to the lesson
type | Fields | Needs | What Creator does |
|---|---|---|---|
ready | — | — | Say it as soon as the package has loaded. If the package declares state, the lesson answers with restore. |
complete | — | complete | Marks the brick as completed and releases its blocker. In a simulation it also closes it (see below). |
resize | height: number ≥ 0, in CSS pixels | resize | The frame takes that height, kept between 100 and 4000 px and rounded up. Until the first message, the brick's configured height applies. |
state | state: a JSON object; done: boolean | state | Saves state exactly as sent. done: true means it is an ending: it closes the simulation and completes the brick. Send it after every decision and when an ending is reached. |
result | score, maxScore, grade, ending, kind | result | Only updates the score. It can arrive partial and the last one wins. It does not complete the brick nor close the simulation. |
interaction | attempt, n, response, description, result, weighting | interaction | Registers one decision. Send it after every decision. |
A message that does not fit these shapes is ignored whole, without any warning. Check each field:
state.state: a plain JSON object (not an array or a single value) of at most 1024 characters once serialized.state.doneis required. Creator treats the state as opaque, except for one optional member,vars, an object of numbers and booleans that conditional content reads.result.score: any finite number (it may be negative).maxScore: a finite number ≥ 0.grade: a number from 0 to 100 that your package computes; it is the only value that counts for the grade.ending: an identifier.kind:good,mixedorbad.interaction.attemptandn(the decision's number inside the attempt): integers from 1 to 9999.response: the identifier of the chosen option.description: text of up to 250 characters, required (an empty string is fine).result:correct,incorrectorneutral.weighting: a number greater than 0 (send 1 if the decision was worth 0).- Identifiers (
ending,response): lowercase letters, digits and_, starting with a letter, up to 64 characters.
From the lesson to the package
type | Fields | When |
|---|---|---|
restore | state: the last saved state, or null | In answer to ready, if the package declares state. It is null if nothing was saved. |
reset | — | When the exam opens a new attempt. Start over: the saved state and score are already cleared. |
A minimal package
const send = (type, data = {}) =>
window.parent.postMessage({ channel: "slxd-embed", version: 1, type, ...data }, "*");
let scene = "s1";
let vars = { trust: 0, reused: false };
window.addEventListener("message", (event) => {
const msg = event.data;
if (!msg || msg.channel !== "slxd-embed") return;
if (msg.type === "restore" && msg.state) {
scene = msg.state.scene;
vars = msg.state.vars;
render();
}
if (msg.type === "reset") {
scene = "s1";
vars = { trust: 0, reused: false };
render();
}
});
function choose(option) {
vars.trust += option.points;
scene = option.next;
send("interaction", {
attempt: 1,
n: 1,
response: option.id,
description: option.label,
result: option.points > 0 ? "correct" : "incorrect",
weighting: Math.abs(option.points) || 1,
});
send("state", { state: { scene, vars }, done: false });
render();
}
send("ready");
send("resize", { height: document.body.scrollHeight });
Finishing: result before complete
A simulation is closed by complete, or by a state with done: true. A result never closes it. The score that counts is the one from the last result received before the closing; once closed, later results are ignored. If the package closes without having sent any result, the simulation is worth 0.
So the right order at an ending is:
send("result", { score: 8, maxScore: 10, grade: 80, ending: "agreement", kind: "good" });
send("state", { state: { scene: "end", vars }, done: true }); // or: send("complete")
A state without done after an ending is a new game: the previous score is forgotten.
A package that will be marked Scored must therefore declare complete or state, or its score could never be closed.
What Creator does with each thing
Blocking the advance. The Embed code brick does not hold the learner back by default. If the author turns the blocker on (in the brick or in the theme's embedded-code settings), it only works for a package that declares complete: the learner cannot continue until the message arrives, with no timeout and no unlock button. state and result do not release it. A package without complete, and every external URL, counts as completed when seen. The editor warns when the blocker is on but the package cannot honor it.
Height. The frame follows the last resize (between 100 and 4000 px). Until the first one, it uses the height configured in the brick.
State when coming back. The simulation's state is stored under its simulation identifier, per content. When the package loads and says ready, it receives restore with the last state. In a SCORM export the state survives between sessions (it travels in the LMS's suspend data); in the preview, share links and review it lasts for the session. If the state does not fit in the LMS's room, Creator trims what it must, in a fixed order: first the simulations that neither count nor are read by conditional content, then the state of the others, keeping their variables and scores as long as possible.
Score. For the simulation to count, the author gives the brick a simulation identifier (unique in the content) and ticks Scored with a weight; the package must declare result, plus complete or state. Inside an exam, the simulation counts next to the questions: each simulation is worth its weight times grade / 100, and the exam does not close until every counted simulation is closed. In a content without exams, the content's score is the weighted average of its scored simulations once all have finished, against the content's pass threshold (shown on the export page). Scored simulations next to graded exams do not count; the editor warns.
Notes in SCORM. In a SCORM export, every interaction becomes an entry of cmi.interactions of type choice, with the id <simulation identifier>-<attempt>-<n>, the chosen option, the result (correct, wrong/incorrect or neutral) and the weighting; SCORM 2004 also stores the description. In the preview there is no LMS, so nothing is recorded.
xAPI. Where xAPI is active, each interaction is reported as answered on a decision activity (with the description, the chosen option and success for correct and incorrect; neutral carries none), and when the simulation closes a single scored statement carries the score that counts, with the ending and the kind as extensions. A partial result emits nothing. Starting a new game allows the statement again. The author can turn xAPI off for the brick.
Limits at a glance
| What | Limit |
|---|---|
slxd-embed.json | 16 KB, version 1 only |
| Capabilities | up to 32 entries |
| Variables | up to 64, number or boolean |
state | JSON object, ≤ 1024 characters |
| Frame height | 100–4000 px |
| Decision description | ≤ 250 characters |
attempt, n | integers 1–9999 |
| Identifiers | [a-z][a-z0-9_]*, ≤ 64 characters |
| Saved progress (SCORM) | shared 4096-character budget for the whole content |
Next steps
- Conditional content: show or hide bricks depending on what the learner did in a simulation.
- Content Bricks: the Embed code brick's panel.
- xAPI Tracking and Exporting Content.
- MCP Server: how an assistant uploads a package and writes the brick.