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

Headless H5P Laravel Package

escolalms/headless-h5p

Headless H5P for Laravel: manage and deliver interactive H5P content through a backend API without a bundled frontend. Integrates H5P storage, content handling, and endpoints for headless apps.

View on GitHub
Deep Wiki
Context7

Getting Started

This package wraps the H5P PHP core behind a Laravel REST layer, so the "getting started" curve is mostly about H5P's content model rather than Laravel conventions.

Install and bootstrap steps:

composer require escolalms/headless-h5p
php artisan vendor:publish --provider="EscolaLMS\HeadlessH5P\Providers\H5PServiceProvider"
php artisan migrate

Confirm auto-discovery picked up the provider, then check config/headless-h5p.php (or the equivalent published config) for:

  • Storage disk — where the H5P libraries and content files live. Point this at a persistent disk (S3 works, but read the gotchas below).
  • Auth guard — EscolaLMS packages typically assume Sanctum/Passport tokens; wire it to whichever the host app uses.
  • Route prefix — the API usually exposes /api/h5p/...; verify against your host app's route groups.

Where to look first: the package's routes/ directory (this is the source of truth for endpoints — the README drifts), then the H5PContent model, then the migrations so you understand the content/library schema. H5P stores content as opaque JSON params plus a library_id reference, so don't expect readable columns.

First real use case: build the authoring-to-playback loop end-to-end before touching anything else.

  1. GET /api/h5p/libraries → enumerate installed content types.
  2. POST /api/h5p/content with {library, params, ...} → create a content instance.
  3. GET /api/h5p/content/{id} → fetch params.
  4. Hand the params + library to your frontend H5P player (e.g. @lumieducation/h5p-webcomponents or the H5P standalone player) to render.

If step 3 returns content but step 4 fails to render, that's almost always a library asset URL problem, not a data problem — see Gotchas.

Implementation Patterns

Authoring via .h5p upload (the common path). Most real integrations don't create content via JSON params — they upload an .h5p file (a zip of h5p.json + content/ + library dependencies) exported from the H5P editor or H5P.org. The package exposes an import endpoint that unpacks and registers the content. Pattern:

// typical controller flow in a host app
$response = Http::withToken($token)
    ->attach('file', $request->file('h5p'), 'content.h5p')
    ->post("/api/h5p/content/import");
$contentId = $response->json('id');

Treat the import as the source of truth — re-importing is idempotent enough for content migration, but not for updates. Edits go through the content update endpoint, not re-import.

Wrapping content in a domain resource. Because H5P content is opaque, the day-to-day pattern is to hang your own model off it:

class LessonActivity extends Model
{
    public function h5pContent()
    {
        return $this->belongsTo(H5PContent::class, 'h5p_content_id');
    }
}

Store the ID returned by the API, keep the H5P payload at arm's length, and let the host app own the semantics (which lesson, which user, completion rules). Don't try to query inside params.

Serving the player. Headless means the package does not ship a JS player. Your frontend needs an H5P player bundle pointed at a base URL that resolves H5P core JS/CSS and library assets. The reliable pattern is a small controller in the host app that proxies or serves:

  • /h5p/core/* → H5P core JS/CSS (from the package's core/editor assets)
  • /h5p/libraries/* → installed library assets
  • /h5p/content/{id}/... → content files

If the package ships a H5PController::serveAssets()-style route, use it; if not, implement the above and keep the disk layout in sync with the config. CORS must allow the player origin.

API token / middleware integration. EscolaLMS conventions mean the package's routes are wrapped in middleware groups you control. For a headless API consumed by an SPA:

// config/headless-h5p.php or a route group in the host app
'middleware' => ['api', 'auth:sanctum'],

Public playback endpoints (students retrieving content to render) often need to be unauthenticated or use signed URLs — split those from authoring endpoints explicitly.

Library management as a deployment concern. Installing H5P content types is not an API call in most setups — it's dropping library folders into the libraries disk. Pattern this into your deploy pipeline:

# example: sync libraries from a known-good snapshot
rsync -a --delete storage/h5p-libraries/ /path/to/persistent/h5p/libraries/
php artisan h5p:cache-libraries   # whatever cache/refresh command ships

Version-pin your library set. A content item authored against H5P.InteractiveVideo 1.24 will not render if only 1.22 is installed.

Testing without a browser. Since the API returns JSON params, assert on the envelope, not the interaction:

$response->assertCreated()
    ->assertJsonStructure(['id', 'library', 'params']);

For playback regressions, snapshot the rendered params JSON and diff on upgrade.

Gotchas and Tips

Library dependencies are the #1 failure. H5P libraries have a library.json graph of dependencies. Install a leaf library without its dependencies and the player silently renders a blank box (or throws Missing dependency in the browser console). When adding a content type, copy the whole dependency tree — easiest is to export a sample .h5p containing it and import it, which pulls in the dependencies the authoring environment bundled.

Storage disk: S3 is fine for content, painful for libraries/player assets. H5P assets are requested by relative paths with specific MIME types and cache headers, and the player does a lot of small requests. Local (or a CDN in front of local) is far less friction. If you must use S3, front it with CloudFront and ensure Access-Control-Allow-Origin is set on the asset responses; otherwise the JS player fails with opaque CORS errors.

Config caching bites after publish. php artisan config:cache + a stale headless-h5p.php is a common "why is it writing to the wrong disk" bug. Clear on deploy. Also: the package likely reads config('filesystems.disks.<x>') by name, so if you rename disks, update both sides.

Permissions and migrations ordering. EscolaLMS packages share a permission layer (spatie/laravel-permission). Migrations that register permissions can fail if run before the permissions table exists. Run host-app migrations first, or use the package's documented seed order. If you see a "permissions table not found" error, that's the cause.

.h5p uploads need real zip handling and generous limits. A course-presentation .h5p can be 100MB+ of video. Check upload_max_filesize, post_max_size, and the package's own max_upload_size config. On S3, ensure the upload path is a direct server upload, not a presigned URL that skips validation — H5P validation (the zip manifest check) needs to run server-side.

Content type updates break old content — silently. H5P is versioned per content type but upgrades can change params semantics. After bumping a library, re-import or re-open each affected content item in the editor and export. There's no schema migration for params. Keep a database snapshot before library upgrades.

Auth on content endpoints is inconsistent by default. Playback endpoints in these packages are frequently left unauthenticated so any client can fetch content for the player. Audit the routes file — if student content is sensitive (exams, gated lessons), add explicit authorization in a policy or a middleware that checks the content ID against the user's enrollment.

Route conflicts with the host app. A generic prefix like /api/h5p can collide if the host app also mounts it. Register the package routes after your own, or namespace under a version. Check php artisan route:list after install — that's the cheapest debugging tool in this package.

Extension point: the content model. If you need custom metadata (e.g. SCORM/xAPI bridge, completion tracking), extend the package's content model or use a side table keyed by content ID. Don't fork the package — its content schema changes between versions. Listen for the package's events (creation/update) and mirror to your own tables instead.

Debugging: H5P errors are usually surfaced in the browser, not the API. Set the player's debug mode on the frontend and check the network tab for 404s on /h5p/libraries/... and /h5p/core/.... On the PHP side, the package logs under the app's default channel — a missing library asset that 404s at playback will not appear in Laravel logs, so instrument your asset-serving route if you hit issues.

Headless caveat on the editor. The package's API is content-storage and playback focused; the H5P editor (the drag-and-drop authoring UI) is a heavier, stateful frontend and is not what "headless" gives you. Plan for authoring to happen in H5P.org / H5P editor exports, or invest in a separate editor frontend. Don't assume the REST API can replace authoring tooling.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource