So you need to implement SCORM. Maybe you're building custom eLearning content, integrating a course player, or debugging why a course isn't tracking properly in your LMS. Whatever brought you here, I'm going to walk you through exactly how SCORM works at the technical level.
Not a developer? If you're looking for a high-level overview without the code, check out our What is SCORM? An Executive Guide for L&D Leaders.
Quick note: In practice, most people don't hand-code SCORM packages. Authoring tools like Articulate Storyline, Adobe Captivate, or iSpring handle the packaging for you. But understanding what's inside helps when things go wrong. And things will go wrong.
The Three Core Components of SCORM
SCORM isn't one thing. It's a bundle of related specifications. Understanding these three components is key to working with SCORM effectively:
- Content Packaging — how you bundle your files into a ZIP that an LMS can understand
- Run-Time Environment — the JavaScript API your content uses to talk to the LMS
- Data Model — the specific fields you can read and write (scores, completion, bookmarks, etc.)
1. Content Packaging
Content packaging defines how you bundle your training materials so an LMS can understand them. It's essentially a ZIP file with a specific structure.
At the heart of every SCORM package is imsmanifest.xml. This file tells the LMS everything it needs to know: what files are included, how they're organized, and what should be launched. If you already have a package, you can inspect that file with Edaxu's free SCORM manifest parser.
Here's what a typical SCORM package looks like when unzipped:
scorm-package/
├── imsmanifest.xml # Required - the manifest file
├── index.html # Your course entry point
├── js/
│ ├── scorm-api.js # JavaScript to communicate with the LMS
│ └── course-logic.js # Your course code
├── css/
│ └── styles.css
├── images/
│ └── ...
└── adlcp_rootv1p2.xsd # Schema files (usually included)The manifest file is critical. Here's a working example:
<?xml version="1.0" encoding="UTF-8"?>
<manifest identifier="course_001" version="1.0"
xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2">
<metadata>
<schema>ADL SCORM</schema>
<schemaversion>1.2</schemaversion>
</metadata>
<organizations default="org_001">
<organization identifier="org_001">
<title>Introduction to Safety Training</title>
<item identifier="item_001" identifierref="resource_001">
<title>Module 1: Workplace Safety Basics</title>
</item>
</organization>
</organizations>
<resources>
<resource identifier="resource_001" type="webcontent"
adlcp:scormtype="sco" href="index.html">
<file href="index.html"/>
<file href="js/scorm-api.js"/>
<file href="js/course-logic.js"/>
<file href="css/styles.css"/>
</resource>
</resources>
</manifest>A few things:
- The
identifierattributes must be unique within the manifest adlcp:scormtype="sco"marks this as a Sharable Content Object (something that communicates with the LMS)- Every file in your package should be listed under
<file>elements - File paths are case-sensitive on many systems
2. Run-Time Environment (The API)
This is where the actual communication happens. When a learner launches a SCORM course, the LMS provides a JavaScript API that your content uses to send and receive data.
The API Hunt
Your content runs in a browser window (usually an iframe), but the API object lives in the parent window hierarchy. SCORM defines a standard way to find it:
function findAPI(win) {
let attempts = 0;
const maxAttempts = 500;
// Search up through parent windows
while (win.API == null && win.parent != null &&
win.parent != win && attempts < maxAttempts) {
attempts++;
win = win.parent;
}
// If not found, check the opener window
if (win.API == null && win.opener != null) {
win = win.opener;
while (win.API == null && win.parent != null &&
win.parent != win && attempts < maxAttempts) {
attempts++;
win = win.parent;
}
}
return win.API;
}For SCORM 2004, look for API_1484_11 instead of API. The weird name comes from the IEEE standard number.
The Eight Core API Methods
Once you've found the API, you have eight methods to work with:
| SCORM 1.2 | SCORM 2004 | Purpose |
|---|---|---|
LMSInitialize("") | Initialize("") | Start the session |
LMSFinish("") | Terminate("") | End the session |
LMSGetValue(element) | GetValue(element) | Read data |
LMSSetValue(element, value) | SetValue(element, value) | Write data |
LMSCommit("") | Commit("") | Persist data |
LMSGetLastError() | GetLastError() | Get error code |
LMSGetErrorString(code) | GetErrorString(code) | Get error message |
LMSGetDiagnostic(code) | GetDiagnostic(code) | Get error details |
A Basic SCORM Session
Here's what a complete SCORM interaction looks like:
// Find the API
const API = findAPI(window);
if (!API) {
console.error('SCORM API not found - running outside LMS?');
// Decide whether to continue or abort
}
// Initialize the connection
const initResult = API.LMSInitialize('');
if (initResult !== 'true') {
const errorCode = API.LMSGetLastError();
const errorMsg = API.LMSGetErrorString(errorCode);
console.error(`SCORM init failed: ${errorMsg}`);
}
// Read learner info
const learnerName = API.LMSGetValue('cmi.core.student_name');
const learnerId = API.LMSGetValue('cmi.core.student_id');
console.log(`Welcome, ${learnerName} (${learnerId})`);
// Check existing progress
const lessonStatus = API.LMSGetValue('cmi.core.lesson_status');
const bookmark = API.LMSGetValue('cmi.core.lesson_location');
if (lessonStatus === 'completed' || lessonStatus === 'passed') {
// Maybe show a "review mode" message
}
if (bookmark) {
// Resume from where they left off
jumpToLocation(bookmark);
}
// When the learner progresses...
function saveProgress(location) {
API.LMSSetValue('cmi.core.lesson_location', location);
API.LMSCommit(''); // Important: persist the data
}
// When they complete the course...
function completeCourse(score) {
API.LMSSetValue('cmi.core.lesson_status', 'completed');
API.LMSSetValue('cmi.core.score.raw', score.toString());
API.LMSCommit('');
}
// When they leave - CRITICAL: always call this
function closeCourse() {
API.LMSFinish('');
}
// Make sure LMSFinish gets called no matter how they exit
window.addEventListener('beforeunload', closeCourse);
window.addEventListener('unload', closeCourse);Critical: Always call LMSFinish (or Terminate in SCORM 2004). If you don't, the LMS might not save the session data. I can't tell you how many "my completion didn't track" bugs come down to a missing LMSFinish call when the browser closes unexpectedly.
3. The Data Model
SCORM defines specific elements you can read and write. You can't just make up your own field names—you have to use the standard data model elements.
Essential Data Model Elements (SCORM 1.2)
// Learner Identity
cmi.core.student_id // Read-only - learner's ID
cmi.core.student_name // Read-only - learner's name
// Status and Completion
cmi.core.lesson_status // "not attempted", "incomplete", "completed",
// "passed", "failed"
cmi.core.entry // "ab-initio" (new), "resume" (returning), or ""
// Score
cmi.core.score.raw // Numeric score (e.g., "85")
cmi.core.score.min // Minimum possible score
cmi.core.score.max // Maximum possible score
// Time Tracking
cmi.core.session_time // How long this session lasted (format: "HH:MM:SS")
cmi.core.total_time // Read-only - cumulative time across sessions
// Bookmarking
cmi.core.lesson_location // Free-form string to store position (max 255 chars)
// Custom Data
cmi.suspend_data // Free-form string for custom data (max 4096 chars)
cmi.launch_data // Read-only - data passed from manifestSCORM 2004 Differences
The data model paths are slightly different in SCORM 2004:
// SCORM 1.2 // SCORM 2004
cmi.core.student_id → cmi.learner_id
cmi.core.student_name → cmi.learner_name
cmi.core.lesson_status → cmi.completion_status + cmi.success_status
cmi.core.score.raw → cmi.score.raw + cmi.score.scaled
cmi.core.session_time → cmi.session_time (ISO 8601 duration)
cmi.core.lesson_location → cmi.location
cmi.suspend_data → cmi.suspend_data (64KB limit vs 4KB)The big change in SCORM 2004 is splitting lesson_status into two separate fields: completion_status (completed/incomplete) and success_status (passed/failed). This gives you more granular control.
Tracking Quiz Interactions
SCORM lets you record individual question responses, which is invaluable for analytics:
// Record a multiple-choice question
API.LMSSetValue('cmi.interactions.0.id', 'question_safety_001');
API.LMSSetValue('cmi.interactions.0.type', 'choice');
API.LMSSetValue('cmi.interactions.0.student_response', 'A');
API.LMSSetValue('cmi.interactions.0.correct_responses.0.pattern', 'B');
API.LMSSetValue('cmi.interactions.0.result', 'incorrect');
API.LMSSetValue('cmi.interactions.0.weighting', '1');
API.LMSSetValue('cmi.interactions.0.latency', '00:00:45'); // Time to answer
// Record a true/false question
API.LMSSetValue('cmi.interactions.1.id', 'question_safety_002');
API.LMSSetValue('cmi.interactions.1.type', 'true-false');
API.LMSSetValue('cmi.interactions.1.student_response', 't');
API.LMSSetValue('cmi.interactions.1.correct_responses.0.pattern', 't');
API.LMSSetValue('cmi.interactions.1.result', 'correct');
// Record a fill-in-the-blank
API.LMSSetValue('cmi.interactions.2.id', 'question_safety_003');
API.LMSSetValue('cmi.interactions.2.type', 'fill-in');
API.LMSSetValue('cmi.interactions.2.student_response', 'fire extinguisher');
API.LMSSetValue('cmi.interactions.2.correct_responses.0.pattern', 'fire extinguisher');
API.LMSSetValue('cmi.interactions.2.result', 'correct');
API.LMSCommit('');The interaction index (0, 1, 2...) increments for each new question. Interaction types include: choice, true-false, fill-in, matching, performance, sequencing, likert, numeric.
SCORM 1.2 vs SCORM 2004: Technical Differences
The API naming isn't the only thing that changed. Here's what actually matters:
Sequencing and Navigation
SCORM 1.2 has no sequencing specification. If you want to control navigation (e.g., "complete Module 1 before accessing Module 2"), you build that logic in your course JavaScript.
SCORM 2004 added a complex sequencing specification that lets you define rules in the manifest. The LMS then enforces navigation. It's powerful but notoriously difficult to implement correctly.
<!-- SCORM 2004 Sequencing Example -->
<sequencing>
<controlMode choice="false" flow="true"/>
<sequencingRules>
<preConditionRule>
<ruleConditions conditionCombination="all">
<ruleCondition condition="completed" referencedObjective="obj_module1"/>
</ruleConditions>
<ruleAction action="disabled"/>
</preConditionRule>
</sequencingRules>
</sequencing>Most developers I know avoid SCORM 2004 sequencing unless specifically required. It's hundreds of pages of specification, and debugging issues is painful.
Data Limits
| Data Element | SCORM 1.2 | SCORM 2004 |
|---|---|---|
suspend_data | 4,096 chars | 64,000 chars |
lesson_location/location | 255 chars | 1,000 chars |
comments_from_learner | 4,096 chars | 64,000 chars |
If you're building a complex simulation or branching scenario that needs to save a lot of state, SCORM 1.2's 4KB limit can be a real constraint.
Time Format
// SCORM 1.2 - HH:MM:SS.ss format
API.LMSSetValue('cmi.core.session_time', '00:45:30.50');
// SCORM 2004 - ISO 8601 duration format
API.SetValue('cmi.session_time', 'PT45M30.5S'); // 45 minutes, 30.5 secondsError Handling
SCORM 2004 has more detailed error codes and better diagnostic information. But in practice, error handling in SCORM is minimal—most issues surface as tracking failures that you debug through logging.
Building a SCORM Wrapper
Rather than calling API methods directly throughout your code, wrap them in a clean interface:
const SCORM = (function() {
let API = null;
let initialized = false;
function findAPI(win) {
let attempts = 0;
while (win.API == null && win.parent != null &&
win.parent != win && attempts < 500) {
attempts++;
win = win.parent;
}
if (win.API == null && win.opener != null) {
return findAPI(win.opener);
}
return win.API;
}
function init() {
API = findAPI(window);
if (!API) {
console.warn('SCORM API not found');
return false;
}
const result = API.LMSInitialize('');
initialized = (result === 'true');
if (!initialized) {
console.error('SCORM initialization failed:', getError());
}
return initialized;
}
function terminate() {
if (!initialized) return true;
const result = API.LMSFinish('');
initialized = false;
return result === 'true';
}
function get(element) {
if (!initialized) return '';
return API.LMSGetValue(element);
}
function set(element, value) {
if (!initialized) return false;
return API.LMSSetValue(element, value) === 'true';
}
function save() {
if (!initialized) return false;
return API.LMSCommit('') === 'true';
}
function getError() {
if (!API) return 'API not found';
const code = API.LMSGetLastError();
return API.LMSGetErrorString(code);
}
// Public interface
return {
init,
terminate,
get,
set,
save,
getError,
// Convenience methods
getStudentName: () => get('cmi.core.student_name'),
getStudentId: () => get('cmi.core.student_id'),
setStatus: (status) => set('cmi.core.lesson_status', status),
setScore: (score) => set('cmi.core.score.raw', score.toString()),
setLocation: (loc) => set('cmi.core.lesson_location', loc),
getLocation: () => get('cmi.core.lesson_location'),
setSuspendData: (data) => set('cmi.suspend_data',
typeof data === 'string' ? data : JSON.stringify(data)),
getSuspendData: () => {
const data = get('cmi.suspend_data');
try { return JSON.parse(data); }
catch { return data; }
}
};
})();
// Usage
window.addEventListener('load', () => {
if (SCORM.init()) {
console.log('Hello,', SCORM.getStudentName());
const savedState = SCORM.getSuspendData();
if (savedState) {
restoreState(savedState);
}
}
});
window.addEventListener('beforeunload', () => {
SCORM.terminate();
});There are also open-source wrappers available, like pipwerks SCORM API Wrapper, that handle edge cases and cross-version compatibility.
When Things Break
"The LMS Won't Import My SCORM Package"
Symptoms: Upload fails or course appears but won't launch.
Likely causes:
imsmanifest.xmlisn't at the ZIP root (most common)- Invalid XML syntax in the manifest
- File paths don't match actual files (case-sensitivity!)
- Missing required manifest elements
- Corrupted ZIP file
Debug steps:
# Unzip and check structure
unzip -l course.zip | head -20
# manifest should be at the root level, not in a subfolder
# Validate XML
xmllint --noout imsmanifest.xml
# Check for case mismatches
find . -name "*.html" -o -name "*.HTML""Course Launches But Doesn't Track"
Symptoms: Course opens, learner completes it, but LMS shows no progress.
Likely causes:
LMSInitializenever called or failedLMSSetValuecalled butLMSCommitmissingLMSFinishnot called on exit- Wrong data model element names
- LMS expects specific status values
Debug with console logging:
// Add this to your SCORM wrapper
function logAPICall(method, args, result) {
console.log(`SCORM: ${method}(${args.join(', ')}) → ${result}`);
}
// Then wrap your API calls
const origInit = API.LMSInitialize;
API.LMSInitialize = function(arg) {
const result = origInit.call(API, arg);
logAPICall('LMSInitialize', [arg], result);
return result;
};
// ... same for other methods"Works in LMS A But Not LMS B"
Symptoms: Identical package works in one LMS, fails in another.
Likely causes:
- Different timing requirements (some LMSs need delays between calls)
- Different iframe/window configurations
- Different strictness in validating values
- Security policies blocking cross-origin access
Solutions:
- Add small delays between API calls:
setTimeout(() => API.LMSCommit(''), 100) - Test in Edaxu first by uploading your package and confirming the tracked completion, score, and bookmark data
- Check browser console for CORS or security errors
- Verify your API hunt checks both parent and opener windows
"Suspend Data Not Persisting"
Symptoms: Custom data saves during session but is gone when learner returns.
Likely causes:
- Exceeding the size limit (4KB for SCORM 1.2)
- Missing
LMSCommitafterLMSSetValue LMSFinishnot being called- Special characters causing issues
Fix:
// Compress data and check size
function saveSuspendData(data) {
const json = JSON.stringify(data);
// SCORM 1.2 limit
if (json.length > 4096) {
console.error(`Suspend data too large: ${json.length} chars (max 4096)`);
return false;
}
const result = API.LMSSetValue('cmi.suspend_data', json);
API.LMSCommit('');
return result === 'true';
}Debugging Checklist
- Open browser DevTools console - Watch for JavaScript errors
- Log all API calls - See exactly what's being sent to the LMS
- Test in Edaxu - Upload your package and confirm completion, score, and bookmark behavior
- Validate manifest - Use an XML validator or SCORM validation tool
- Check network tab - Look for failed requests or CORS issues
- Test the close button - Make sure
LMSFinishfires on all exit paths
Testing Your SCORM Package
Before uploading to production:
1. Validate the Structure
# Create the package correctly
cd your-course-folder
zip -r ../course.zip * # NOT: zip -r course.zip your-course-folder/
# Verify manifest is at root
unzip -l course.zip | grep imsmanifest
# Should show: imsmanifest.xml (not folder/imsmanifest.xml)2. Validate the Manifest
Validate your imsmanifest.xml before uploading anywhere. At minimum, confirm the XML is well formed, the manifest sits at the package root, every referenced launch file exists, and the declared SCORM version matches what your LMS expects. Edaxu's free SCORM manifest parser can help you inspect the package structure before you upload it to an LMS.
This catches things like malformed XML, missing required elements, and file path mismatches before they become production problems.
3. Test Your Full Package
Want to test the complete package—not just the manifest? Start a free Edaxu trial and test your first SCORM course to see exactly how it behaves in a real LMS environment. You can check what data is being tracked and catch issues before rolling out to learners.
4. Test in Your Target LMS
Don't assume compatibility. Test the actual package in the actual LMS your learners will use. Check:
- Course launches correctly
- Progress saves when closing mid-course
- Completion registers when finishing
- Score records accurately
- Bookmarking works for returning learners
Quick Reference: Common API Patterns
// Minimum viable SCORM (just track completion)
API.LMSInitialize('');
// ... course content ...
API.LMSSetValue('cmi.core.lesson_status', 'completed');
API.LMSCommit('');
API.LMSFinish('');
// Track completion with score
API.LMSSetValue('cmi.core.lesson_status', 'passed');
API.LMSSetValue('cmi.core.score.raw', '85');
API.LMSSetValue('cmi.core.score.min', '0');
API.LMSSetValue('cmi.core.score.max', '100');
// Bookmark current position
API.LMSSetValue('cmi.core.lesson_location', 'slide_15');
// Save complex state
const state = { currentSlide: 15, answers: [1, 2, 1, 3], branch: 'A' };
API.LMSSetValue('cmi.suspend_data', JSON.stringify(state));
// Restore state on return
const savedState = JSON.parse(API.LMSGetValue('cmi.suspend_data') || '{}');Looking for the business perspective? If you need to explain SCORM to stakeholders or understand its role in L&D strategy, see our executive guide to SCORM for L&D leaders.
Ready to See SCORM in Action?
Skip the compatibility headaches. Edaxu supports both SCORM 1.2 and SCORM 2004 out of the box—just upload your packages and start testing. Start a free trial, see exactly what data your courses are sending, and debug tracking issues before they become problems.
Frequently Asked Questions
Should I use SCORM 1.2 or SCORM 2004 for my project?
Honestly? SCORM 1.2, unless you have a specific reason not to.
SCORM 2004's extra features—larger suspend_data, LMS-controlled sequencing, split completion/success status—sound nice on paper. But most projects never use them, and you'll spend extra time dealing with compatibility edge cases. Start with 1.2. You can always upgrade later if you hit its limits.
Why is my LMSFinish call not working?
This one bites everyone eventually. Usually it's one of these:
- Your API handle went stale (you stored it in a variable that's now undefined)
- The iframe got unloaded before your unload handler ran
- A JavaScript error earlier in your cleanup code killed execution
My advice: call LMSFinish as the absolute first thing in your unload handler, before any other cleanup logic. And test by actually closing the browser tab, not just clicking a "close" button.
How do I handle courses that run outside an LMS?
Check for the API first and gracefully degrade:
const API = findAPI(window);
if (API) {
// Full SCORM tracking
} else {
// Standalone mode - maybe use localStorage for progress
console.log('Running in standalone mode');
}What's the best way to debug SCORM tracking issues?
Log every API call with its parameters and return value. Use browser DevTools to watch the console. Upload to Edaxu to test in a real LMS environment and rule out package issues. Check that LMSCommit is called after LMSSetValue and that LMSFinish fires on all exit paths.
Can I track custom data beyond what SCORM provides?
Yep—that's what cmi.suspend_data is for. It's a free-form string field, so most developers just JSON.stringify their state object and store it there.
The catch is size limits: 4KB for SCORM 1.2, 64KB for SCORM 2004. If you're building something complex (branching simulations, detailed interaction logs), you might hit that wall. At that point, either compress your data, be more selective about what you store, or look into xAPI which doesn't have these constraints.
How do I convert between SCORM 1.2 and SCORM 2004?
It's not just a find-and-replace job, unfortunately. You'll need to:
- Update the manifest XML schema references
- Change all your API method names (
LMSInitialize→Initialize, etc.) - Update data model paths (
cmi.core.student_name→cmi.learner_name) - Handle the split status fields in 2004 (
lesson_statusbecomescompletion_status+success_status)
If you're using an authoring tool like Storyline or Captivate, the easy path is just re-exporting to the target version. Hand-converting custom code is tedious but doable.
Further Reading and Sources
- SCORM Run-Time Environment: Explanation and Examples
- Technical guide to SCORM 2004: A developer's guide
- SCORM 1.2: A developers guide to the SCORM eLearning standard
- The differences between SCORM 1.2 and SCORM 2004
- SCORM 1.2 vs SCORM 2004: Which One Works Best? - iSpring Solutions
- Troubleshooting SCORM: Solutions to Common Issues - Captioning Star
- ADL SCORM Resources
