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

Php Pdftk Laravel Package

mikehaertl/php-pdftk

PHP wrapper for the pdftk command-line tool that lets you fill PDF forms, merge or split documents, extract pages, apply stamps, watermarks and backgrounds, and rotate or compress PDFs from your Laravel or PHP application.

Deep Wiki
Context7

Getting Started

Install the wrapper and the binary separately. Composer only pulls in the PHP side; pdftk is a system dependency that must exist on every machine that runs your app (local, CI, queue workers, production).

composer require mikehaertl/php-pdftk

# Debian/Ubuntu — plain `pdftk` was removed from recent releases
sudo apt-get install pdftk-java
# macOS
brew install pdftk-java

Verify with which pdftk and pdftk --version. If it lives somewhere unusual, capture the path — you'll feed it to the package explicitly.

First use case — filling an AcroForm. This is what 90% of Laravel apps use the package for:

use mikehaertl\pdftk\Pdf;

$pdf = new Pdf(storage_path('app/forms/w9.pdf'));

$success = $pdf->fillForm([
    'name'    => 'Jane Doe',
    'address' => '123 Main St',
])
    ->needAppearances(true)
    ->saveAs(storage_path('app/filled/w9-'.$user->id.'.pdf'));

if ($success === false) {
    report(new \RuntimeException($pdf->getError()));
}

Where to look first in the source:

  • src/Pdf.php — every public operation (fillForm, cat, merge, stamp, background, rotate, saveAs, send, getError, getCommand).
  • src/Command.php — the Symfony\Component\Process\Process wrapper. This is where binary, timeouts, and useExec live.
  • src/Form.php / src/FdfFile.php — form field handling, XFDF/FDF generation, needAppearances.

Read Command::getCommand() early — it prints the exact shell string the package builds, which is your best debugging tool for the rest of your life with this library.

Implementation Patterns

Wrap it in a dedicated service, not in controllers. Resolve the binary path from config and keep the package behind one seam so you can swap it or fake it in tests.

// config/pdf.php
return ['binary' => env('PDFTK_BINARY', '/usr/bin/pdftk')];

// app/Services/PdfService.php
class PdfService
{
    public function make(string $path): Pdf
    {
        $pdf = new Pdf($path);
        $pdf->getCommand()->binary = config('pdf.binary');
        return $pdf;
    }
}

Fill + flatten for "final" documents. Filling keeps the form editable; flattening bakes values in and shrinks the file. Combine both for contracts and receipts you hand back to users:

$pdf->fillForm($data)
    ->needAppearances(true)
    ->flatten()
    ->saveAs($out);

Chain operations to avoid round-tripping through disk. The class is fluent and each call appends arguments to one pdftk invocation:

$pdf->fillForm($data)
    ->rotate(90)
    ->background($letterheadPath)
    ->saveAs($out);

Merge / split. merge() takes an array of paths; cat() takes page ranges:

(new Pdf())->merge([$a, $b, $c])->saveAs($combined);

(new Pdf($combined))->cat(1, 3, 5)->saveAs($excerpt);

Stream to the browser for one-off downloads instead of writing to disk:

return response()->streamDownload(
    fn () => print $pdf->toString(),
    'document.pdf',
    ['Content-Type' => 'application/pdf']
);

Push heavy work to a queue. Large merges can take seconds; wrap the whole call in a Job and write to Storage::disk('s3') at the end. Pass absolute paths — the process runs with an unpredictable working directory.

Debug by inspecting the command, then running it yourself:

dd($pdf->getCommand()->getCommandLine());

Copy that string into a shell. If it works there but not in PHP, it's an environment/permissions problem, not a package problem.

Testing. Point config('pdf.binary') at a fake script that echoes a fixture path, or assert on getCommand()->getCommandLine() to verify the argument construction without executing anything.

Gotchas and Tips

"sh: pdftk: not found" on production is the #1 failure. Composer install doesn't install binaries. Add pdftk-java to your Dockerfile / provisioning and add a health check that fails the deploy if pdftk --version returns non-zero.

saveAs() returns false, not an exception. Always check the return value and surface $pdf->getError(). On the Command object you can also set $pdf->getCommand()->timeout = 120 — the default Symfony Process timeout is 60s and large PDFs will hit it.

Relative paths bite you. The child process inherits the web server's cwd. Always pass storage_path(...) or Storage::path(...) — never bare filenames.

needAppearances(true) is mandatory for many forms. Without it, fields are filled in the file but render blank in some viewers (notably Acrobat on Windows and many browser PDF plugins). Also note: some forms have read-only fields, and fillForm silently ignores those. Pdf::getCommand() won't tell you — inspect the form with pdftk form.pdf dump_data_fields first.

Empty field values need '', not null. Passing null doesn't clear a field; explicitly pass an empty string.

Password-protected PDFs need inputPw($password) (or ownerPw/userPw when creating). Without it you get a cryptic Error: Failed to open PDF file.

Temp files go to the system temp dir. For long-running workers or containers with small /tmp, override it — the package uses sys_get_temp_dir() by default and cleans up on destruct, but a crashing process leaves orphans behind. Set a Pdf-level temp dir or ensure your container's /tmp is regularly pruned.

pdftk-java vs. classic pdftk differ subtly. Unicode field names, AcroForm edge cases, and PDF 1.7+ handling vary. Pin the binary version in your Docker image so behavior doesn't drift between deploys.

No shell-injection escape hatch to worry about — arguments go through escapeshellarg in Command. But that means arrays are the wrong type for single args; passing an array where a string is expected produces a confusing shell string. Check getCommandLine() if a call misbehaves.

Extension points: subclass Pdf or Command to add your own operations (e.g. a compress() for pdftk input.pdf output out.pdf compress), or override Command::getCommandLine() to inject global flags like --verbose in non-prod environments. Pdf::send() sets the HTTP headers for you but assumes a web SAPI — don't call it from a queue job.

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.
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
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager