All insights
Engineering 14 min readDecember 15, 2025

How to Build a SCORM-Compliant eLearning Platform: The Complete Technical Guide

SCORM seems simple. Then you discover xAPI, cmi5, LRS backends, and completion tracking edge cases that will break your platform in production. Here's everything we learned building SCORM-compliant content for enterprise clients.

By MindGrid Engineering

What SCORM Actually Is (And Isn't)

SCORM (Sharable Content Object Reference Model) is a standard that defines how eLearning content communicates with a Learning Management System (LMS). When your HR team says "the course must be SCORM compliant," they mean: the course file must work inside their LMS and report completion, scores, and time-spent back to the LMS database.

SCORM exists in two main versions that you will encounter:

  • SCORM 1.2 (2001): Still the most widely used. Required by most legacy LMS platforms.
  • SCORM 2004 (4th edition): Better data model, more granular tracking. Required by some modern platforms.

There is also xAPI (Tin Can API) and cmi5, both newer standards that track learning data with more flexibility. Many modern LMS platforms support all four.

This guide focuses on SCORM 1.2 and 2004, which cover 90%+ of enterprise eLearning projects.

The SCORM Package Structure

A SCORM package is a ZIP file (called a PIF - Package Interchange File) containing:

```

course.zip

├── imsmanifest.xml ← Required: course structure and metadata

├── index.html ← Entry point for the course

├── assets/

│ ├── videos/

│ ├── images/

│ └── audio/

└── js/

├── scorm-api.js ← Your SCORM communication library

└── course.js

```

The imsmanifest.xml is the most important file. It defines the course title, description, SCO (Sharable Content Object) structure, and resource references. An incorrect manifest will cause the LMS to reject the package entirely.

The SCORM JavaScript API

SCORM communication happens through a JavaScript API object that the LMS injects into the browser window:

  • SCORM 1.2: `window.API` object with methods like `LMSInitialize()`, `LMSSetValue()`, `LMSGetValue()`, `LMSFinish()`
  • SCORM 2004: `window.API_1484_11` object with methods like `Initialize()`, `SetValue()`, `GetValue()`, `Terminate()`

Your course content communicates with the LMS by calling these API methods. The key data elements you'll interact with:

SCORM 1.2:

  • `cmi.core.lesson_status` - Values: "passed", "failed", "completed", "incomplete", "not attempted"
  • `cmi.core.score.raw` - Numeric score
  • `cmi.core.session_time` - Time spent in current session (format: HH:MM:SS)
  • `cmi.suspend_data` - Free-text field for storing bookmark/progress data (4096 char limit)

SCORM 2004:

  • `cmi.completion_status` - "completed" | "incomplete" | "not attempted" | "unknown"
  • `cmi.success_status` - "passed" | "failed" | "unknown"
  • `cmi.score.raw`, `cmi.score.scaled`, `cmi.score.min`, `cmi.score.max`
  • `cmi.progress_measure` - Float 0.0–1.0 representing completion percentage

Building the SCORM API Wrapper

Don't interact with the raw API directly. Build a wrapper that handles LMS detection, version negotiation, and error handling:

```javascript

class SCORMAdapter {

constructor() {

this.version = null;

this.api = null;

}

initialize() {

// Try SCORM 2004 first

this.api = window.API_1484_11;

if (this.api) {

this.version = '2004';

} else if (window.API) {

// Fall back to SCORM 1.2

this.api = window.API;

this.version = '1.2';

} else {

// No LMS - development mode

console.warn('No SCORM API found. Running in standalone mode.');

return false;

}

const initFn = this.version === '2004' ? 'Initialize' : 'LMSInitialize';

return this.api[initFn]('') === 'true';

}

setValue(key, value) {

if (!this.api) return;

const fn = this.version === '2004' ? 'SetValue' : 'LMSSetValue';

this.api[fn](key, value);

}

getValue(key) {

if (!this.api) return '';

const fn = this.version === '2004' ? 'GetValue' : 'LMSGetValue';

return this.api[fn](key);

}

commit() {

if (!this.api) return;

const fn = this.version === '2004' ? 'Commit' : 'LMSCommit';

this.api[fn]('');

}

terminate() {

if (!this.api) return;

const fn = this.version === '2004' ? 'Terminate' : 'LMSFinish';

this.api[fn]('');

}

setCompletion(completed, score) {

if (this.version === '1.2') {

this.setValue('cmi.core.lesson_status', completed ? 'completed' : 'incomplete');

if (score !== undefined) this.setValue('cmi.core.score.raw', score);

} else {

this.setValue('cmi.completion_status', completed ? 'completed' : 'incomplete');

if (score !== undefined) {

this.setValue('cmi.score.raw', score);

this.setValue('cmi.score.scaled', score / 100);

}

}

this.commit();

}

}

```

Common Production Failure Points

The LMS Window Scope Problem

Some LMS platforms load SCORM content in an iframe, others in a popup window. The API object location depends on this:

  • In an iframe: `window.parent.API` or `window.parent.API_1484_11`
  • In a popup: `window.opener.API` or `window.opener.API_1484_11`

You need to traverse the window hierarchy to find the API object:

```javascript

function findAPI(win) {

let attempts = 0;

while (win.API == null && win.API_1484_11 == null && win.parent != null && win.parent != win) {

if (++attempts > 7) break;

win = win.parent;

}

return win.API_1484_11 || win.API || null;

}

```

Commit Timing

SCORM data is only persisted when you call `LMSCommit()` / `Commit()`. If a user closes the browser tab before you call commit, all session progress is lost. Call commit:

  • After every significant progress milestone
  • On the `beforeunload` window event
  • On a 60-second heartbeat timer for long content

The 4096-Character suspend_data Limit

SCORM 1.2's suspend_data field is limited to 4096 characters. If your course has complex bookmark data (module progress, quiz answers, video timestamps), this fills up fast. Strategies:

  • Compress your data before storing (LZ-string compression can reduce JSON by 60–80%)
  • Store only the minimum needed to restore position, not full state
  • Use SCORM 2004 if the LMS supports it - the interaction data model is richer

Cross-Browser and LMS Compatibility Testing

Every LMS implements SCORM slightly differently. Moodle, Cornerstone, SAP SuccessFactors, TalentLMS, and Docebo all have quirks. Test your package against:

  • SCORM Cloud (scorm.com) - the reference implementation for SCORM testing
  • Your client's specific LMS in their actual environment
  • Multiple browsers (Chrome, Safari, Edge)

Modern Alternative: xAPI

If your client's LMS supports xAPI (Tin Can), consider using it instead of SCORM for new content. xAPI advantages:

  • No package structure required - content can live anywhere on the internet
  • Richer data model (tracks any learning experience, not just courses)
  • No 4096-character limits
  • Works offline with LRS sync when connection is restored

The xAPI statement format: `{actor, verb, object}` - e.g., "Deep Singh completed Introduction to Cloud Computing with a score of 87."

xAPI requires a Learning Record Store (LRS) - a backend database for learning statements. Commercial LRS options include Watershed, SCORM Cloud's LRS, and Learning Locker (open source).

When to Use an Authoring Tool vs. Custom Development

Use an authoring tool (Articulate Storyline, Adobe Captivate, iSpring) when:

  • Your content is primarily slides, narration, and quizzes
  • Your instructional designer is non-technical
  • Rapid production of many courses is required

Build custom when:

  • Your content requires simulations, branching scenarios, or interactive elements that authoring tools can't support
  • You need to integrate with external systems (pull real-time data, connect to APIs)
  • The visual quality of authoring tool output is below your standard
  • You're building a course that is itself a software product (not just training material)
1