h5p/h5p-editor
A general PHP library for implementing the H5P editor in most PHP projects. It provides the editor assets and backend needed to create and modify H5P content, with SCSS sources and Compass build instructions for customizing styles.
h5p/h5p-editor (repo: h5p/h5p-editor-php-library) is not a Laravel package. There is no service provider, no config file, no publishable migrations. It is the framework-agnostic PHP backend + JS UI for the H5P authoring experience: it emits the editor HTML, resolves library dependencies, and exposes a set of AJAX endpoints (libraries, content-type-cache, translations, files, save, …). Your job is to wire it into Laravel.
You need three pieces stacked together:
| Piece | Package | Role |
|---|---|---|
| Core | h5p/h5p-core (h5p-php-library) |
Content rendering, library semantics, upgrade scripts |
| Editor | h5p/h5p-editor |
Authoring UI, AJAX endpoints, content-type cache |
| Your code | — | H5peditorStorage + H5PEditorAjaxInterface implementations, routes |
Minimal steps
composer require h5p/h5p-core h5p/h5p-editor.h5peditor.js, styles/, language/, scripts/) into public/vendor/h5p-editor (or symlink). The editor will 404 silently if the base path is wrong.H5peditorStorage — library persistence and language loading.H5PEditorAjaxInterface — content-type cache, translations, recently-used libraries.$core = new \H5PCore($storage, storage_path('h5p'), url('h5p'), 'en', true);
$editor = \H5PEditor::getInstance();
$editor->setStorage($editorStorage);
$editor->setAjax($ajaxImpl);
$editor->setFileStorage(storage_path('h5p/content'));
$editor->setCore($core);
H5PEditorAjax::action($action, $_POST, $_GET).Where to look first: h5peditor-storage.interface.php and h5peditor-ajax.interface.php. Those two files are the entire integration contract. Read them before touching anything else — every other class is either shipped assets or something H5PEditor::getEditor() calls on your behalf.
First use case: let an admin create an "Interactive Video" or "Course Presentation". On save you get a JSON params blob plus a main_library_id/main_library_version pair; that pair plus params is what H5PCore needs to render the finished content later. If you only get the create flow working end-to-end, you've already validated the storage layer, the cache, the file upload path, and the dependency resolver.
Interface implementations backed by Eloquent. The natural mapping:
| Interface method | Table |
|---|---|
saveLibrary / loadLibrary / getLibrary |
h5p_libraries |
getLibraries |
h5p_libraries (filtered by runnable, content_type) |
loadContent / saveContent |
h5p_contents |
getLanguage / getAvailableLanguages |
h5p_libraries_languages |
deleteLibraryUsage |
h5p_libraries_library_dependencies |
Keep the interface signatures exact — H5P passes objects ($library, $content) with public properties, not arrays. Returning a wrong shape produces the classic "Unable to find library" error far downstream.
Editor page. H5PEditor::getInstance()->getEditor($contentId, $contentType, $params, $language) returns a string of HTML + inline <script>. Drop it into an unescaped Blade slot:
<div id="h5p-editor">{!! $editorHtml !!}</div>
Do not wrap it in your own layout's JS bundle ordering — the editor expects its scripts to run last and will attach to #h5p-editor.
Route grouping. Put the authoring routes behind auth + a policy/gate, and deliberately exclude the AJAX route from the web middleware group. The H5P AJAX endpoints are designed to echo JSON and exit; CSRF middleware and session cookie churn fight with that. A thin controller is fine:
Route::post('/h5p/ajax', fn () => app(\H5PEditorAjax::class)->action(
request('action'), request()->all(), request()->query()
))->withoutMiddleware(['web']);
If you must keep web (you usually must, for session-based auth inside the editor), send X-CSRF-TOKEN from the editor and add the route to VerifyCsrfToken::$except.
Content-type cache as a scheduled job. getContentTypeCache() hits the H5P hub over the network. Never call it live in a request path. Store the result in a h5p_content_type_cache table, refresh via php artisan h5p:refresh-cache on a schedule, and serve from the DB in H5PEditorAjaxInterface::getContentTypeCache().
File uploads. H5peditorFile writes to the directory you gave setFileStorage(). It bypasses Laravel's Storage facade entirely — handle permissions and cleanup yourself, and reconcile filenames into your own media table if you need to track ownership.
Save flow. After H5PEditorAjax::save, persist params and the resolved main_library_id / main_library_version. On re-edit, upgrade dependencies first:
$upgradeScript = \H5PCore::getUpgradeScript($currentLib, $targetLib);
Then hand the migrated params to the editor. Skipping this gives you editable-but-broken content after any library update.
Import/export. H5PEditor::exportContent() and the .h5p upload path are the easiest way to seed a fresh environment with content types — far faster than clicking through the hub installer.
language/en.json. Check the Network tab for h5peditor.js and semantics.json 404s first.{"success": false, "message": "..."}. Your generic exception handler will never see them. Log inside your interface implementations, and watch the response body during debugging.H5PEditor::getInstance() is a process-wide singleton. Configure it in a service provider's register(), not per-request in a controller, or you will get "already initialized" behaviour and stale core references under Octane. Bind it as a singleton and resolve it lazily..h5p adds rows to h5p_libraries keyed by (machine_name, major, minor, patch). Dependency errors are usually a missing patch row, not a missing library — check h5p_libraries_library_dependencies before assuming a corrupted install.h5p/h5p-core and h5p/h5p-editor independently. They share the semantics/dependency format. Pin them to compatible ranges in composer.json and test the editor after every bump.hook_h5p_alter_* here. Customize behaviour by decorating H5peditorStorage — getLibraries() with $type filters what appears in the type picker, and alterLibraryFiles()-equivalent logic belongs in your own wrapper around getAssets().H5PDevelopment::MODE_DEV and APP_DEBUG=true when chasing editor-side JS errors; switch off in production or you ship unminified bundles.How can I help you explore Laravel packages today?