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

Pdfjs Bundle Laravel Package

nti/pdfjs-bundle

nti/pdfjs-bundle integrates Mozilla's PDF.js into Symfony apps, adding a controller, route and Twig helper to display PDF documents in the browser with no plugins or external viewers required. Configurable viewer options let you control toolbar, zoom and rendering behavior per file.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're getting. This is a Symfony bundle that wires Mozilla's PDF.js viewer into a Symfony app — it does not parse, extract, or manipulate PDFs server-side. If your requirement is "show a PDF in the browser with page navigation, zoom, and text selection," it's the right shape of tool. If you need search indexing, form filling, or PDF generation, look elsewhere.

Install and register. With Symfony Flex there is almost certainly no recipe for this package, so registration is manual:

composer require nti/pdfjs-bundle
// config/bundles.php
NTI\PdfJsBundle\NTIPdfJsBundle::class => ['all' => true],

Publish the assets before anything else. PDF.js ships as static JS (the viewer plus pdf.worker.js). Bundles of this kind almost always expect you to run bin/console assets:install --symlink (or copy into public/bundles/...). If the page renders blank, this is the first thing to check.

Read the README for the one thing that matters: the Twig function signature. The entire day-to-day value of the bundle is a single helper — something like {{ pdfjs_render(asset('doc.pdf')) }} or a controller-driven variant. Confirm the exact name, its arguments, and the config key namespace (config/packages/nti_pdf_js.yaml) before writing templates. Do not infer these; copy them from the repo's own docs.

First use case — a route that serves a controlled PDF plus a page that embeds the viewer. Keep the two concerns separate from day one:

#[Route('/documents/{id}/file', name: 'document_file')]
public function file(Document $document): Response
{
    $this->denyAccessUnlessGranted('VIEW', $document);

    return $this->file($document->getPath(), null, ResponseHeaderBag::DISPOSITION_INLINE);
}
{{ pdfjs_render(path('document_file', {id: document.id}), {height: '80vh'}) }}

Routing the PDF through Symfony (rather than pointing at a static file) is what lets you enforce authorization — the single most important decision you'll make here.

Implementation Patterns

Always proxy the file through a controller. A viewer that takes a public URL is only safe if the URL is already access-controlled. Combine it with Symfony's BinaryFileResponse, a Voter, and — for anything semi-public — a signed URL (Symfony\Component\HttpKernel\UriSigner) so a leaked link expires. Never pass a raw filesystem path into the Twig helper; it will end up in the DOM.

Treat the viewer as an iframe, not a component. PDF.js viewers are heavy and stateful (current page, zoom, scroll). Embedding via iframe isolates them: closing/reopening the document for free, no global namespace collisions, and multiple viewers on one page work without fighting each other. Where the bundle gives you an inline embed, wrap it.

Drive viewer state through options, not by postMessage-hacking. Most PDF.js integrations accept options for page, zoom, search, and a worker URL. Pass page/zoom through the Twig call or query string so deep links like /documents/42?page=12 work. This is where the bundle earns its keep — it normalizes those options into the PDF.js hash/query format.

Pair with your existing upload pipeline. VichUploader, Flysystem, and friends all resolve to a path or a stream. Since the helper just needs a URL, the integration is usually one small controller action — no coupling needed between the bundle and your storage layer.

Configuration belongs in config/packages/. Expect keys for the worker path, default viewer options (theme, toolbar, default zoom), and possibly the asset base. Commit this file; it's where future you will look when the viewer misbehaves in a specific environment.

For CSP-strict apps, plan ahead. PDF.js spawns a Web Worker. You will likely need worker-src 'self' blob: and, depending on how it renders, img-src/connect-src allowances. If your CSP is generated by a bundle (e.g. Nelmio), add the directives in config rather than relaxing the policy globally.

Consider whether you need the bundle at all. If you only need a viewer on one page, dropping the prebuilt pdfjs-dist viewer into public/ and iframing it is a well-trodden path with better documentation. Adopt this bundle when you want Twig-native embedding and Symfony asset routing across many pages — otherwise you're adding an unmaintained layer for one use case.

Gotchas and Tips

The worker path is the classic failure. PDF.js will throw Setting up fake worker failed or silently render blank when pdf.worker.js isn't reachable. Fix the workerSrc config to the published asset path (e.g. /bundles/ntipdfjs/.../pdf.worker.js), verify it loads in the Network tab, and keep that path versioned alongside the viewer assets. This is the #1 support question for any PDF.js wrapper.

Zero stars and zero dependents is a real signal. There's no community to lean on and no guarantee of Symfony version compatibility. Read the composer.json require block for the supported symfony/framework-bundle range before installing on Symfony 7. Pin the exact version, and if you're committing to it, fork it — the surface area is small enough that maintaining a fork is genuinely cheap.

Absence of a Flex recipe means the install is undocumented-implicit. No auto-registration, no default config file, no asset publishing hook. Write your own setup notes in the repo README so the next developer doesn't rediscover the steps.

Asset versioning and cache busting. After upgrading the bundle, browsers will serve stale pdf.worker.js against a new viewer core and produce cryptic API-mismatch errors. Bump your asset version or hard-reload when debugging; assets:install with --symlink can leave dangling links if the vendor dir is reinstalled.

Bundle template/asset overrides. If you need to change the viewer chrome, use Symfony's standard override path (templates/bundles/NTIPdfJsBundle/) rather than editing files under vendor/. Check the repo for the expected template naming first — it may be a single template or none at all if the viewer is pure JS config.

Large PDFs need range requests. PDF.js requests byte ranges; a Symfony BinaryFileResponse handles it, but a custom streaming response may not. If a 200-page document downloads entirely before rendering, check that Accept-Ranges/206 responses are working and that the viewer isn't in a mode that disables range fetching.

CORS for cross-origin PDFs. Serving PDFs from S3 or another domain requires CORS with Range allowed, or PDF.js falls back to full downloads. Same-origin proxying through your controller sidesteps this entirely and is usually the simpler answer.

Debugging order that actually works: browser console for worker errors → Network tab for the PDF response status and content type (application/pdf, not text/html from a redirect to login) → verify workerSrc resolves → only then look at bundle config. Nine times out of ten the "viewer is broken" bug is an auth redirect returning your login page as the PDF.

Test the unauthorized path explicitly. The most common security bug in PDF viewer integrations is an unauthenticated route that serves the file — the viewer works so well in dev that nobody notices the route lacks a check. Add a functional test asserting a 403 for a user without the Voter attribute.

Have a migration path ready. Because this is a thin wrapper, you can replace it with direct PDF.js assets, or with a maintained alternative, in an afternoon — provided your templates call the bundle in one place. Centralize the embed in a single Twig partial (_pdf_viewer.html.twig) so a future swap is a one-file change.

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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
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