LMS Platform Development with SCORM Support

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1 servicesAll 2065 services
LMS Platform Development with SCORM Support
Complex
from 2 weeks to 3 months
FAQ
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1043
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815

SCORM Support in LMS

SCORM (Sharable Content Object Reference Model) is a standard for packaging e-learning content. Most corporate courses created in Articulate Storyline, Adobe Captivate, iSpring are SCORM packages. An LMS must be able to upload them, run them in an iframe, and get progress data.

What is a SCORM Package

A SCORM package is a ZIP archive with an imsmanifest.xml file and HTML/JS/media files of the course. Versions: SCORM 1.2 (most common) and SCORM 2004 (4th edition).

Package structure:

course.zip
├── imsmanifest.xml        # metadata, structure
├── index.html             # course entry point
├── scorm_api.js           # SCORM API implementation
└── content/
    ├── slide1.html
    ├── media/
    └── ...

SCORM API — Bridge Between Course and LMS

The course communicates with the LMS via JavaScript API. The LMS creates a global object API (SCORM 1.2) or API_1484_11 (SCORM 2004) in the window where the iframe is running:

// SCORM 1.2 API object — created on LMS page
class ScormApi12 {
  private lessonStatus = 'not attempted';
  private suspendData = '';
  private score = 0;
  private sessionTime = '';
  private dataStore = new Map<string, string>();
  private onComplete: (data: ScormData) => void;

  constructor(onComplete: (data: ScormData) => void) {
    this.onComplete = onComplete;
  }

  LMSInitialize(_: string): string {
    this.lessonStatus = 'incomplete';
    return 'true';
  }

  LMSGetValue(element: string): string {
    switch (element) {
      case 'cmi.core.lesson_status': return this.lessonStatus;
      case 'cmi.suspend_data': return this.suspendData;
      case 'cmi.core.score.raw': return String(this.score);
      case 'cmi.core.lesson_location': return this.dataStore.get('lesson_location') ?? '';
      default: return this.dataStore.get(element) ?? '';
    }
  }

  LMSSetValue(element: string, value: string): string {
    switch (element) {
      case 'cmi.core.lesson_status':
        this.lessonStatus = value;
        break;
      case 'cmi.suspend_data':
        this.suspendData = value;
        break;
      case 'cmi.core.score.raw':
        this.score = Number(value);
        break;
      case 'cmi.core.session_time':
        this.sessionTime = value;
        break;
      default:
        this.dataStore.set(element, value);
    }
    return 'true';
  }

  LMSCommit(_: string): string {
    // Send data to server (throttle — no more than once per 5 seconds)
    this.saveProgress();
    return 'true';
  }

  LMSFinish(_: string): string {
    this.onComplete({
      status: this.lessonStatus,
      score: this.score,
      suspendData: this.suspendData,
      sessionTime: this.sessionTime,
    });
    return 'true';
  }

  LMSGetLastError(): string { return '0'; }
  LMSGetErrorString(_: string): string { return 'No error'; }
  LMSGetDiagnostic(_: string): string { return ''; }

  private async saveProgress() {
    await fetch('/api/scorm/progress', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        status: this.lessonStatus,
        score: this.score,
        suspendData: this.suspendData,
      }),
    });
  }
}

API Injection into iframe

The course looks for API in parent windows (window.parent.parent...). The LMS sets the object before loading the iframe:

function ScormPlayer({ courseId, enrollmentId }) {
  const iframeRef = useRef<HTMLIFrameElement>(null);

  useEffect(() => {
    // Set SCORM API on current window — iframe will find it via parent
    const api = new ScormApi12(async (data) => {
      await fetch(`/api/enrollments/${enrollmentId}/complete`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });
    });

    // SCORM 1.2
    (window as any).API = api;
    // SCORM 2004
    (window as any).API_1484_11 = api;

    return () => {
      delete (window as any).API;
      delete (window as any).API_1484_11;
    };
  }, [enrollmentId]);

  return (
    <iframe
      ref={iframeRef}
      src={`/api/courses/${courseId}/launch`}
      className="w-full border-0"
      style={{ height: 'calc(100vh - 64px)' }}
      allow="camera; microphone; fullscreen"
      title="SCORM Course"
    />
  );
}

Uploading and Extracting SCORM Package

import AdmZip from 'adm-zip';
import { parseStringPromise } from 'xml2js';

app.post('/api/courses/upload', authenticate, upload.single('scorm'), async (req, res) => {
  const zipBuffer = req.file!.buffer;
  const zip = new AdmZip(zipBuffer);

  // Extract to storage (S3 or local)
  const courseId = crypto.randomUUID();
  const extractPath = `/courses/${courseId}`;

  zip.extractAllTo(path.join(process.env.STORAGE_PATH!, extractPath), true);

  // Parse manifest
  const manifestEntry = zip.getEntry('imsmanifest.xml');
  if (!manifestEntry) throw new Error('Invalid SCORM package: no imsmanifest.xml');

  const manifest = await parseStringPromise(manifestEntry.getData().toString());
  const title = manifest.manifest.organizations[0].organization[0].title[0];
  const launchUrl = manifest.manifest.resources[0].resource[0]['$']['href'];
  const scormVersion = manifest.manifest['$']['version']?.includes('1.2') ? '1.2' : '2004';

  const course = await db.courses.create({
    id: courseId,
    title,
    launchUrl: `${extractPath}/${launchUrl}`,
    scormVersion,
    uploadedBy: req.user.id,
  });

  res.json(course);
});

Storing Progress

CREATE TABLE scorm_progress (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  enrollment_id UUID REFERENCES enrollments(id),
  lesson_status VARCHAR(50),  -- 'passed' | 'failed' | 'completed' | 'incomplete'
  score NUMERIC(5,2),
  suspend_data TEXT,          -- for bookmarks and course state
  session_time INTERVAL,
  completed_at TIMESTAMPTZ,
  updated_at TIMESTAMPTZ DEFAULT now()
);

SCORM 1.2 vs SCORM 2004

Parameter SCORM 1.2 SCORM 2004
API object window.API window.API_1484_11
Statuses passed/failed/completed/incomplete passed/failed/completed/incomplete/not attempted/unknown
Score 0–100 0.0–1.0 (min/max/raw)
Progress suspend_data suspend_data + adl.nav
Prevalence Wide Less

Timeframe

SCORM 1.2 support with package upload, API object, and progress storage — 1–1.5 weeks. With SCORM 2004 support — additional 3–5 days.