To publish a SCORM course to Moodle: export from your authoring tool as SCORM 1.2 (Moodle core is conformance-tested against SCORM 1.2 and does not support SCORM 2004 without a third-party plugin), then zip the course files so that imsmanifest.xml sits at the very root of the archive — never zip the parent folder. Add a SCORM package activity, upload the ZIP without unzipping it, then set Grading method, Number of attempts, Force new attempt, Auto-commit and the Require status completion boxes to match what your package actually reports. Finally, take one full attempt as a genuine test student and confirm the status stored in Reports before you release the course to anyone.
Key takeaways
- Publish in SCORM 1.2 unless you have a specific reason not to. Moodle core passes the ADL conformance test suite for 1.2, while SCORM 2004 development in core was abandoned and is only available through third-party plugins such as Rustici's SCORM Cloud connector.
- The most common upload failure has nothing to do with SCORM. It is a ZIP built by right-clicking the course folder instead of its contents, which buries imsmanifest.xml one level down and produces the error Incorrect file package - missing imsmanifest.xml or AICC structure.
- Do not unzip the package before uploading. Moodle's own documentation is explicit about this: you upload the ZIP itself and Moodle extracts it into the activity's file area.
- Grading method and Attempts grading are two different settings answering two different questions. Grading method decides how one attempt is scored, Attempts grading decides which attempt reaches the gradebook. Getting them backwards is why gradebook numbers look wrong.
- Force new attempt set to Always is the single most damaging default change you can make to a resumable course. It throws away suspend data on every launch, so learners restart from slide one every time.
- Require status in activity completion maps directly onto the value the package writes to cmi.core.lesson_status. Tick both Passed and Completed unless you know exactly which one your package sends, otherwise a perfectly good course never completes.
- Test with a real student account, not the teacher preview. Preview mode reports the status browsed, which Moodle will never accept as completion, and it is the reason half of all pre-launch tests give a false negative.
Uploading a SCORM course to Moodle is one of those tasks that is either completely uneventful or completely maddening, with very little in between. It works on the first try, or you spend an afternoon staring at Incorrect file package - missing imsmanifest.xml or AICC structure while the vendor insists the package is fine.
The reason it goes wrong so often is that three separate systems have to agree, and each was designed by different people at different times: your authoring tool decides what to write into the package, the SCORM specification decides what a valid package looks like, and Moodle decides which parts of the specification it will honour. Most guides on this topic document the middle click-path and skip the two ends. That is where the failures actually live.
This walkthrough covers the whole chain, in the order you will hit it: choosing the version you publish in, checking that the package is structurally valid, zipping it so Moodle will accept it, uploading it, configuring the handful of settings that genuinely change behaviour, and proving completion works before a single learner sees it. We run Moodle for clients and build SCORM content, so everything below is what we actually do on handover days, not a paraphrase of the docs.
Step zero: decide which SCORM version you publish in
This decision is made in your authoring tool before you export anything, and it constrains everything downstream. For Moodle specifically the answer is less open than the internet suggests.
Moodle core supports SCORM 1.2. MoodleDocs states it passes the ADL Conformance test suite for SCORM 1.2. For SCORM 2004, the documentation is unusually blunt: development stopped, and bugs raised against SCORM 2004 behaviour are closed as will-not-fix. If you need genuine SCORM 2004 support, including its sequencing and navigation rules, you need a third-party route such as Rustici's SCORM Cloud plugin. AICC packages are supported natively from Moodle 2.1 onwards.
| Publish as | On stock Moodle | Choose it when |
|---|---|---|
| SCORM 1.2 | Fully supported and conformance-tested | Default answer. Single-SCO courses, compliance modules, anything that has to just work. |
| SCORM 2004 3rd / 4th Ed. | Not supported in core; needs a plugin | You genuinely need separated completion and success status, or sequencing rules, and you control the plugin stack. |
| AICC | Supported since Moodle 2.1 | Legacy content you cannot re-author. Not a choice for anything new. |
| xAPI / cmi5 | Plugin-based (logstore xAPI, mod_tincanlaunch) | You need statements from outside the course, and you have an LRS to send them to. |
The historical context helps here. SCORM 1.2 shipped in October 2001 and Rustici still describe it as the industry workhorse. SCORM 2004 arrived in four editions between 2004 and 2009, and its main addition — Sequencing and Navigation — is precisely the part Moodle core never implemented. So publishing 2004 into stock Moodle buys you the complexity without the capability.
The one real cost of choosing 1.2 is that it has a single status field, cmi.core.lesson_status, doing the job of both completion and success, and a 4,096-character cap on suspend_data. If your course carries a lot of resume state, that cap matters. We covered what happens when these constraints bite in why your SCORM course shows incomplete in Moodle.
What a valid package actually contains
When SCORM content is put into a ZIP, the specification calls it a Package Interchange File, or PIF. Rustici's wording on the manifest is unambiguous: the manifest file must always exist at the root of the content. Everything else in the archive is described by that one file.
Inside imsmanifest.xml there are three parts that matter:
- metadata — declares which schema and version the package claims to conform to. This is what tells Moodle it is looking at SCORM 1.2.
- organizations — the hierarchy of
<item>elements. This becomes the table of contents Moodle renders, and each item points at a resource viaidentifierref. - resources — the actual parts of the course. Each
<resource>carries anhreffor its launch page, a list of<file>elements, and anadlcp:scormtypeof eitherscoorasset.
That last attribute is the one people misread. A SCO can talk to the LMS; an asset is static content that cannot. If a page that is supposed to report progress is declared as an asset, it will render perfectly and track nothing at all.
A minimal, valid SCORM 1.2 manifest looks like this. If you have never opened one, this is worth five minutes:
<?xml version="1.0" encoding="UTF-8"?>
<manifest identifier="MANIFEST-fire-safety-2026"
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">
<metadata>
<schema>ADL SCORM</schema>
<schemaversion>1.2</schemaversion>
</metadata>
<organizations default="ORG-fire-safety">
<organization identifier="ORG-fire-safety">
<title>Fire Safety Awareness 2026</title>
<item identifier="ITEM-01" identifierref="RES-01" isvisible="true">
<title>Fire Safety Awareness</title>
<adlcp:masteryscore>80</adlcp:masteryscore>
</item>
</organization>
</organizations>
<resources>
<resource identifier="RES-01" type="webcontent"
adlcp:scormtype="sco" href="index_lms.html">
<file href="index_lms.html"/>
<file href="scormdriver/indexAPI.js"/>
</resource>
</resources>
</manifest>
Two details in there cause real-world grief. First, identifiers must begin with a letter and cannot contain spaces or special characters — hyphens and underscores are fine. Second, that <adlcp:masteryscore> element hands Moodle permission to overwrite your status: with Mastery score overrides status enabled, a raw score below the mastery value rewrites the status to failed when LMSFinish is called, regardless of what the course reported.
Zipping it so Moodle will accept it
This is where more packages die than anywhere else, and the fix takes ten seconds once you know it.
The rule: select the contents of the course folder and compress the selection. Do not select the folder and compress that. Compressing the folder produces an archive whose root contains one directory, and the manifest is therefore one level down — which is exactly what Moodle refuses.
Moodle's own error text for this is Incorrect file package - missing imsmanifest.xml or AICC structure. Some authoring tools produce a friendlier version of the same complaint: An imsmanifest.xml file was found but it was not in the root of your zip file. Please repackage your SCORM. Both mean the same thing.
Three more packaging details worth knowing:
- Case matters. Moodle servers run Linux, and the filename must be lowercase
imsmanifest.xml.IMSManifest.xmlworks on a Windows authoring machine and fails on the server. - macOS adds junk. The Finder's Compress command writes a
__MACOSXfolder and.DS_Storeentries into the archive. Moodle tolerates them, but they inflate the package and some stricter validators reject them. Building the ZIP from the command line avoids the problem entirely. - Whitespace in the manifest is not always harmless. MoodleDocs records that some authoring tools — Articulate Presenter is named — generate manifests padded with excessive whitespace when the reporting and tracking fields are left blank, producing manifest-not-found errors. Filling in the title and description fields before publishing avoids it.
To verify a ZIP before uploading, unzip it into an empty folder and look at what appears. If you see imsmanifest.xml immediately, you are fine. If you see a single folder, repackage. On any Unix-like machine, unzip -l course.zip | head tells you the same thing in one line.
Getting a large package past the upload limit
Video-heavy SCORM packages routinely exceed default limits, and Moodle's limit is a chain rather than a single number. Each level can only tighten what the level above allows: PHP first, then site, then course, then activity.
| Level | Where it lives | Note |
|---|---|---|
| Server | php.ini: upload_max_filesize, post_max_size, max_execution_time | The hard ceiling. Moodle can never exceed it. 600 seconds is the recommended execution time for large uploads. |
| Site | Site administration → Security → Site security settings → Maximum uploaded file size | Shows the effective limit once PHP restrictions are applied. |
| Course | Course settings page → Maximum upload size | Frequently the real culprit on a site where the server limits are already generous. |
| Activity | The activity's own settings | Last and least restrictive link in the chain. |
If you cannot change PHP settings — common on shared hosting — the practical fix is to stop shipping media inside the package. Host the video externally, reference it by URL from the course, and the ZIP drops to a few megabytes.
The settings that actually change behaviour
The SCORM activity form has around thirty fields. Roughly eight of them change what a learner experiences. Here is what each one does and what we set it to by default.
| Setting | What it really does | Our default |
|---|---|---|
| Display package | Current window or new window. New window depends on the learner's pop-up blocker, and a blocked pop-up looks to the learner like a broken course. | Current window |
| Display course structure on entry page | Shows the package table of contents before launch. Useful for multi-SCO courses, noise for single-SCO ones. | Off for single-SCO |
| Width / Height | Stage size as a CSS value. Defaults are 100% and 500px, and 500px is too short for most modern 16:9 content, producing an inner scrollbar. | 100% / 700px or more |
| Number of attempts | Applies to SCORM 1.2 and AICC only. Note that a learner stays inside one attempt until the package reports completed, passed or failed — the Start new attempt checkbox only appears once a status has been set. | Unlimited unless assessed |
| Attempts grading | Which attempt reaches the gradebook: Highest, Average, First or Last completed attempt. | Highest attempt |
| Force new attempt | No, When previous attempt completed passed or failed, or Always. Always discards resume state on every launch. | When previous attempt completed |
| Grading method | How one attempt is scored: Learning objects, Highest grade, Average grade or Sum grade. Learning objects counts completed SCOs rather than marks. | Highest grade if scored, Learning objects if not |
| Auto-commit | Commits tracking data 60 seconds after a value changes when the package never calls Commit itself. The best available defence against a learner closing the tab. | Yes |
| Auto-continue | Launches the next learning object automatically when one closes communication. Only meaningful in multi-SCO packages. | Yes for multi-SCO |
| Mastery score overrides status | If a raw score is below the manifest's mastery score at LMSFinish, the status is rewritten to failed. Silent and surprising. | Off unless the manifest is deliberate |
| Disable preview mode | Preview reports the status browsed, which never satisfies completion. Turning it off removes an entire class of support ticket. | Yes, disable preview |
Completion settings, which are separate and easy to miss
Activity completion is configured further down the same form and is not part of the SCORM settings proper. For a SCORM activity you get Require view, Require grade, Require status with Passed and Completed checkboxes, Require all scoes to return completion status, and a minimum score condition.
Require status maps directly onto the value your package writes to cmi.core.lesson_status. Moodle's own support documentation puts it plainly: if the stored value is completed, use Completed; if it is passed, use Passed. Recent Moodle versions let you tick both, which is treated as any-one-of and is almost always the right choice. Require all scoes to return completion status is only relevant for multi-SCO packages, and it is unforgiving: one optional SCO the learner never opens will hold the whole activity at incomplete.
- 1Publish from the authoring tool as SCORM 1.2, with tracking set deliberately
In Storyline, Rise, Captivate, iSpring or Iomad's builder, choose SCORM 1.2 as the output. Before exporting, open the reporting and tracking options and set them yourself rather than accepting defaults. Pick one unambiguous completion trigger. Fill in the title, identifier and description fields even if they look cosmetic, because leaving them blank is what produces malformed manifests in some tools. Note which status the tool says it will report, because you will need that when you configure completion in Moodle.
- 2Open the manifest and read it before you zip anything
Unzip the tool's output into a scratch folder and open imsmanifest.xml in any text editor. Confirm four things: schemaversion says 1.2, the organizations element contains the items you expect, every page that must report progress has adlcp:scormtype set to sco rather than asset, and identifiers begin with a letter with no spaces or special characters. If there is an adlcp:masteryscore element, decide now whether you want Moodle's mastery score override switched on, because that element gives the LMS permission to rewrite your status to failed.
- 3Zip the contents, never the folder
Open the course folder, select everything inside it including imsmanifest.xml, and compress the selection. Do not select the folder itself. On macOS use Finder's Compress on the selected files, or better, run zip -r ../course.zip . from inside the folder in Terminal. On Windows, select the files then right-click and choose Send to, Compressed folder. Confirm the filename is lowercase imsmanifest.xml, because Linux servers are case-sensitive and Windows machines are not.
- 4Verify the archive before you upload it
Unzip the new archive into an empty folder. You should see imsmanifest.xml straight away, not a single folder containing it. On a Unix-like machine, unzip -l course.zip | head does the same job in one command. This ten-second check prevents the single most common upload failure in Moodle, and it is worth doing every time even when you are certain.
- 5Create the SCORM activity and upload the ZIP without extracting it
Turn editing on in the course, choose Add an activity or resource, and pick SCORM package. Give it a name that matches what the learner expects to see on the course page. Drag the ZIP into the Package file area. MoodleDocs is explicit that you must not unzip the package before upload, since Moodle extracts it into the activity's own file area. If the upload is refused for size, work up the chain: activity limit, then course Maximum upload size, then Site administration, Security, Site security settings, Maximum uploaded file size, then php.ini.
- 6Set Appearance so the course is actually usable
Set Display package to Current window unless you have a specific reason to use a pop-up, because pop-up blockers turn New window into a support ticket. Raise the stage height from the 500px default to something that fits 16:9 content, typically 700px or more, and leave width at 100%. For a single-SCO package turn off Display course structure on entry page. Set Disable preview mode to Yes, since preview writes the status browsed and browsed can never satisfy completion.
- 7Configure Grade and Attempts to match what the package reports
If the package reports a raw score, set Grading method to Highest grade and set Maximum grade to the same scale the course uses. If it only reports a status and no score, use Learning objects, which counts completed learning objects instead of marks. Then set Attempts grading, usually Highest attempt, and Force new attempt to When previous attempt completed, passed or failed. Avoid Always on any course with resume behaviour, because it discards suspend data every launch and learners restart from the beginning.
- 8Turn Auto-commit on in Compatibility settings
Auto-commit writes tracking data 60 seconds after a value changes when the package does not call Commit itself. It is off by default and it is the most effective protection you have against a learner closing the tab before the final call reaches the server. While you are in that section, decide on Auto-continue for multi-SCO packages and leave Mastery score overrides status off unless the mastery score in your manifest is intentional.
- 9Set activity completion to the status the package actually sends
Open the Activity completion section and choose Show activity as complete when conditions are met. Tick Require status and select both Passed and Completed unless you know precisely which one your package writes, since Moodle treats multiple ticks as any-one-of. Only enable Require all scoes to return completion status on genuinely multi-SCO courses where every SCO is mandatory, because one skipped optional SCO will hold the activity at incomplete forever.
- 10Take one full attempt as a real test student, then read the raw tracking
Do not test as a teacher and do not use preview. Enrol a dedicated test student account, launch the course, complete it exactly as a learner would including closing it the way a learner would, then open the activity's Reports tab and choose Basic report. Click the number in the Attempts column to see the CMI elements stored for that attempt. You are looking for a status of passed or completed and, if the course is scored, a raw score. If both are present and the activity shows a completion tick, you are done. If the status is right but the tick is missing, the problem is your completion settings, not the package.
- 11Roll out behind a restricted section, then release
Keep the activity hidden or restricted to a small pilot group for the first cohort. Watch the Basic report for the first ten to twenty genuine learners rather than assuming your own successful test generalises, since real learners use different browsers, close tabs mid-course and take breaks that expire sessions. Once the report shows clean statuses across that group, remove the restriction. Fixing a broken package after three hundred people have half-finished it is a far worse day than delaying release by one.
A package Moodle accepts on the first try
- imsmanifest.xml sits at the archive root, visible the moment the ZIP is opened
- The filename is lowercase, because the server is Linux and case-sensitive
- schemaversion declares 1.2, the version Moodle core is conformance-tested for
- Every trackable page is declared adlcp:scormtype="sco", not asset
- Identifiers start with a letter and contain no spaces or special characters
- The ZIP was built by compressing the folder's contents, not the folder
- Total size is under the site's effective upload limit, with media hosted externally if needed
- Reporting and tracking fields were filled in before export, so the manifest is well formed
A package Moodle rejects, or accepts and then fails to track
- The archive root contains one folder and the manifest is inside it
- IMSManifest.xml with capitals, which worked on the authoring laptop
- Published as SCORM 2004, which core Moodle does not support
- The launch page is declared as an asset, so it renders but reports nothing
- Identifiers with spaces, ampersands or a leading digit
- Compressed by right-clicking the course folder, the default instinct
- A 400MB ZIP with embedded video hitting post_max_size before it reaches Moodle
- Blank reporting fields producing a whitespace-padded manifest that fails to parse
Would you rather hand this to someone who does it every week?
Packaging is the easy half. The hard half is a course that survives real learners on real browsers with real session timeouts, and that is only proven by testing inside the LMS it will actually run in. Our course development team builds SCORM packages and validates completion inside the client's own Moodle before handover, with the activity settings documented so nobody has to reverse-engineer them later. We also remediate packages built elsewhere, including legacy courses from tools that no longer exist. Links to everything are at the end of this article.
The five-minute pre-launch test that catches almost everything
Enrol one dedicated test student account, launch the course as that student rather than as a teacher, complete it, then deliberately close the browser tab instead of clicking exit. Reopen the activity and check two things in Reports, Basic report: that the stored status reads passed or completed, and that resume returned you to where you left off. Closing the tab is the single most common way real learners end a course and the single least common way it gets tested. If the status survives that, it will survive most of what production throws at it.
Frequently asked questions
Which SCORM version should I publish in for Moodle?
SCORM 1.2, in almost every case. MoodleDocs states that Moodle passes the ADL Conformance test suite for SCORM 1.2, and equally states that SCORM 2004 development has stopped with its bugs closed as will-not-fix. Publishing 2004 into stock Moodle gives you the added complexity of the 2004 data model without the sequencing and navigation features that were the reason to choose it. If you genuinely need SCORM 2004, you need a third-party route such as Rustici's SCORM Cloud plugin.
Why does Moodle say Incorrect file package - missing imsmanifest.xml or AICC structure?
Nine times out of ten the ZIP was created by right-clicking the course folder rather than its contents, so the archive root contains a single folder and imsmanifest.xml is one level down. Moodle only looks at the root. Rebuild the archive by selecting everything inside the course folder and compressing the selection. The other causes are a capitalised filename such as IMSManifest.xml, which fails on case-sensitive Linux servers, and a manifest that is present but malformed.
Do I need to unzip the SCORM package before uploading it to Moodle?
No, and doing so will break the upload. MoodleDocs states directly that you must not unzip the package before upload. You drag the ZIP file itself into the Package file area and Moodle extracts it into the activity's own file area. Uploading loose files, or uploading only the manifest, gives you an activity with nothing to launch.
My SCORM ZIP is bigger than Moodle's upload limit. What do I change?
Work up the chain, because each level can only tighten the one above it. Check the activity's limit, then the course's Maximum upload size on the course settings page, then Site administration, Security, Site security settings, Maximum uploaded file size, and finally php.ini where upload_max_filesize and post_max_size set the hard ceiling. Moodle can never exceed the PHP values. If you have no server access, the practical fix is to stop embedding video in the package and reference it from an external host instead.
What is the difference between Grading method and Attempts grading?
They answer two different questions and are commonly confused. Grading method decides how a single attempt is scored: Learning objects counts completed learning objects, while Highest grade, Average grade and Sum grade work from the scores the SCOs report. Attempts grading decides which of several attempts reaches the gradebook, with options for Highest, Average, First and Last completed attempt. If your gradebook numbers look wrong, check both before assuming the package is at fault.
Can I replace a SCORM package without losing learner attempt data?
You can upload a new ZIP into an existing SCORM activity and the existing attempts stay attached to it, but attempt tracking is keyed to the SCO identifiers declared in the manifest. If the new package changes those identifiers, old tracking no longer lines up with the new structure and reports become misleading. For a minor content fix that keeps the same identifiers, replacing in place is fine. For a materially restructured course, create a new activity and retire the old one, so the historical record stays intact and auditable.
How do I test that completion works before rolling the course out?
Use a dedicated test student account, never a teacher account and never preview mode, because preview writes the status browsed which Moodle will never accept as completion. Complete the course as a learner would, then open the activity's Reports tab, choose Basic report, and click the number in the Attempts column to see the actual CMI elements stored. If the status reads passed or completed and the activity shows a tick, you are done. If the status is right but the tick is missing, the problem is in your completion settings rather than the package.
The part that is easy to skip
Everything above takes maybe forty minutes for a course you have packaged before. The step people skip is the last one: taking a full attempt as a real student and reading the raw tracking data before anyone else sees the course.
It gets skipped because it feels redundant. The course played fine in the authoring tool's preview, the upload succeeded, the table of contents rendered. But none of those things exercise the runtime API, and the runtime API is the only part that determines whether the course counts. A package can look completely healthy right up until the moment you need a compliance report, which is the worst possible moment to discover it was never tracking.
The other reason it gets skipped is organisational rather than technical. The content vendor ships a ZIP and considers the job done, the LMS admin uploads it and considers the job done, and nobody owns the join between them. That gap is why we do both sides — we build the content and run the Moodle it lands in, so completion is verified in the real environment with the real settings before anything is signed off.
Where to go next:
- Course & content development — SCORM packages, scenario-based modules, AI-assisted training video and multilingual courses, delivered LMS-tested rather than thrown over the wall.
- Why your SCORM course shows incomplete in Moodle — the nine causes we actually see in production, with the specific setting or spec behaviour behind each one. Read this one if the package uploaded fine but completion will not fire.
- Moodle DevOps & managed support — for when the real problem turns out to be PHP limits, cron or the hosting layer rather than the course.
Got a package Moodle will not accept? Send us the ZIP and your Moodle version and we will tell you what is wrong with it — email marketing@edzlms.com.