What 2004 changed
SCORM 2004 keeps the shape of SCORM 1.2 — a course finds a JavaScript object, calls functions on it, reads and writes string values — and changes almost every detail. The object has a different name. The functions have different names. The status field became two fields. Times are written in a different format. If you have read the 1.2 data model reference, the fastest way to hold both in your head is this: nothing carries over unchanged except the idea.
| SCORM 1.2 | SCORM 2004 | |
|---|---|---|
| API object on the window | API | API_1484_11 |
| Start / end the session | LMSInitialize / LMSFinish | Initialize / Terminate |
| Status | One field, cmi.core.lesson_status | Two: cmi.completion_status and cmi.success_status |
| Normalised score | None — score.raw only | cmi.score.scaled, −1 to 1, used for rollup |
| Partial progress | None | cmi.progress_measure, 0 to 1 |
| Time format | HHHH:MM:SS.SS | ISO 8601 duration, e.g. PT1H30M15S |
| Bookmark size | lesson_location, 255 chars | cmi.location, 1,000 chars |
| Suspend data | 4,096 chars | 4,000 (2nd Ed.) / 64,000 (3rd, 4th Ed.) |
| Interactions | Write-only | Read/write, with correct-response patterns |
| Navigation from content | Not possible | adl.nav.request |
| Error codes | 9 in practice | 24, most naming the exact call-sequence violation |
Editions matter. 2004 2nd Edition, 3rd Edition and 4th Edition are all "SCORM 2004" and differ in ways that bite: the suspend-data cap is smaller in 2nd Edition than in 1.2, and 4th Edition added the shared-data elements and tightened sequencing. A package declaring 2004 4th Edition to a platform that implements 3rd may import and then behave unexpectedly.
The eight API functions
Same count as 1.2, all renamed. Every argument and every return value is a string.
| Function | Returns | Notes |
|---|---|---|
Initialize("") | "true" / "false" | Exactly once, before anything else. A second call is error 103. |
Terminate("") | "true" / "false" | Ends the session and implies a commit. Nothing after it counts — a second call is error 113. |
GetValue(element) | value, or "" | Empty string is both a valid value and the error return; check GetLastError to tell them apart. |
SetValue(element, value) | "true" / "false" | Not guaranteed persisted until a commit. |
Commit("") | "true" / "false" | Asks the LMS to persist. Platforms may throttle frequent calls. |
GetLastError() | error code string | Resets on the next call — read it immediately after the failure. |
GetErrorString(code) | short text, ≤255 chars | Human-readable name of the code. |
GetDiagnostic(code) | vendor text, ≤255 chars | Vendor detail. The most useful field on a strict LMS, and often empty on a lenient one. |
The 2004 error space encodes the call sequence, which makes debugging much easier than in 1.2. A failed SetValue before Initialize is 132; after Terminate it is 133. The equivalent for GetValue is 122 and 123, and for Commit, 142 and 143. If you see any of those six, the problem is when the call happened, not what it contained.
Status, score, and progress
The 2004 change people actually feel is the status split. SCORM 1.2 has one field that answers two questions badly; 2004 has two fields that answer them separately.
| Element | Access | Type / values | Notes |
|---|---|---|---|
cmi.completion_status | RW | completed, incomplete, not attempted, unknown | Did the learner get through the content. |
cmi.success_status | RW | passed, failed, unknown | Did they meet the bar. Independent of completion. |
cmi.score.scaled | RW | real, −1 to 1 | The normalised score. This is what the LMS uses for rollup — set it, not just raw. |
cmi.score.raw / .min / .max | RW | real | Human-facing score and its bounds. Most report screens show raw. |
cmi.progress_measure | RW | real, 0 to 1 | How far through. No 1.2 equivalent. Useful for "62% complete" progress bars in LMS reports. |
cmi.completion_threshold | RO | real, 0 to 1 | From the manifest. If set, the LMS may derive completion from progress_measure and override what the content wrote. |
cmi.scaled_passing_score | RO | real, −1 to 1 | From <imsss:minNormalizedMeasure>. The 2004 replacement for masteryscore; the LMS may recompute success from it. |
cmi.location | RW | string, ≤1,000 chars | The bookmark. Larger than 1.2's 255, still not for real state. |
cmi.suspend_data | RW | string, ≤64,000 (3rd/4th Ed.) | All resume state. See suspend data limits. |
cmi.session_time | WO | ISO 8601 duration | PT1H30M15S, not 01:30:15.00. Writing the 1.2 format is error 406. |
cmi.total_time | RO | ISO 8601 duration | Accumulated across sessions by the LMS. |
cmi.mode | RO | browse, normal, review | Content should not overwrite a real status when mode is review. |
cmi.credit | RO | credit, no-credit | Whether the attempt counts toward the record. |
cmi.learner_id / .learner_name | RO | string | Renamed from student_id / student_name. |
cmi.launch_data | RO | string, ≤4,000 | From <adlcp:dataFromLMS> in the manifest. |
cmi.max_time_allowed / .time_limit_action | RO | duration / state | Enforcement varies widely by platform. |
cmi.learner_preference.* | RW | audio_level, language, delivery_speed, audio_captioning | Renamed from student_preference. Persisted per learner across attempts. |
Objectives and interactions
Both groups are arrays, indexed from zero, addressed as cmi.objectives.0.id and so on. cmi.objectives._count and cmi.interactions._count are read-only and tell you how many exist. The index must be written in order: writing index 3 when _count is 1 is error 407.
Objectives carry their own status and score, which is how a single SCO reports on several learning objectives:
| Element | Access | Notes |
|---|---|---|
cmi.objectives.n.id | RW | Required first, before any other field in that index. Must be unique. |
cmi.objectives.n.score.scaled / .raw / .min / .max | RW | Same semantics as the top-level score. |
cmi.objectives.n.success_status | RW | passed, failed, unknown |
cmi.objectives.n.completion_status | RW | completed, incomplete, not attempted, unknown |
cmi.objectives.n.progress_measure | RW | 0 to 1. Not in 1.2. |
cmi.objectives.n.description | RW | ≤250 chars, localised. |
Interactions are the per-question records. The big 2004 change is that they are read/write — 1.2 made them write-only, so content could not read back what it had stored, and many platforms accepted and discarded them.
| Element | Access | Notes |
|---|---|---|
cmi.interactions.n.id | RW | Required first in each index. |
cmi.interactions.n.type | RW | true-false, choice, fill-in, long-fill-in, likert, matching, performance, sequencing, numeric, other. long-fill-in is new in 2004. |
cmi.interactions.n.learner_response | RW | Renamed from student_response. Its format is dictated by type — writing a value the type does not allow is error 406. |
cmi.interactions.n.correct_responses.n.pattern | RW | The expected answer, in the type's pattern syntax. No 1.2 equivalent worth using. |
cmi.interactions.n.result | RW | correct, incorrect, unanticipated, neutral, or a real number. |
cmi.interactions.n.weighting | RW | Relative weight of this question. |
cmi.interactions.n.latency | RW | ISO 8601 duration — time from presentation to response. |
cmi.interactions.n.timestamp | RW | When it was presented. |
cmi.interactions.n.objectives.n.id | RW | Links a question to an objective. |
cmi.interactions.n.description | RW | ≤250 chars. Often the question text. |
Navigation, exit, and resume
2004 lets content ask the LMS to navigate, which 1.2 could not do at all. The request is written before Terminate and acted on after it.
| Element | Access | Values |
|---|---|---|
adl.nav.request | RW | continue, previous, choice, jump, exit, exitAll, abandon, abandonAll, suspendAll, _none_ |
adl.nav.request_valid.continue | RO | true, false, unknown — ask before offering a Next button |
adl.nav.request_valid.previous | RO | Same |
adl.nav.request_valid.choice.{target=ITEM-2} | RO | Whether jumping to that activity is allowed by the sequencing rules |
Whether those requests are honoured depends on the control modes in the manifest's sequencing block, which is a separate subject covered in SCORM 2004 sequencing.
Exit and resume work as in 1.2 but with an extra value and a second mechanism. cmi.exit (write-only) takes time-out, suspend, logout, normal, or an empty string; normal is new in 2004. To be resumable, an attempt must end with cmi.exit set to suspend — or, in a multi-SCO course, with adl.nav.request set to suspendAll. On the next launch, cmi.entry reads resume and cmi.suspend_data returns what was stored. Ending with an empty exit closes the attempt on many platforms and discards the suspend data, which is the most common cause of "it forgot my progress" that is not a size problem.
Error codes
All twenty-four, grouped as the specification groups them. In practice the 1xx family tells you the session is out of order and the 4xx family tells you the element or value is wrong.
| Code | Meaning | Usual cause |
|---|---|---|
0 | No error | — |
101 | General exception | The catch-all. Read GetDiagnostic. |
102 | General initialization failure | Initialize itself failed — often the LMS session expired. |
103 | Already initialized | A second Initialize, usually a relaunch inside a live session. |
104 | Content instance terminated | Initialize after Terminate in the same instance. |
111 | General termination failure | Terminate failed. |
112 | Termination before initialization | Terminate with no session open. |
113 | Termination after termination | A second Terminate. |
122 | Retrieve data before initialization | GetValue too early — a launch race. |
123 | Retrieve data after termination | GetValue after the session closed. |
132 | Store data before initialization | SetValue too early. |
133 | Store data after termination | SetValue too late — the classic lost completion. |
142 | Commit before initialization | Commit too early. |
143 | Commit after termination | Commit too late. |
201 | General argument error | A non-empty string passed to Initialize, Terminate or Commit. |
301 | General get failure | Unspecified GetValue failure. |
351 | General set failure | Unspecified SetValue failure. |
391 | General commit failure | Unspecified Commit failure — often the network. |
401 | Undefined data model element | The element does not exist in 2004. Frequently a 1.2 name such as cmi.core.score.raw. |
402 | Unimplemented data model element | Valid in the spec, not implemented by this LMS. |
403 | Data model element value not initialized | Reading something never set — reading cmi.location on a first attempt. |
404 | Data model element is read only | Writing cmi.total_time, cmi.entry, cmi.mode… |
405 | Data model element is write only | Reading cmi.exit or cmi.session_time back. |
406 | Data model element type mismatch | Wrong format — the 1.2 time format in cmi.session_time, or a value outside a vocabulary. |
407 | Data model element value out of range | score.scaled above 1, or an array index beyond _count. |
408 | Data model dependency not established | Setting cmi.interactions.0.result before cmi.interactions.0.id. |
Codes 403 and 405 swap meanings between 1.2 and 2004 — in 1.2, 403 is "read only" and 404 is "write only". Content ported between versions that interprets codes numerically will misreport them.
Mapping every 1.2 element to 2004
For porting content or reading a runtime log from the other version:
| SCORM 1.2 | SCORM 2004 | What changed |
|---|---|---|
cmi.core.student_id | cmi.learner_id | Renamed |
cmi.core.student_name | cmi.learner_name | Renamed |
cmi.core.lesson_location | cmi.location | Renamed; 255 → 1,000 chars |
cmi.core.lesson_status | cmi.completion_status + cmi.success_status | Split into two fields; browsed dropped |
cmi.core.credit | cmi.credit | Moved out of core |
cmi.core.entry | cmi.entry | Moved |
cmi.core.exit | cmi.exit | Moved; adds normal |
cmi.core.lesson_mode | cmi.mode | Renamed |
cmi.core.score.raw / .min / .max | cmi.score.raw / .min / .max | Moved; score.scaled added |
cmi.core.session_time | cmi.session_time | Moved; format changes to ISO 8601 |
cmi.core.total_time | cmi.total_time | Moved; format changes |
cmi.suspend_data | cmi.suspend_data | Same name; cap 4,096 → 64,000 (3rd/4th Ed.) |
cmi.launch_data | cmi.launch_data | Same; manifest element renamed to dataFromLMS |
cmi.comments | cmi.comments_from_learner.n.comment | Becomes an array with location and timestamp |
cmi.comments_from_lms | cmi.comments_from_lms.n.comment | Becomes an array |
cmi.student_data.mastery_score | cmi.scaled_passing_score | Scale changes from 0–100 to −1–1 |
cmi.student_data.max_time_allowed | cmi.max_time_allowed | Moved |
cmi.student_data.time_limit_action | cmi.time_limit_action | Moved |
cmi.student_preference.* | cmi.learner_preference.* | Renamed |
cmi.objectives.n.* | cmi.objectives.n.* | Adds completion_status, progress_measure, score.scaled, description |
cmi.interactions.n.student_response | cmi.interactions.n.learner_response | Renamed; group becomes readable |
| — | cmi.progress_measure | New |
| — | cmi.completion_threshold | New |
| — | adl.nav.request / request_valid.* | New |
Porting is therefore never a search-and-replace: the status split needs a decision about what "done" and "passed" mean for your content, and the time format change breaks silently with error 406 if missed. Which version to build for a given destination is covered in SCORM 1.2 vs 2004, and when a ported package misbehaves, work through the diagnostic checklist.