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

Laravel Printing Laravel Package

rawilk/laravel-printing

Laravel Printing lets your app send raw print jobs directly to printers through multiple drivers, including CUPS and Windows, with a simple, unified API for listing printers and dispatching print jobs from anywhere in your Laravel application.

Deep Wiki
Context7

Getting Started

Install and publish config

composer require rawilk/laravel-printing
php artisan vendor:publish --tag="printing-config"

config/printing.php has a default driver plus a drivers array. Two drivers ship with the package:

  • printnode — cloud service. Needs an API key and the PrintNode Client running on a machine that can see the physical printer. The Laravel app never talks to the printer directly.
  • cups — talks to a CUPS server over IPP. Needs the CUPS client binaries on the web/queue server and network access to the print host.

Set the driver in .env and add credentials to the relevant driver block (drivers.printnode.key, CUPS host/queue settings).

Verify before writing any code. The single fastest way to separate "my code is wrong" from "the printer infrastructure is wrong" is:

use Rawilk\Printing\Facades\Printing;

Printing::printers(); // Collection of Printer value objects

If this returns an empty collection, stop — the agent isn't running, the API key is wrong, or CUPS isn't reachable. Don't debug your PrintTask yet.

First real use case — print an invoice on order completion:

$job = Printing::newPrintTask()
    ->printer($printerId)
    ->file(storage_path("app/invoices/{$order->id}.pdf"))
    ->title("Invoice #{$order->id}")
    ->send();

$job->id();

Look first at Rawilk\Printing\Facades\Printing, PrintTask, and the Printer / PrintJob value objects — those three classes are the whole mental model. Everything else is configuration.

Implementation Patterns

Never hardcode printer IDs. They're environment-specific (dev CUPS queue ≠ prod PrintNode ID). Store them in a printers table or a settings key per location/station, and resolve them by name in your app code.

Wrap the facade in one service. Don't sprinkle Printing:: across controllers:

class PrintDocument
{
    public function __invoke(string $printerKey, string $path, string $title): ?PrintJob
    {
        $printer = PrinterSetting::where('key', $printerKey)->firstOrFail();

        return Printing::newPrintTask()
            ->printer($printer->remote_id)
            ->file($path)
            ->title($title)
            ->send();
    }
}

This gives you one choke point for logging, retries, and driver switching.

Always print from a queued job, never inline in a request. PrintNode is a synchronous HTTP call to a third party; a slow or dead printer agent will hang your checkout response. Dispatch a job, and put it behind afterCommit() (or DB::afterCommit()) so a rolled-back order never prints.

Content sources: ->content($string) for raw text/plain output, ->file($absolutePath) for local PDFs, ->url($url) for remote content. For invoices, the usual flow is: render Blade → PDF (dompdf/Snappy) → temp file → ->file().

Guard against double prints. Print jobs are not idempotent, and queue retries will re-send. Keep a print_jobs table keyed on (order_id, document_type) with a unique constraint, and check it before sending.

Reconcile status asynchronously. ->send() returning a job ID only means "accepted". For PrintNode, poll Printing::printJob($id) (or listen to the package's job events) to move your record from sent to printed/failed. Treat "no status update" as its own state — printers go offline and jobs sit in the queue.

Per-call driver override is the cleanest dev/prod split:

Printing::driver(config('printing.default'))->newPrintTask()...

Point local at CUPS (or a fake), production at PrintNode — same application code.

Testing: fake the facade rather than hitting a printer.

Printing::shouldReceive('newPrintTask')->once()->andReturn($task = Mockery::mock(PrintTask::class));
$task->shouldReceive('printer')->with('abc')->andReturnSelf();
// ... assert the chain ended in send()

Assert which printer and which content was sent — that's the part that actually breaks.

Gotchas and Tips

  • Empty printer list is almost never a Laravel bug. PrintNode: is the client service running on the workstation? CUPS: does lpstat -p work as the same user PHP runs as (www-data usually lacks printer permissions by default)?
  • ->file() requires a local, absolute, readable path. Storage::path() is fine for the local disk; for S3 you must download to a temp file first. Queue workers run as a different user than your shell — permissions differ.
  • ->url() means the driver fetches it, not your app. PrintNode needs a publicly reachable URL; use a long-TTL signed URL, and remember signed URLs leak job content into logs.
  • Payload size limits. PrintNode caps the content payload — multi-hundred-page PDFs fail. Split or use the URL path.
  • Print options are driver-specific. copies, duplex, color, media, bin map differently between PrintNode and CUPS. Don't share one options array across drivers and assume parity; test both.
  • Config caching and long-lived workers. After editing config/printing.php, run config:cache. Under Octane/queue workers, the resolved driver is held in the container — restart workers after config changes or you'll print to yesterday's printer.
  • Log the printer ID and returned job ID on every send, and keep the raw driver response. When a user says "it didn't print", that log line is the difference between a five-minute and a five-hour investigation.
  • Wrap sends in try/catch and distinguish transport failures (network/auth) from printer-state failures (offline, out of paper). Only the former should burn a queue retry; the latter should alert a human.
  • Extension point: implement the package's driver contract and register it under printing.drivers if you need something exotic (e.g. an internal IPP service or a wrapper that adds audit logging around PrintNode). A custom driver is also the cleanest test double — cleaner than mocking a fluent chain.
  • Privacy: document content leaves your infrastructure with the PrintNode driver. Don't route anything with PII to a shared office printer without a deliberate decision, and check retention on the third-party side.
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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums