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

H5P Extractor Laravel Package

snordian/h5p-extractor

A Laravel/PHP package for extracting H5P content packages. It helps developers parse, inspect, and access H5P files, metadata, and assets directly from their applications, simplifying work with interactive H5P content.

View on GitHub
Deep Wiki
Context7

Getting Started

snordian/h5p-extractor is not a Laravel package — there is no service provider, facade, or publishable config. It is a plain PHP library that renders H5P content packages to HTML server-side, and as of 0.4.1 it also generates plain text for every supported content type. You wire it yourself.

Minimal steps:

  1. composer require snordian/h5p-extractor:^0.4.1 — 0.4.x is the first line with plain text generation, so pin the minor if you need it.
  2. Read the repository README and the src/ directory before writing code. With 6 stars and no dependents, docs will be thin and the public API is small — reading two or three class files is faster than guessing. In 0.4.1 specifically, find the text generator entry point: confirm whether it is a separate class, a method on the extractor, or a flag, and whether it returns a string or a structured array of per-library chunks.
  3. Confirm the H5P core PHP library (h5p/h5p-core or the vendored h5p-php-library) is available and at a version compatible with the .h5p packages you will host. The extractor needs the core library to resolve libraries referenced in h5p.json — this applies to text generation too, since it reads the same library metadata.
  4. Provide the extractor with absolute paths to (a) the H5P core library root, (b) the extracted content directory (the folder containing h5p.json and content.json), and (c) a writable temp/cache directory.

First use case: an admin-only "preview" route that takes an uploaded .h5p file, unzips it to storage/app/h5p/{uuid}/, and renders both HTML and plain text so you can eyeball both outputs side by side. This validates your path plumbing before you attempt production features like SEO fallbacks, search indexing, or PDF export.

// app/Services/H5PRenderer.php
namespace App\Services;

class H5PRenderer
{
    public function __construct(
        protected string $corePath,
        protected string $tempPath,
    ) {}

    public function render(string $contentDir): string
    {
        // Instantiate per the README — the constructor/execute shape
        // depends on the version, verify against src/.
        $extractor = new \Snordian\H5PExtractor\Extractor($this->corePath);
        $extractor->setTempPath($this->tempPath);

        return $extractor->extract($contentDir);
    }

    public function toText(string $contentDir): string
    {
        // 0.4.1+ — confirm the method/class name against src/.
        $extractor = new \Snordian\H5PExtractor\Extractor($this->corePath);
        $extractor->setTempPath($this->tempPath);

        return $extractor->extractText($contentDir);
    }
}

Bind it in AppServiceProvider with paths pulled from config/services.php or a dedicated config/h5p.php so you are not hardcoding storage_path() inside the library wrapper.

Implementation Patterns

Wrap it once, inject everywhere. Do not let controllers touch the extractor directly. A single H5PRenderer service makes the untested library swappable and gives you one place to add caching, logging, and exception normalization. This matters more than usual here because the upstream project has no test suite pedigree — you want a single choke point when it misbehaves. In 0.4.x that wrapper exposes two outputs (HTML and text) behind one interface.

Store content packages on the local disk. H5P content must be an extracted directory tree (h5p.json, content.json, content/, H5P.*/ library folders). Even if your source of truth is S3, sync the package to storage/app/h5p/{id}/ before rendering. The extractor will not stream from a flysystem disk.

Cache rendered HTML keyed by content hash. Extraction parses JSON, walks dependency trees, and reads many small files — it is not a per-request operation. Cache the output:

$key = "h5p:render:{$contentId}:".md5_file($contentDir.'/h5p.json').':'.md5_file($contentDir.'/content.json');
return Cache::remember($key, now()->addDay(), fn () => $renderer->render($contentDir));

Invalidate by deleting the prefix when the package is re-uploaded.

Treat plain text as its own cache entry, not a derivative of the HTML. The 0.4.1 text generators are cheap relative to the HTML render but still require parsing every content file. Store text under a separate key (h5p:text:{id}:{hash}) with a longer TTL — text output changes less often than markup and is small enough to keep indefinitely. This also lets you regenerate text without invalidating HTML caches when only the extractor version changes.

Index plain text — this is the headline use case. Before 0.4.1, H5P content was a black box to search: everything lived inside content.json and the client runtime. Now feed the text generator output into Scout/Meilisearch/Algolia alongside your normal model fields:

class H5PContentObserver
{
    public function saved(H5PContent $content): void
    {
        dispatch(new IndexH5PContent($content))->onQueue('h5p');
    }
}

// In the job:
Search::index($content)->with([
    'title' => $content->title,
    'body'  => $renderer->toText($content->directory),
]);

Pair this with a toSearchableArray() on the model and you get real full-text search over Interactive Videos, Course Presentations, and quizzes for the first time.

Use the text output for teasers, summaries, and metadata. A 160-character excerpt for <meta name="description">, an Open Graph blurb, an RSS summary, or a "What's inside" collapsible on a listing page — all of these can now be derived without a second rendering pass. Truncate with Str::limit() and normalize whitespace (preg_replace('/\s+/', ' ', $text)) since generators emitting per-library chunks may leave ragged newlines.

Accessibility: text is a fallback, not a solution. A plain-text dump of an Interactive Video satisfies the "there is a non-visual alternative" bar for many content policies, but it is not a WCAG-conformant equivalent — you lose headings, lists, and answer semantics. If accessibility auditing is the driver, pair the text output with a human-authored summary field and use the generator only as a starting point or a diff check.

Queue bulk operations. PDF exports, newsletter previews, sitemap generation, text re-indexing after a version bump, migration of existing content — push each render onto a job. Horizon + a dedicated queue (h5p) keeps extraction off the web workers, and lets you tune timeout and retry per job.

Use the observer pattern for lifecycle. When an H5P package is uploaded or replaced, dispatch H5PPackageUploaded. A listener handles unzip, dependency validation, and a warm cache job (HTML and text). This keeps upload controllers thin and ensures the cached outputs never lag behind the content.

Distinguish "embed" and "static" rendering. H5P's normal mode requires the h5p.js runtime. The extractor's value is producing markup that stands on its own — for SEO fallbacks, email, or PDF. If you need full interactivity, keep the standard client-side embed path and use the extractor only for <noscript> content or pre-render. Expose two routes: /h5p/{id}/embed (normal) and /h5p/{id}/static (extracted) and decide per consumer.

For PDF and email: after extraction, inline CSS from the referenced stylesheets and rewrite relative asset URLs to absolute (point at your CDN). H5P libraries use relative paths heavily; without rewriting, images and fonts disappear in email clients and PDF renderers. If your email template is text-only, use the 0.4.1 text generator and skip the HTML pipeline entirely — a text part plus the extracted HTML part is a clean multipart email with no iframe and no JS.

Gotchas and Tips

The package is immature — pin the version. With 6 stars, 0 dependents, and a single maintainer, treat releases as potentially breaking. Pin snordian/h5p-extractor and the H5P core library to exact versions in composer.json and only bump deliberately with a test fixture. 0.4.1 is additive on paper, but a 0.x minor bump in a library with no test suite deserves the same review as a major.

"All supported content types" is doing real work in that release note. The 0.4.1 generators cover the content types the maintainer supports — not the entire H5P ecosystem. Any type outside that set will likely return an empty string or fall through silently rather than throwing. Before you index, assert non-empty output and log a warning with the library name and version when you get nothing back, otherwise a whole content type quietly vanishes from search.

Plain text is lossy and unsanitized in a different way than HTML. The text output is not HTML-escaped, but it is also not guaranteed to strip markup-like content that H5P authors typed literally. Always render it with {{ }} in Blade (never {!! !!}), and when persisting to a search index or a database column, apply your normal length and character-set guards — some generators concatenate content files and can produce very long strings for large Course Presentations.

H5P core library version must match your packages. A .h5p file declares preloadedDependencies with library versions (e.g. H5P.InteractiveVideo 1.27). If the core library folder does not contain that exact version, extraction either fails or silently produces partial HTML — and, with 0.4.1, partial or empty text. Maintain a smoke test using a known-good .h5p package (ideally one per content type you host) that asserts both the HTML is non-empty and the text contains an expected phrase. Re-run it in CI after any dependency bump.

Temp directory permissions are a quiet failure mode. Extraction writes intermediate files. On containerized deploys, storage/framework/h5p-temp will not exist or not be writable unless your deploy script creates it. Configure the path explicitly rather than relying on sys_get_temp_dir().

No Laravel auto-discovery. After composer require, nothing is registered. If the library throws Class not found on your first call, you're likely referencing the wrong namespace — check composer.json autoload.psr-4 in the package to confirm.

Output escaping. Extracted HTML contains user-supplied text (H5P authors can put arbitrary content in questions and text). Do not {!! !!} it in Blade if that content crosses trust boundaries. Sanitize with an allowlist (mews/purifier or HtmlSanitizer) at the render boundary. The client-side H5P runtime avoids this because the DOM is sandboxed by the iframe, but the extractor output is raw HTML in your page.

Debugging tips:

  • Log the exact $contentDir and the first 20 lines of h5p.json before extraction — most failures are missing or wrong paths.
  • Diff h5p.json preloadedDependencies against directories actually present in the extracted content; use a one-off command (php artisan h5p:validate {id}) that you keep around.
  • If extraction succeeds but HTML is empty, check content.json content field — some H5P types nest everything and a mismatch in library versions yields an empty render rather than an error.
  • If HTML renders fine but text is empty, the type is probably outside the 0.4.1 supported set. Dump the resolved library name from h5p.json and compare it against the generator list in src/ before assuming a bug.

Extension points. If the extractor lacks a hook you need (asset rewriting, custom cache headers, a text format variant — Markdown rather than raw text), subclass or wrap it rather than forking. Both outputs are strings; post-processing belongs in your wrapper service, not in the library.

Cost. Extraction is CPU-bound and proportional to package size. A large Course Presentation with dozens of images can take hundreds of milliseconds; text generation is typically faster but still walks every content file, so do not generate text on render for a listing page with 50 packages. Never call it synchronously in a request that a user is waiting on for a page load; pre-warm after upload.

Consider whether you need this at all. If you only need H5P on pages where JavaScript runs, the standard h5p-php-library + h5p.js integration is better supported and better documented. Reach for snordian/h5p-extractor specifically when you need output that must exist without client-side JS or must be machine-readable — SEO HTML, PDFs, emails, archiving, or (new in 0.4.1) full-text search over H5P content. That is where the 6-star risk is justified, and search is now the strongest reason to adopt it.

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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle