Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Scorm Laravel Package

devianl2/laravel-scorm

Laravel SCORM helps integrate SCORM e-learning content into Laravel applications, with tools for uploading and serving packages, managing user attempts, and tracking completion, scores, and runtime data through a simple API.

View on GitHub
Deep Wiki
Context7

Getting Started

The package ships a Laravel-flavoured SCORM 1.2 runtime and content model: it gives you somewhere to store packages, a way to serve the launched SCO back to the browser, and an HTTP endpoint that the bundled JavaScript API adapter talks to when content calls LMSInitialize, LMSSetValue, LMSCommit and LMSFinish.

  1. Install and scaffold:

    composer require devianl2/laravel-scorm
    php artisan vendor:publish --provider="Devianl2\Scorm\ScormServiceProvider"
    php artisan migrate
    

    Check the published config/scorm.php (or equivalent) for the storage disk, route prefix and middleware before writing any code — most integration pain is configured there.

  2. Read four things before anything else:

    • The package migrations. They tell you the real data model — usually a scorms table (the package) and a scos/scorm_scos table (individual launchable items parsed from imsmanifest.xml), plus a tracking table keyed by user/enrollment.
    • The routes file. Note the URL the player and the runtime API are exposed on; you will need to put auth middleware in front of both.
    • The player view/blade component. That is where the iframe and the JS API object are wired up; it's your main extension point.
    • The manifest parser. Everything about how a zip becomes SCO rows lives there.
  3. First use case — get one course working end to end:

    • Export a small course from Articulate Storyline or Adobe Captivate as SCORM 1.2 (start with 1.2, not 2004; 1.2 is far better supported by this generation of packages).
    • Upload the zip through whatever upload route/controller the package exposes, or create the Scorm record yourself and point it at an extracted directory.
    • Launch the player view as a logged-in user, complete the course, and confirm a row appears in the tracking table with lesson_status set to completed or passed.

Once that loop works, everything else is reporting and hardening.

Implementation Patterns

Treat ingestion as a pipeline, not a controller action. The typical flow is: receive zip → validate that it contains imsmanifest.xml at the archive root → store the archive on a private disk → extract to a per-package directory → parse the manifest → create Scorm + Sco rows → store the launch path of the default organization's first item. Move extraction and parsing into a queued job. Real SCORM packages are 50–500 MB and full of thousands of small files; doing this inline will time out behind PHP-FPM.

Serve content through a route, not public/storage. SCORM assets (.js, .css, .html, .xml, fonts, audio) need correct MIME types and must be gated by the same authorization as the launch. Either use a controller that streams files with response()->file() after a policy check, or symlink a per-tenant directory and put auth on the launch URL only. Streaming is slower but lets you enforce enrollment per request.

Model attempts explicitly. The package's tracking tables usually key progress by SCORM + user. In production you want an attempt (or enrollment) concept layered on top, so a learner can retake a course without overwriting the first attempt. Add your own table referencing scorm_id, user_id, attempt_no, and store the CMI data blob there. Keep the package's table as the "current attempt" pointer if rewriting is too invasive.

Decide between a raw CMI blob and normalized columns. SCORM's data model is ~40 fields in 1.2 and ~80+ in 2004. Storing the whole CMI object as JSON on each LMSCommit is simplest and lossless; add generated columns or a small denormalized set (lesson_status, score_raw, score_min, score_max, session_time, last_accessed) for reporting queries. Do both: JSON for fidelity, columns for dashboards.

Wire the runtime API to your auth. The bundled JS adapter posts {method, parameter, value} payloads. Back it with a route behind auth middleware and resolve the attempt server-side from the session — never trust a user_id or attempt_id sent from the client. The iframe is same-origin in the default setup, so session cookies work; if you host content on a separate domain you'll need to handle SameSite and CORS deliberately.

Integration points that pay off:

  • Listen for the package's completion/commit events (if any) or dispatch your own from the API controller's LMSFinish/commit handler to update your own enrollment/progress tables.
  • Wrap the Scorm model in a repository or your own subclass so reporting code doesn't reach into package internals — you'll want to fork or replace parts of this package eventually.
  • Multi-tenant: namespace extracted content and DB rows by tenant, and override the storage path resolver in config rather than duplicating the package.
  • Re-uploading a new version of a course: keep scorm_id stable and create a new revision/SCO set; learners mid-attempt will otherwise lose their suspend_data.

Gotchas and Tips

The package is stale. Last release was September 2022, ~48 stars. Assume Laravel 9-era constraints. Before committing, verify composer constraints against your Laravel and PHP versions (composer why-not devianl2/laravel-scorm), and expect to vendor/fork it. Budget a day to patch service provider bindings, deprecated Str/Arr helpers, and facade signatures. Treat it as a starting implementation, not a maintained dependency.

Zip extraction is the biggest security surface. Guard against zip-slip (../ entries), absolute paths, and zip bombs (cap uncompressed size and file count). Reject archives without a root imsmanifest.xml, and normalize case — SCORM packages authored on Windows frequently reference Storyline Output/index.html with mismatched casing, which breaks on Linux.

SCORM 1.2 vs 2004 are different data models. 1.2 uses cmi.core.lesson_status (passed, completed, failed, incomplete, browsed, not attempted) and cmi.core.score.raw. 2004 uses cmi.completion_status and cmi.success_status, plus cmi.score.scaled. If the package was written for 1.2, 2004 content will silently fail to record completion — you'll need to translate or add a version flag and branching in the API handler.

cmi.suspend_data is bounded. 1.2 allows 4096 characters; many authoring tools pack bookmarks and quiz state into it. Store it as TEXT/LONGTEXT, never VARCHAR(255), and don't truncate silently on commit — log it, because a truncated bookmark means learners restart from the beginning.

Cross-frame API discovery. The runtime adapter must be reachable from the content iframe by walking window.parent/window.top looking for an API object. If your player view doesn't expose it before the content's window.onload fires, you'll see "SCORM API not found" and zero tracking. Load the adapter script in the player template synchronously, ahead of the iframe.

Auth inside the iframe. If the launch URL sits behind auth and the session cookie is SameSite=Strict, subsequent asset and API requests from the iframe can 401/419. Test the full launch loop in an incognito window with a fresh login, and check the network tab for CSRF 419s on the commit endpoint (add the XSRF token or exclude the route).

MIME types matter. Serving .js as text/html or .json as text/plain breaks LMS-less players that sniff content. If you stream files, set Content-Type from the file extension explicitly rather than letting Symfony guess.

Debugging playbook: log every runtime API call (method, parameter, value, timestamp) to a dedicated channel for a single attempt. Nine times out of ten the fix is that content called LMSSetValue('cmi.core.lesson_status', 'completed') without a following LMSCommit, or the player navigated away before LMSFinish.

Extension points to lean on: published config, published views/blade player template, the service provider bindings, and the manifest parser — override the parser in the container if you need to support multi-SCO courses or custom organization selection. Anything else, fork.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo