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

Printly Laravel Package

g4t/printly

Printly is a Laravel package for generating print-ready documents from Blade views. It streamlines printer-friendly HTML and PDF output with configurable layouts, paper sizes, and templates, making it easy to produce invoices, receipts, reports, and labels consistently.

View on GitHub
Deep Wiki
Context7

Getting Started

hussein4alaa/printly-pdf is at 0.0.1 — treat it as pre-stable and read the source before you trust the docs. The 0.0.1 release ships with nothing but a changelog link, so there is no migration guide, no deprecation policy, and no promise that 0.0.2 won't rename the façade or change a config key.

Minimal path to a first PDF:

  1. Pin the version exactly. Because 0.0.x has no BC guarantees, install with an exact constraint rather than a caret:

    composer require "hussein4alaa/printly-pdf:0.0.1"
    

    A ^0.0.1 constraint will happily pull 0.0.9 on your next composer update in CI.

  2. Check what engine it wraps. Open the package's composer.json require block first. Whether it delegates to dompdf, mpdf, wkhtmltopdf, or a headless-Chrome driver determines almost everything downstream — CSS support, font handling, and whether you need a binary on the host.

  3. Publish and read the config. If the service provider exposes vendor:publish, publish the config and read every key before writing code. Paper size, orientation, default driver, and any binary path almost always live here.

  4. Confirm auto-discovery. Laravel 11+ apps use bootstrap/providers.php; if the package's extra.laravel.providers entry is missing or wrong, register the provider and façade alias manually.

First use case: take a Blade view you already have — an invoice, a receipt, a report — and render it to a PDF streamed to the browser. That exercises the whole chain (view resolution, asset paths, font loading, engine invocation) in one request and surfaces the environment problems early, before you build queued exports on top of it.

Where to look first, in order: composer.json (engine + PHP/Laravel constraints) → src/*ServiceProvider.php (what's bound, what's publishable) → config/*.php (defaults and env var names) → the façade or main entry class (the actual public API surface). At 0.0.1 the source is the documentation.

Implementation Patterns

Wrap it, don't sprinkle it. Put a thin application-level service between your controllers and the package. When 0.0.2 renames a method, you edit one class instead of forty call sites.

final class InvoicePdf
{
    public function __construct(private PdfBuilder $pdf) {}

    public function forInvoice(Invoice $invoice): Response
    {
        return $this->pdf
            ->view('pdf.invoice', ['invoice' => $invoice->load('lines')])
            ->paper('a4', 'portrait')
            ->download("invoice-{$invoice->number}.pdf");
    }
}

One view per document type, self-contained. PDF views should not extend your web layout. Web layouts pull in Vite bundles, nav partials, and Alpine directives that a PDF engine will either ignore or choke on. Keep resources/views/pdf/* standalone with inline <style> blocks and no @vite.

Treat output mode as a parameter, not a decision. stream (inline preview), download (attachment), and save (write to a Storage disk and return the path) are three call sites over the same generation logic. Don't fork the generation code per mode.

Push heavy documents onto the queue. Anything with more than a few dozen rows, or any external image, belongs in a job:

final class GenerateInvoicePdf implements ShouldQueue
{
    use SerializesModels;

    public function __construct(public int $invoiceId) {}

    public function handle(InvoicePdf $pdf): void
    {
        $path = $pdf->store(Invoice::findOrFail($this->invoiceId));
        Mail::to(...)->queue(new InvoiceReady($path));
    }
}

Pass the ID, not the model graph, and certainly not a half-built PDF object. Large eager-loaded relations serialize badly, and a queued PDF handle is meaningless once the worker restarts.

Per-call options override config; global config sets the house style. Set A4/portrait and your margins in config/printly.php, then override per document (landscape for wide financial tables, Letter for US clients). This keeps the majority of documents zero-configuration.

Test the response, not the bytes. Assert Content-Type: application/pdf, assert the filename in Content-Disposition, assert the file exists on a Storage::fake() disk. Byte-level or pixel-level assertions are brittle across engine versions and unavailable in CI without the binary installed.

Keep the engine identical across environments. If local uses one driver and production another, your layouts will drift in ways that only appear in production. Pin the driver in config and make sure CI has the same one.

Gotchas and Tips

0.0.1 means the API is a draft. Pin exactly, commit composer.lock, and read the diff of every bump before merging. Assume method names, config keys, and the façade alias will all change before 1.0.

Binary dependencies are not installed by Composer. If the engine is wkhtmltopdf or headless Chrome, composer install gets you a PHP wrapper around a program that isn't there. Watch for a config key or env var pointing at the binary path, and make sure your Docker image installs the binary and a font package. This is the single most common cause of "works on my machine."

Silent blank pages. A PHP error inside the view is frequently swallowed by output buffering before the engine sees it, producing a valid but empty PDF. Debug by rendering the same Blade view in the browser first, or dump the generated HTML to a file before handing it to the engine. Nine times out of ten it's a missing variable or an image path that 404s.

CSS support is engine-dependent, not browser-dependent. Flexbox and CSS Grid are partially or entirely unsupported in the older engines. Use tables for tabular layouts, keep styles inline or in a <style> block, and avoid @media screen. Page breaks need page-break-inside: avoid on the rows you don't want split.

Non-Latin glyphs will render as boxes unless the font is registered with the engine. Arabic, CJK, and many currency symbols are not in the default font set. Check the package's font registration path and confirm the directory it writes to (storage/fonts in dompdf-style engines) is writable and gitignored.

Temp directory permissions. Engines write intermediate files to sys_get_temp_dir(). In containers with a read-only root filesystem or a locked-down /tmp, this fails with an unhelpful error. Confirm the temp path is writable, and clean up your own artifacts in a finally block so failed jobs don't leak disk.

Remote images need explicit permission. Most engines refuse to fetch external URLs by default, and relative asset() paths resolve against nothing unless you set a base URL or host. Prefer absolute local filesystem paths, or enable remote fetching deliberately — not by default, since it makes the renderer an SSRF vector if any user-controlled HTML reaches it.

Config caching bites on deploy. php artisan config:cache means any env() call outside a config file returns null. If the package reads env vars directly rather than through its config file, that's a bug you'll need to work around. Re-run config:cache after publishing or editing.

Memory and timeouts on big reports. Thousands of rows will exhaust memory_limit and hit max_execution_time before the engine finishes. Chunk long tables into sections and merge, or move to the queue where you can raise limits per-job via retry_after and a dedicated worker.

Extension points are the thing to verify before you commit to the package. Look for a driver/engine contract, a macro on the builder, or a resolving() hook. At 0.0.1 you may find none — in which case plan on vendoring or forking if you need custom behaviour rather than assuming an escape hatch exists.

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.
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
uni-method/symfony-lib