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

Start free →

SCORM integration and cross-domain problems

SCORM integration fails when a course and the LMS sit on different domains. Why the browser blocks the API search, and the proxy and bridge fixes.

How SCORM integration finds the LMS

SCORM integration is a JavaScript link inside the browser, not a server connection. The LMS places an API object on its own page, and the course searches its parent and opener windows to find it. If the course is served from a different domain than that page, the browser blocks the search and nothing is tracked.

The object is called API in SCORM 1.2 and API_1484_11 in SCORM 2004. The course does not know where it is; it only knows the name. So every SCORM course carries a small discovery routine that walks up the chain of parent frames, and then tries the window that opened it, looking for that name:

function findAPI(win) {
  var tries = 0;
  while (win.API == null && win.parent != null && win.parent != win) {
    if (++tries > 7) return null;
    win = win.parent;
  }
  return win.API;
}
var api = findAPI(window) || (window.opener ? findAPI(window.opener) : null);

Once the object is found, everything else follows: Initialize, GetValue, SetValue, Commit, Terminate. If it is not found, none of those calls can be made. The full sequence is covered in the SCORM API explained. The point here is narrower: the whole standard rests on one page being allowed to read a JavaScript object that lives in another window.

Why does a different domain break it?

Because reading an object in another window is exactly what browsers refuse to do across domains. The discovery routine is allowed to hold a reference to window.parent, but the moment it reads parent.API on a window served from somewhere else, the browser throws a security error instead of returning the object. In the console it looks something like this:

SecurityError: Blocked a frame with origin "https://cdn.example.net"
from accessing a cross-origin frame.

What the learner sees depends on how the course handles that error. Some courses show an "unable to find the API" message. Many catch the error quietly and carry on as if they were running offline. The learner has a normal experience, finishes the course, and the LMS records nothing, because no call ever reached it.

This usually appears when someone separates the content from the LMS on purpose: the course files are moved to a content delivery network, a media server, or a supplier's hosting, while the LMS page that holds the API stays where it was. The files load perfectly. Only the tracking is gone.

The same-origin policy in plain terms

The rule the browser is enforcing is the same-origin policy. An origin is three things together: the scheme (https), the host name, and the port. Two pages can reach into each other's JavaScript only if all three match.

LMS pageCourse pageSame origin?
https://lms.example.comhttps://lms.example.com/content/course1/Yes. The path does not matter
https://lms.example.comhttps://content.example.comNo. A different subdomain is a different host
https://lms.example.comhttp://lms.example.comNo. The scheme differs
https://lms.example.comhttps://lms.example.com:8443No. The port differs
https://lms.example.comhttps://cdn.example.netNo

Two points catch people out. First, a subdomain of the same company is still a different origin. Second, CORS headers do not fix this. CORS controls whether a script may read the response to a network request. It has no say over whether one window may read another window's objects, so adding Access-Control-Allow-Origin to the content server changes nothing for SCORM.

There used to be a workaround for sibling subdomains: both pages set document.domain to the shared parent domain and the browser treated them as one origin. Browsers are phasing that out, so it should not be relied on for anything new.

Proxy and same-domain hosting

The simplest answer is to make the problem disappear: serve the course from the same origin as the LMS page that holds the API. There are two ways to get there.

  • Host the content on the LMS itself. This is what a normal SCORM import does. The zip is unpacked into the LMS's own storage and served from its own address, which is why most courses never meet this problem.
  • Put a reverse proxy in front of the remote content. The files stay on the other server, but the LMS's web server is configured so that a path such as /content/ on the LMS domain quietly fetches from it. The browser only ever sees the LMS address, so the origins match and the discovery routine works unchanged.

The proxy route keeps the course code untouched, which is its main attraction. Its cost is that it needs access to the LMS's server configuration. On a hosted LMS run by someone else, that is often not available, and then the only remaining option is a bridge.

postMessage bridges

Browsers provide one sanctioned way for windows on different origins to talk: window.postMessage. One window sends a message, the other receives it as an event and can check which origin it came from. A SCORM bridge uses that channel to relay API calls in two halves:

  1. A small launcher page is imported into the LMS as an ordinary SCORM package. Because the LMS serves it, it is on the LMS origin and can find the real API.
  2. The launcher opens the remote course in a frame. On the course side, a stub object named API or API_1484_11 stands in for the real one.
  3. When the course calls the stub, the stub posts a message to the launcher, and the launcher makes the real call and posts the result back.

This is the same thin-launcher shape described in SCORM dispatch explained, and it is how remotely hosted content is usually made to report into an LMS it does not live on.

The catch is timing. The SCORM API is synchronous: GetValue must return its answer immediately. postMessage is asynchronous: the answer arrives later. A bridge therefore has to read the values the course will need up front, answer GetValue from that local copy, and queue the writes. If the learner closes the window before the queued messages are delivered, the last writes can be lost.

Two checks matter in any bridge. Both sides should verify the origin of every incoming message and name the exact target origin when sending, rather than using a wildcard. And the course should commit as it goes, so that the final status is not sitting in a queue when the window closes.

How to test for the problem

A cross-domain failure is quick to confirm with the browser's developer tools.

  1. Compare the two addresses. Note the scheme, host and port of the LMS page, then inspect the course frame and note the same three parts of its address. If any differ, you have a cross-origin launch.
  2. Read the console. Launch the course with the console open and look for a security error mentioning a cross-origin frame, or the course's own "API not found" message.
  3. Ask for the object directly. Switch the console's context to the course frame and evaluate window.parent.API (or API_1484_11). A security error, rather than an object, confirms the cause.
  4. Watch the network tab. Most LMSs send a request to their server when a course commits. Complete a page or two. If no such request appears, nothing is being saved.
  5. Relaunch. Close the course and open it again. A course that restarts from the first page with a status of not attempted never made contact.

Two neighbouring problems are worth ruling out at the same time. A course loaded over http inside an https LMS page is blocked as mixed content before any script runs. And a course on another domain that depends on its own cookies may find the browser treats them as third-party and withholds them. If the origins do match and tracking still fails, work through the wider checklist in SCORM not working in your LMS.

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 — Testing & Troubleshooting

Testing & Troubleshooting4 min SCORM conformance testing explained What a SCORM test suite checks, why a conformant package can still fail in your LMS, and the lighter checks you can run yourself before upload. Testing & Troubleshooting5 min What does SCORM compliant content mean? SCORM compliant content meets two rules: a zip with a valid imsmanifest.xml, and a SCO that calls the LMS API. How to check a vendor compliance claim. Testing & Troubleshooting6 min SCORM mobile compatibility: what breaks SCORM mobile compatibility fails in predictable ways: lost commits when unload never fires, fixed layouts, and blocked autoplay. How to fix each.
Testing & Troubleshooting

Test your SCORM package free.

Upload the zip and watch every runtime call it makes — Initialize, SetValue, Commit — plus manifest checks, before your LMS sees it. No account.

→ upload  course-scorm2004.zip
  manifest … valid (2004 4th Ed)
  Initialize("") → "true"
  SetValue cmi.score.scaled → "0.84"
  Commit("") → "true"
✓ 0 errors, 2 notes