Render .odt files with Blade, convert to pdf with Libre/OpenOffice

Maintainers

Details

github.com/petecoop/odt

Source

Issues

Installs: 831

Dependents: 0

Suggesters: 0

Security: 0

Stars: 1

Watchers: 2

Forks: 0

Open Issues: 0

pkg:composer/petecoop/odt

1.0.2 2025-10-24 14:54 UTC

This package is auto-updated.

Last update: 2025-10-24 15:52:10 UTC


README

Use Open Document Text (.odt) files as templates in PHP/Laravel projects, rendering them with Blade syntax and outputting as ODT or PDF.

This allows you to create complex document templates in LibreOffice or OpenOffice, including loops and conditionals using Blade syntax, render them with dynamic data and convert into PDF.

Why

I've found that there are some things that just can't be done when generating PDF's with HTML or PDF libraries that word processors have solved. This is usually where dynamic content can flow over multiple pages, and you want to do things like repeat table headers, or have some items of text stay together, or other more complex layouts like anchoring a section to the bottom of the final page. This also allows non-developers to create and edit document templates in a familiar word processor rather than having to write HTML or code.

Installation

composer require petecoop/odt

Usage

In Laravel

Access via the ODT facade:

use Petecoop\ODT\Facades\ODT;

ODT::open(resource_path("file.odt"))
    ->render([
        "some" => "arguments",
    ]);

Other PHP Projects

Call ODT::make() to create an instance:

use Petecoop\ODT\ODT;

ODT::make()
    ->open(__DIR__ . "/templates/file.odt")
    ->render([
        "some" => "arguments",
    ]);

Converting to PDF

Conversion can be done using either the soffice binary directly, or via a Gotenberg server. soffice is the default and is slower due to the overhead of starting the LibreOffice process for each conversion, but requires no additional setup (other than installing LibreOffice). Gotenberg is faster for multiple conversions as it keeps a LibreOffice process running in the background, but requires a Gotenberg server to be running.

Using soffice for conversion

The soffice binary is required to be installed for PDF conversion. This should be available if LibreOffice or OpenOffice is installed on the system.

To convert to PDF, use the pdf() method after rendering.

$pdf = ODT::open(resource_path("file.odt"))
    ->render([
        "some" => "arguments",
    ])
    ->pdf();

If soffice is not in the system PATH, you can specify the path to the binary using the officeBinary method:

ODT::officeBinary('/path/to/soffice')
    ->open(resource_path("file.odt"));

// if using ODT::make()
ODT::make('/path/to/soffice')
    ->open(__DIR__ . "/templates/file.odt");

Using Gotenberg for conversion

To use Gotenberg for PDF conversion, you need to have a Gotenberg server running. You can specify the Gotenberg server URL using the gotenberg method:

$pdf = ODT::gotenberg('http://localhost:3000')
    ->open(resource_path("file.odt"))
    ->render([
        "some" => "arguments",
    ])
    ->pdf();

A HTTP Client is required to be installed, if outside of Laravel you can install the Guzzle client:

composer require php-http/guzzle7-adapter

See a full list of clients if you'd like to use a different one.

Saving or Downloading

The resulting .odt or PDF file can be saved to disk or returned as a download response:

$odt = ODT::open(resource_path("file.odt"))
    ->render([
        "some" => "arguments",
    ]);

// Save ODT file
$odt->save(storage_path("file.odt"));

// Convert to PDF and save
$pdf = $odt->pdf()->save(storage_path("file.pdf"));

// Return as download response in a Laravel Controller
return $pdf;

Templating

Some Blade directives are provided to help with common templating tasks in ODT files.

Image Embedding

Use the @image directive to embed base64 encoded images into your ODT templates. This directive accepts a base64 image string and optional maximum width and height parameters (in cm).

@image($base64ImageString, '5cm', '5cm')

Table Rows

When in Libre/Open Office you can't wrap a @foreach around a table row - use @rowforeach, this can be done inside a table cell. The entire row will be repeated for each item.

@rowforeach($users as $user)
{{ $user->name }}

All other cells in the row will have access to the $user variable.

@rowif can be used to conditionally include a table row based on an expression. This is useful if you want to display a row only when certain conditions are met.

@rowif($product->description)
{{ $product->description }}

Note: due to a limitation of matching nested parentheses, only one level of nested parentheses is supported in the @rowforeach(...) and @rowif(...) expressions. e.g. @rowforeach($users as $user) or @rowforeach($items->where('active', true) as $item) will work, but more complex expressions with multiple levels of parentheses will not.

If you need more control over what is before / after the row use @beforerow / @endbeforerow and @afterrow / @endafterrow. This example is the equivalent of the above but allows you to put any other directives before or after the row.

@beforerow@foreach ($users as $user)@endbeforerow
{{ $user->name }}
@afterrow@endforeach@endafterrow