maestrodimateo / simple-word
A simple, dev-friendly Laravel wrapper for PHPWord
Requires
- php: ^8.3 || ^8.4
- illuminate/support: ^12.0|^13.0
- phpoffice/phpword: ^1.3
- symfony/process: ^7.0|^8.0
Requires (Dev)
- laravel/pint: ^1.29
- orchestra/testbench: ^10.0
- pestphp/pest: ^4.0
README
A simple, dev-friendly Laravel wrapper for PHPWord.
Built on top of PHPWord's TemplateProcessor, this package provides a fluent API for generating Word documents from .docx templates — with variables, tables, images, conditional blocks, PDF conversion, and Mailable-style template classes out of the box.
Installation
composer require maestrodimateo/simple-word
Publish the config:
php artisan vendor:publish --tag=word-config
Add to your .env (optional):
WORD_TEMPLATES_PATH=/path/to/your/templates
By default, templates are resolved from resources/templates/word.
Quick Start
Replace placeholders
In your .docx template, use ${variable} placeholders:
Hello ${name}, your order ${ref} was placed on ${date}.
word()->template('order.docx') ->set('name', 'John Doe') ->set('ref', 'CMD-001') ->set('date', now()->format('d/m/Y')) ->save(storage_path('app/orders/order-john.docx'));
Bulk replace
word()->template('order.docx') ->set([ 'name' => 'John Doe', 'ref' => 'CMD-001', 'date' => now()->format('d/m/Y'), ]) ->download('order.docx');
Nested arrays (dot notation)
Nested associative arrays are automatically flattened with dot notation. This is useful for grouping related data:
word()->template('contract.docx') ->set([ 'client' => [ 'name' => 'John Doe', 'email' => 'john@example.com', 'address' => [ 'city' => 'Paris', 'zip' => '75001', ], ], 'date' => now()->format('d/m/Y'), ]) ->download('contract.docx');
This replaces ${client.name}, ${client.email}, ${client.address.city}, ${client.address.zip}, and ${date} in the template.
Tables
In your template, prefix table cell placeholders with a group name. See PHPWord cloneRowAndSetValues:
| ${items.produit} | ${items.prix} |
|---|
word()->template('invoice.docx') ->table('items', [ ['produit' => 'Widget A', 'prix' => '100 EUR'], ['produit' => 'Widget B', 'prix' => '200 EUR'], ['produit' => 'Widget C', 'prix' => '50 EUR'], ]) ->download('invoice.docx');
The table row is cloned once for each entry and placeholders are filled automatically.
Images
Use ${placeholder} in your template where an image should appear. See PHPWord setImageValue:
Local file
word()->template('report.docx') ->set('company', 'Acme Corp') ->image('logo', storage_path('app/logo.png'), width: 150, height: 50) ->image('signature', storage_path('app/signature.png')) ->save(storage_path('app/report.docx'));
Remote URL (GED, CDN, Alfresco, etc.)
image() accepts HTTP/HTTPS URLs. The image is downloaded automatically to a temp file:
word()->template('report.docx') ->set('company', 'Acme Corp') ->image('logo', 'https://alfresco.example.com/api/-default-/public/alfresco/versions/1/nodes/abc-123/content', width: 150) ->download('report.docx');
From a Laravel filesystem disk
Use imageFromDisk() to load images from any configured Laravel filesystem disk (S3, SFTP, custom Alfresco adapter, etc.):
word()->template('report.docx') ->set('company', 'Acme Corp') ->imageFromDisk('logo', 'alfresco', 'logos/company.png', width: 150, height: 50) ->imageFromDisk('signature', 's3', 'signatures/ceo.png') ->download('report.docx');
Conditional Blocks
In your template, wrap conditional content with block markers:
${has_discount} You benefit from a 10% loyalty discount! ${/has_discount}
word()->template('offer.docx') ->set('client', 'Jane Doe') ->when('has_discount', $client->is_vip) ->when('has_warranty', $product->warranty_months > 0) ->download('offer.docx');
When the condition is true, the content is kept (markers removed). When false, the entire block is removed.
Template Classes (Mailable-style)
For reusable, testable templates, generate a dedicated class:
php artisan make:word-template InvoiceDocument
This creates app/Word/InvoiceDocument.php:
namespace App\Word; use Maestrodimateo\SimpleWord\DocumentBuilder; use Maestrodimateo\SimpleWord\WordTemplate; class InvoiceDocument extends WordTemplate { protected string $template = 'invoice.docx'; public function __construct( private Invoice $invoice, private Collection $lines, ) {} public function build(DocumentBuilder $builder): void { $builder ->set([ 'client' => [ 'name' => $this->invoice->client->name, 'email' => $this->invoice->client->email, ], 'date' => $this->invoice->date->format('d/m/Y'), 'total' => number_format($this->invoice->total, 2) . ' EUR', ]) ->table('lines', $this->lines->map(fn ($l) => [ 'description' => $l->description, 'quantity' => $l->quantity, 'price' => number_format($l->price, 2) . ' EUR', ])->all()) ->image('logo', storage_path('app/logo.png'), width: 120, height: 40) ->when('has_notes', $this->invoice->notes !== null); } }
Use it anywhere:
// In a controller return word()->generate(new InvoiceDocument($invoice, $lines)) ->download('invoice.docx'); // Save to disk word()->generate(new InvoiceDocument($invoice, $lines)) ->saveTo('s3', "invoices/{$invoice->id}.docx");
PDF Conversion
Convert any document to PDF with toPdf():
word()->template('contract.docx') ->set('name', 'John Doe') ->toPdf() ->download('contract.pdf');
Supported drivers
| Driver | Requires | Quality |
|---|---|---|
libreoffice (default) |
LibreOffice installed on server | Excellent |
gotenberg |
Gotenberg Docker container | Excellent |
Configuration
WORD_PDF_DRIVER=libreoffice LIBREOFFICE_PATH=/usr/bin/libreoffice # Or with Gotenberg WORD_PDF_DRIVER=gotenberg GOTENBERG_URL=http://localhost:3000
Custom converter
Implement the PdfConverter interface for any other conversion service:
use Maestrodimateo\SimpleWord\Contracts\PdfConverter; class CloudConvertPdf implements PdfConverter { public function convert(string $docxPath, string $pdfPath): void { // Your conversion logic } } // Use it directly word()->template('doc.docx') ->set('key', 'value') ->toPdf(new CloudConvertPdf()) ->download('doc.pdf');
Output Methods
All output methods are available on both DocumentBuilder (docx) and PdfBuilder (after toPdf()):
| Method | Description |
|---|---|
save($path) |
Save to a local file path |
saveTo($disk, $path) |
Save to a Laravel filesystem disk (S3, local, etc.) |
download($filename) |
Return a download HTTP response |
stream($filename) |
Return an inline HTTP response (preview in browser) |
// Save locally ->save(storage_path('app/document.docx')); // Save to S3 ->saveTo('s3', 'documents/2026/contract.docx'); // Download return ->download('contract.docx'); // Preview in browser return ->stream('contract.docx');
Advanced Usage
Access the underlying PHPWord TemplateProcessor for features not covered by the fluent API:
$builder = word()->template('complex.docx'); $processor = $builder->getProcessor(); $processor->cloneBlock('repeating_section', 3); $builder->save('output.docx');
Facade
use Maestrodimateo\SimpleWord\Facades\Word; Word::template('doc.docx')->set('key', 'value')->save('output.docx'); Word::generate(new InvoiceDocument($invoice, $lines))->download('invoice.docx');
Helper
word()->template('doc.docx')->set('key', 'value')->save('output.docx');
Configuration
// config/word.php return [ // Default directory for .docx templates 'templates_path' => env('WORD_TEMPLATES_PATH', resource_path('templates/word')), // PDF conversion 'pdf' => [ 'driver' => env('WORD_PDF_DRIVER', 'libreoffice'), 'libreoffice_path' => env('LIBREOFFICE_PATH', '/usr/bin/libreoffice'), 'gotenberg_url' => env('GOTENBERG_URL', 'http://localhost:3000'), ], ];
Artisan Commands
| Command | Description |
|---|---|
make:word-template |
Generate a new WordTemplate class |
Testing
./vendor/bin/pest
Useful PHPWord Links
- PHPWord Documentation
- Template Processing — setValue, cloneRow, cloneBlock, setImageValue
- Elements — text, tables, images, lists, headers/footers
- Styles — fonts, paragraphs, tables
- Writers — Word2007, ODText, HTML, PDF
- GitHub Repository
License
MIT
Credits
- Noel Mebale
- Built on PHPOffice/PHPWord