What the manifest declares
Every SCORM package is a zip, and every SCORM zip has a file called imsmanifest.xml at its root. Not in a subfolder, not renamed, not optional. It is the first file the LMS opens, and for most of the import it is the only file the LMS reads — the HTML, JavaScript and media are just bytes it copies. So when an import fails with a message as unhelpful as "invalid package", the fault is almost always in this one file.
The manifest answers four questions, in this order:
- What specification is this? The
<metadata>block declares the schema and version, and the root element declares namespaces. This is how the LMS decides whether to run 1.2 rules or 2004 rules. - What is the course structure?
<organizations>holds a tree of<item>elements — the table of contents the learner sees. - What files exist, and which are launchable?
<resources>lists every resource, its entry-pointhref, and whether it is a SCO (talks to the runtime) or an asset (does not). - How do those connect? Each item points at a resource by identifier. That pointer is where imports break most often.
Here is a complete, valid, minimal SCORM 1.2 manifest for a single-SCO course. Nothing has been elided — a package containing this file plus content/index.html will import.
<?xml version="1.0" encoding="UTF-8"?>
<manifest identifier="MANIFEST-fire-safety" version="1.0"
xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd
http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd
http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd">
<metadata>
<schema>ADL SCORM</schema>
<schemaversion>1.2</schemaversion>
</metadata>
<organizations default="ORG-default">
<organization identifier="ORG-default">
<title>Fire Safety</title>
<item identifier="ITEM-1" identifierref="RES-1" isvisible="true">
<title>Module 1: Evacuation</title>
<adlcp:masteryscore>80</adlcp:masteryscore>
</item>
</organization>
</organizations>
<resources>
<resource identifier="RES-1" type="webcontent"
adlcp:scormtype="sco" href="content/index.html">
<file href="content/index.html"/>
<file href="content/scorm-api.js"/>
<file href="content/styles.css"/>
</resource>
</resources>
</manifest>
Organizations and items
<organizations> is a container that can hold more than one <organization> — alternative structurings of the same content — and its default attribute names the one the LMS will use. In practice almost every package has exactly one organization, and the default attribute must still match its identifier exactly. A mismatch here produces an import that succeeds and then shows an empty course.
Inside an organization, <item> elements form the course tree. An item is either a leaf that points at a resource with identifierref, or a container with child items and no identifierref of its own. That is how you get a multi-module course:
<item identifier="ITEM-mod1"> <!-- container: no identifierref -->
<title>Module 1</title>
<item identifier="ITEM-mod1-a" identifierref="RES-a">
<title>Evacuation routes</title>
</item>
<item identifier="ITEM-mod1-b" identifierref="RES-b">
<title>Assembly points</title>
</item>
</item>
Two rules that catch people out. Every <item> must have a <title> — it is required, not decorative, and a missing title fails schema validation. And isvisible="false" hides an item from the learner's menu but does not exclude it from the course; strict LMSs still track it, which is a common source of "why is this course 90% complete forever".
In SCORM 1.2, <adlcp:masteryscore> on an item sets the pass mark. Be careful with it: when it is present, many platforms recompute cmi.core.lesson_status from the score the course reported and overwrite the status the course set. If your course decides pass/fail itself, leaving mastery score out is usually safer. SCORM 2004 replaces it with <imsss:minNormalizedMeasure> inside the sequencing block, which is a different mechanism with different rules — see the sequencing reference.
Resources and hrefs
The <resources> section is a flat list. Each <resource> carries four things that matter:
| Attribute | Value | What it does |
|---|---|---|
identifier | Any unique string | The target of an item's identifierref. Must be unique across the whole manifest. |
type | Always webcontent | Fixed by the specification for web content. Other values are not used in practice. |
adlcp:scormtype | sco or asset | Whether this resource talks to the runtime API. A launchable, tracked unit is a sco; a PDF or image referenced by one is an asset. In 2004 the attribute is adlcp:scormType — note the capital T. |
href | Relative path | The entry point the LMS launches. Required on a SCO. Relative to the manifest, or to xml:base if one is set. |
Each <file href="…"/> child declares one file that belongs to the resource. Strict platforms verify that every declared file exists in the zip and refuse the import when one is missing; forgiving platforms ignore the list entirely and just serve whatever is there. This asymmetry is why a package can import cleanly on one LMS and fail on another with no change to the content — the second one is actually reading the manifest.
The identifier chain is the part worth internalising, because four of the five errors below are breaks in it:
The schema and version namespaces
An LMS decides which rule set to apply from the manifest, not from the file name or the content. Two places declare it, and they must agree.
| SCORM 1.2 | SCORM 2004 (3rd/4th Ed.) | |
|---|---|---|
| Default namespace | http://www.imsproject.org/xsd/imscp_rootv1p1p2 | http://www.imsglobal.org/xsd/imscp_v1p1 |
| ADL namespace | xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2" | xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_v1p3" |
| Extra namespaces | — | adlseq, adlnav, imsss for sequencing and navigation |
<schemaversion> | 1.2 | 2004 3rd Edition or 2004 4th Edition |
| SCO type attribute | adlcp:scormtype (lower case) | adlcp:scormType (capital T) |
| Pass mark | <adlcp:masteryscore>, 0–100 | <imsss:minNormalizedMeasure>, 0–1 |
The <schemaversion> string is matched literally by many platforms. 2004 4th Edition is accepted; 2004 4th ed., 4th Edition and 2004v4 are not. Getting this wrong is the classic cause of a 2004 package being imported as 1.2, which then silently discards cmi.success_status because 1.2 has no such element.
The five most common manifest errors
- The manifest is not at the zip root. Zipping the folder rather than its contents puts
imsmanifest.xmlone level down atcourse/imsmanifest.xml, where the LMS will not find it. On Windows, "Send to → Compressed folder" on a selected folder does exactly this. Fix: open the folder, select all the files inside, and zip the selection. identifierrefdoes not match any resourceidentifier. Usually a typo or a case difference (RES-1vsres-1); identifiers are case-sensitive. Symptom: the course imports and the item is unlaunchable, or the whole import is rejected on stricter platforms.- The
hrefpoints at a file that is not in the zip, or is spelled with different case than the actual file. Linux-hosted LMSs are case-sensitive where an author's Windows machine was not, soContent/Index.htmlworks locally and 404s in production. Symptom: the course launches to a blank frame. - A declared
<file>is missing. Strict platforms verify every declared file. Symptom: import rejected with a message naming the file — one of the few genuinely useful SCORM error messages. organizations defaultdoes not match the organization'sidentifier, or<organizations>is empty. Symptom: an import that reports success and produces a course with no content.
Notice what these have in common: four of the five are string mismatches, not conceptual mistakes. They are exactly the class of error that a validator catches in a second and a human misses for an afternoon.
Validating before upload
You can check most of this yourself before an LMS ever sees the package. In order of effort:
- Confirm the location.
unzip -l package.zip | head—imsmanifest.xmlmust appear with no directory prefix. - Check it is well-formed XML.
xmllint --noout imsmanifest.xmlreports a line number for any syntax error. This catches unescaped ampersands in titles, which authoring tools produce more often than you would hope. - Validate against the schema. If the
.xsdfiles are in the package,xmllint --schema imscp_rootv1p1p2.xsd imsmanifest.xml --nooutchecks structure — required elements, attribute values, ordering. - Resolve the identifier chain by hand or by script. Every
identifierrefmust appear as a resourceidentifier; every SCOhrefand every declared<file>must exist in the zip at that exact path and case. A dozen lines of Python overzipfileandxml.etreedoes it, and is worth having in your build. - Then test the runtime. A structurally perfect manifest tells you the package will import; it says nothing about whether the course reports completion. That is a separate question, covered in testing a package before upload, and when a package imports but misbehaves afterwards, work through the diagnostic checklist.
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.