Start with the symptom
"The SCORM package is not working in the LMS" describes four completely different failures with four unrelated causes, and the fastest way to waste a day is to start rebuilding the course before knowing which one you have. A SCORM package passes through four gates, in order. It gets imported, it gets launched, it talks to the runtime, and what it says gets persisted. Each gate fails in its own way and produces its own symptom.
Everything below is organised by gate. Find your symptom, apply the checks in order, and stop when one of them reproduces the problem.
It will not import
The LMS rejects the zip, or accepts it and shows an empty course. The content is irrelevant here — the LMS has not looked at it. This is a manifest problem in almost every case.
| What you see | Likely cause | Fix |
|---|---|---|
| "Invalid package", "manifest not found" | imsmanifest.xml is not at the zip root — the folder was zipped instead of its contents | unzip -l package.zip | head. If the manifest shows a directory prefix, re-zip from inside the folder. |
| Import fails naming a file | A <file href> declared in the manifest is missing from the zip | Add the file or remove the declaration. Strict platforms verify all of them. |
| XML parse error, often with a line number | Malformed manifest — usually an unescaped & in a title | xmllint --noout imsmanifest.xml, then escape as &. |
| Imports, but the course is empty | organizations default does not match the organization identifier, or the organization has no items | Compare the two strings exactly; they are case-sensitive. |
| Imports, but an item will not open | identifierref matches no resource identifier | Resolve the chain — every reference must hit a real resource. |
| Rejected as the wrong version | <schemaversion> text or namespaces do not match the declared edition | Use the exact strings: 1.2, 2004 3rd Edition, 2004 4th Edition. |
| Upload fails before import starts | The zip exceeds the platform's upload limit | A server limit, not a SCORM one. Compress media or host large video externally. |
All of these are checkable in about two minutes without an LMS. The full anatomy, including the identifier chain and the namespace table, is in imsmanifest.xml explained.
It imports but will not launch
The course appears in the catalogue, the learner clicks it, and gets a blank frame, a broken-image page, or a window that opens and closes. The LMS did its job; the content is not loading.
- Check the
hrefresolves. Open the browser's network tab and launch. A 404 on the entry point means the resourcehrefdoes not match the real file path. The usual culprit is letter case: authoring on Windows (case-insensitive) and hosting on Linux (case-sensitive) letsContent/Index.htmlwork locally and fail in production. - Check for mixed content. If the LMS is HTTPS and the course loads a script, font or video over HTTP, the browser blocks it silently and the page renders blank or unstyled. The console shows "Mixed Content" warnings. Fix the URLs to HTTPS or bundle the asset.
- Check the launch mode. A course built to run in a new window can break inside an iframe, and vice versa, because the API-finding code walks a window hierarchy that is not the one it expected. If the LMS offers a choice — new window vs. current — try the other.
- Check the popup blocker. Platforms that launch in a new window lose to popup blockers, which is invisible to the learner apart from "nothing happened".
- Check for a missing entry file. A resource with no
href, or anhrefpointing at a folder rather than a file, produces a directory listing or a blank page.
It launches but tracks nothing
The course runs perfectly for the learner. The LMS shows "not attempted" or "in progress" forever, with no score. This is the most common report, and it has one dominant cause: the content never established a session with the runtime.
Confirm it in the console before doing anything else. On the launched course, in the frame the course runs in:
// SCORM 1.2 — walk up looking for the API object
var w = window, api = null, n = 0;
while (w && n++ < 10) { if (w.API) { api = w.API; break; } w = w.parent === w ? null : w.parent; }
console.log('1.2 API found:', !!api);
// SCORM 2004 uses a different object name
var w2 = window, api2 = null, m = 0;
while (w2 && m++ < 10) { if (w2.API_1484_11) { api2 = w2.API_1484_11; break; } w2 = w2.parent === w2 ? null : w2.parent; }
console.log('2004 API found:', !!api2);
Both false means the content cannot see the API and nothing it does will ever be recorded. Causes, in order of frequency:
- Version mismatch. The course looks for
API(1.2) and the LMS launched it as 2004, providingAPI_1484_11— or the reverse. This is the same root cause as the namespace error in the manifest section: the package says one version and the content speaks another. - The API is above an opener, not a parent. Content launched in a new window must also search
window.openerand its parents. Older hand-written API-finder code often does not. - Cross-origin frames. If the LMS serves the course from a different domain than the player, the walk up the window hierarchy throws a security error and stops. The console shows a cross-origin message. This is a hosting configuration problem, not a package problem.
- A race at launch. The content calls
LMSInitializebefore the LMS has finished placing the API object. Symptom: error301(not initialized) on subsequent calls in 1.2, or102/122in 2004.
If the API is found and calls still fail, read the error code rather than guessing — LMSGetLastError() after a failed call, or GetLastError() in 2004. Code 401 means the element is not implemented on that platform; 403/404 mean you are writing a read-only or reading a write-only element; 405 means the value is the wrong type or outside its vocabulary. The full code list for each version is in the 1.2 data model reference and the 2004 data model reference.
It tracks but loses completion
The hardest one, because it works in testing and fails for some learners. The runtime log looks healthy, values are set, and the record is still wrong afterwards. Four mechanisms account for nearly all of it:
- The final commit never landed. The course sets completion and relies on the browser's unload event to commit. Unload handlers are unreliable, fire late, and are cancelled outright on mobile. Test by closing the tab abruptly right after finishing — not with the course's own exit button — then reopening.
- Suspend data was truncated. The state string exceeded the cap (4,096 characters in 1.2; 4,000 in 2004 2nd Edition; 64,000 in 3rd/4th), the platform truncated it silently, and on resume the course could not parse its own state and started over. Details and how to measure it: suspend data limits.
- Status was written before score. Some platforms lock the attempt record the moment they first see
completedorpassedand ignore later writes — recording a completion with an empty score. Write score first, then success, then completion, then commit. - The exit value ended the attempt. The course must set
cmi.core.exittosuspend(1.2) orcmi.exittosuspend(2004) before finishing if the attempt is meant to resume. Left empty, several platforms close the attempt and discard the suspend data, even though it was written correctly and was under the cap.
A fifth, less common: a masteryscore in the manifest causing the LMS to recompute pass/fail from the score and overwrite the status the course set. The full catalogue with the fix for each is in why SCORM completions go missing.
When the package is fine and the platform is not
If the package imports, launches, tracks, and persists on one LMS and fails on another, the package is probably not the problem. The specification leaves several behaviours open, and platforms take different positions on them: how often Commit may be called before it is throttled, whether an over-long suspend string is truncated or rejected, what a status set before a score records, whether interactions are stored or accepted and dropped, and what an empty exit value does to an attempt.
Two practical consequences. First, "it worked in our LMS" is weak evidence about a customer's LMS — the differences that matter are exactly the ones no conformance badge covers. Second, when you are shipping to platforms you do not control, the useful test is not "does it conform" but "does it survive the behaviours the destination actually has". That is what replaying a finished session against saved runtime behaviour profiles is for, and it is the difference between passing a spec check and passing a customer's environment. If you are also moving content between platforms, keeping SCORM working through an LMS migration covers what breaks in transit.
Where SCORM Central fits
SCORM Central converts files you already have — PDF, PowerPoint, Word, or video — into SCORM 1.2, SCORM 2004, cmi5, or xAPI, and shows you the manifest, the full runtime log, and a conformance report on every build, so you can see exactly what a package will report before an LMS does. See how it works, or start with one file.