New: the SCORM Central WordPress plugin. Put a course on any WordPress page and see who finished. Get the plugin →

Start free →

How to convert HTML5 content to SCORM

HTML to SCORM conversion takes two additions: an imsmanifest.xml that describes the package, and a few API calls that report completion to the LMS.

What does SCORM need from HTML5 content?

HTML to SCORM conversion takes two additions. First, package the HTML5 files as a zip with an imsmanifest.xml at the root that names the start page as a SCO. Second, have that start page find the LMS API, call Initialize, record completion, then Commit and Terminate. The content itself does not change.

That works because an LMS never interprets the content of a SCORM package. It unpacks the zip, reads the manifest to learn which page to open, and serves the files in a frame or a new window. Anything that runs in a browser can be that content: a single page, a set of linked pages, a JavaScript application, or an animation exported from a design tool. SCORM only cares about the wrapping around it and the handful of calls the page makes while it runs.

Decide the SCORM version before you start, because the two requirements are spelled differently in each. SCORM 1.2 and SCORM 2004 do the same jobs under different names, and a package must use one set consistently.

JobSCORM 1.2SCORM 2004
Mark the resource as a SCOadlcp:scormtype="sco"adlcp:scormType="sco"
API object to findAPIAPI_1484_11
Start the sessionLMSInitialize("")Initialize("")
Record completioncmi.core.lesson_statuscmi.completion_status
SaveLMSCommit("")Commit("")
End the sessionLMSFinish("")Terminate("")

Writing the imsmanifest.xml

The manifest is an XML file named exactly imsmanifest.xml. It has to declare three things: an organization, an item inside it, and a resource the item points to. The resource carries the launch href, the SCO marker, and the list of files the content uses. A minimal SCORM 1.2 manifest for a one-page course looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<manifest identifier="fire-safety-course" 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="org1">
    <organization identifier="org1">
      <title>Fire safety</title>
      <item identifier="item1" identifierref="res1">
        <title>Fire safety</title>
      </item>
    </organization>
  </organizations>
  <resources>
    <resource identifier="res1" type="webcontent"
      adlcp:scormtype="sco" href="index.html">
      <file href="index.html"/>
      <file href="css/course.css"/>
      <file href="js/course.js"/>
    </resource>
  </resources>
</manifest>

Read it from the middle outwards. The item's identifierref must match the resource's identifier: that link is how the LMS gets from the course outline to a page it can open. The resource's href is the start page, given as a path relative to the manifest. Each file element lists a file the content uses.

For SCORM 2004 the shape is the same and three details change: the namespaces become the IMS imscp_v1p1 and ADL adlcp_v1p3 ones, schemaversion names the edition (for example 2004 4th Edition), and the marker is written adlcp:scormType with a capital T. XML is case-sensitive, so the two spellings are not interchangeable. Every element and attribute is walked through in imsmanifest.xml explained.

Use relative paths everywhere. The LMS decides where the package is unpacked, so the content cannot know its own address in advance. A link such as /css/course.css or a full URL back to the site the pages came from will break or load from outside the package. Write css/course.css instead, and copy fonts, scripts and media into the package if the course must work without reaching another server.

Adding the SCORM API calls

When the LMS launches the start page, it has already placed an API object in a window above it. The page's first job is to find that object. The specification describes the search: look at the current window, walk up through its parents, and if nothing is found, repeat the walk from the window that opened this one. For SCORM 2004:

function findAPI(win) {
  var tries = 0;
  while (win && !win.API_1484_11 &&
         win.parent && win.parent !== win && tries < 10) {
    win = win.parent;
    tries++;
  }
  return (win && win.API_1484_11) ? win.API_1484_11 : null;
}

var api = findAPI(window);
if (!api && window.opener) {
  api = findAPI(window.opener);
}
if (api) {
  api.Initialize("");
}

For SCORM 1.2, look for API instead and call LMSInitialize(""). Either way the call takes an empty string and returns the string "true" or "false", not a boolean. Call it once per launch. A second call in the same session is an error.

You do not have to write this by hand. A wrapper library handles the discovery and gives you one set of function names for both versions, as described in what a SCORM wrapper does. It is still worth knowing what the wrapper is doing, because "API not found" is the usual reason a converted page runs normally and records nothing.

If the content is several HTML pages, think about which page holds the session. A script is discarded when the browser navigates to another page, and the next page must not call Initialize again. The simplest reliable pattern is one start page that stays loaded for the whole session and shows the other pages inside itself, in an iframe or by swapping content with JavaScript. That one page makes every SCORM call.

Reporting completion

The LMS knows nothing about what happens inside the page unless the page says so. Decide what "finished" means for this content, such as reaching the last screen, watching a video to the end or passing a quiz, and report it at that moment:

function finishCourse() {
  if (!api) { return; }
  api.SetValue("cmi.completion_status", "completed");
  api.Commit("");
}

function exitCourse() {
  if (!api) { return; }
  api.Terminate("");
  api = null;
}

In SCORM 1.2 the same step is LMSSetValue("cmi.core.lesson_status", "completed"), followed by LMSCommit("") and, on exit, LMSFinish("").

Three details matter here:

  • Commit straight after the status. SetValue hands the value to the API. Commit asks the LMS to store it. A status that was set but not committed can be lost if the learner closes the window.
  • Do not leave Terminate to the browser's unload event alone. Unload handlers are unreliable, especially on mobile. Give the course its own exit button that calls Terminate, and treat unload as a backup.
  • Add a score only if the content has one. SCORM 2004 takes cmi.score.scaled and a separate cmi.success_status of passed or failed. SCORM 1.2 takes cmi.core.score.raw and folds passed or failed into cmi.core.lesson_status. Write the score before the status.

Every value is passed as a string, and every call returns one. If a call returns "false", GetLastError() (LMSGetLastError() in 1.2) gives the error code.

Zipping and testing the package

Zip the contents of the course folder, not the folder itself. The manifest must sit at the root of the zip. If you compress the parent folder, the manifest ends up one level down, at course/imsmanifest.xml, and the LMS will not find it. This is the most common reason a hand-built package is refused on import.

Before you upload, check the basics:

  1. imsmanifest.xml is at the top level of the zip and is well-formed XML.
  2. The resource href and every file entry match real files, including upper and lower case. Many servers treat Index.html and index.html as different files.
  3. The manifest and the script use the same SCORM version. A 1.2 manifest with 2004 API calls imports cleanly and then records nothing.

Then run it somewhere that speaks SCORM. Opening index.html from your desktop proves the content works. It proves nothing about tracking, because there is no API to find. In the destination LMS or a test player, confirm that Initialize returns "true", finish the course, exit, and look at the record. Then launch it a second time to see what a returning learner gets.

The specification sets these requirements, but import checks vary from one LMS to another. Some validate the manifest strictly and some accept nearly anything, so the LMS the course is going to is the test that counts. To compare your result with a known-good layout, see the SCORM package example.

Where SCORM Central fits

SCORM Central turns PowerPoint, PDF, and video files into SCORM 1.2 or SCORM 2004 4th Edition packages you can edit in its course builder, and its free tester shows the manifest checks and every runtime call a package makes before an LMS sees it. See how it works, or test a package free.

Keep reading — SCORM Conversion

SCORM Conversion6 min SCORM viewer: how to open a SCORM file A SCORM viewer has to supply the SCORM API, or nothing is tracked. How to unzip a SCORM file, find its launch page in the manifest, and view it properly. SCORM Conversion5 min How to convert PowerPoint to SCORM without losing the tracking that makes it worth doing Exporting slides to a zip is easy. Getting a package that reports completion correctly, keeps its bookmark, and imports cleanly on the LMS you were given is the actual job. A step-by-step guide. SCORM Conversion4 min Convert SCORM to video or PowerPoint? Can you convert SCORM to MP4, HTML5 or PowerPoint? What is inside a package, the routes that work, and what you lose with each one.
SCORM Conversion

Convert a file you already have.

Upload a PowerPoint, PDF, or video, edit it as a course, build it as a SCORM 1.2 or 2004 package, and check it in the free tester.

→ upload  handbook.pdf
  rendering … 86 pages
  course … 1 lesson, 86 pages
  building … SCORM 2004 4th Ed
  validating … 0 errors
✓ handbook-scorm2004.zip