In short
For e-invoices without Laravel, install quittio/core and the country pack quittio/country-de. Instead of the facade, create one instance with Quittio::create() and build invoices with InvoiceBuilder::make(). ZUGFeRD, XRechnung, Peppol, validation and reading incoming invoices work exactly as in Laravel.
Which packages you need
| Package | Without Laravel | Content |
|---|---|---|
quittio/core | required | Data model, calculation, CII and UBL, ZUGFeRD/Factur-X, validation, reading |
quittio/country-de | recommended | German mandatory fields, VAT rates, XRechnung rules, DIN 5008 |
quittio/laravel | not needed | Facade, configuration, Blade, Artisan |
quittio/quittio | not needed | Complete package for Laravel projects |
You need PHP 8.3 or newer with the extensions ctype, dom, gd, libxml, mbstring, xmlwriter and zlib. Laravel or any other framework is not required.
Installation
One command, no config file:
composer require quittio/core quittio/country-deA complete script
The script creates a ZUGFeRD invoice (PDF/A-3 with embedded XML), saves it and reads it back as a check. Run it with php invoice.php.
<?php
require __DIR__.'/vendor/autoload.php';
use Quittio\Core\Builder\InvoiceBuilder;
use Quittio\Core\Builder\Party;
use Quittio\Core\Enums\PaymentMeansCode;
use Quittio\Core\Enums\UnitCode;
use Quittio\Core\Quittio;
use Quittio\Core\Tax\CountryDefaultVat;
use Quittio\Country\DE\CountryDE;
$quittio = Quittio::create([], [new CountryDE]);
InvoiceBuilder::useDefaultVat(new CountryDefaultVat($quittio->countries())); // lines without vat(): 19 %
$invoice = InvoiceBuilder::make()
->number('RE-2026-0120')
->issueDate('2026-10-14')
->deliveryDate('2026-10-14')
->seller(Party::make('Muster GmbH')->vatId('DE123456789')->address('Hauptstraße 1', '12345', 'Musterstadt', 'DE'))
->buyer(Party::make('Kunde AG')->vatId('DE987654321')->address('Bahnhofstraße 5', '54321', 'Beispielstadt', 'DE'))
->line('Consulting', netPrice: '120.00', quantity: 4, unit: UnitCode::Hour)
->payment(PaymentMeansCode::SepaCreditTransfer, iban: 'DE02120300000000202051')
->dueInDays(14)
->build();
$result = $quittio->make($invoice)->render(); // ZUGFeRD: PDF/A-3 with XML
$path = $result->save(__DIR__.'/invoices/'.$result->filename()); // invoices/Rechnung_RE-2026-0120.pdf
// Read it back, as the recipient would
$read = $quittio->readFile($path);
echo $read->data->totals->amountDue; // EUR 571.20- Without Laravel there is no config file, so seller and bank account are set in code.
- useDefaultVat() gives lines without their own VAT the default rate of the country pack, 19 % in Germany. Without it, build() fails for such lines.
- render() validates the invoice. If it is invalid, an exception with all findings is thrown before any file is written.
What is different from Laravel
| Topic | Laravel | Plain PHP |
|---|---|---|
| Entry point | Facade Quittio::invoice() | Instance from Quittio::create(), invoice with InvoiceBuilder::make() |
| Configuration | config/quittio.php | Array or Config object passed to Quittio::create() |
| Seller and bank | from the configuration | in code: ->seller(…) and ->payment(…) |
| Default VAT | automatic | call InvoiceBuilder::useDefaultVat(…) once at start-up |
| Country pack | discovered automatically | pass new CountryDE to Quittio::create() |
| Templates | Blade | Plain PHP templates; Twig etc. via your own TemplateEngine |
| Saving | Storage disk | $result->save($path) or file_put_contents() |
Pass settings as an array, for example for the validation levels:
$quittio = Quittio::create([
'validation' => ['mode' => 'error', 'levels' => ['L1', 'L2', 'L3', 'L4', 'L7']],
'pdf' => ['b2c_embed_xml' => false],
], [new CountryDE]);XRechnung and validation
Switching format is one line. check() validates an invoice without creating a PDF:
// XRechnung instead of ZUGFeRD
$result = $quittio->make($invoice)->format('xrechnung-cii')->render();
// Validate only, no PDF; never throws, returns a report
$report = $quittio->make($invoice)->format('xrechnung-cii')->check();
$report->isValid();
$report->errors();Cancellations work without Laravel too:
$cancellation = $quittio->cancellation($result->data(), 'ST-2026-0001', reason: 'Charged twice');Reading incoming invoices
Since 2025 every business in Germany must be able to receive e-invoices. The instance reads CII, UBL and ZUGFeRD and can check them on all levels:
$read = $quittio->read($uploadedContent); // content of an XML or PDF file
$read = $quittio->readFile('/path/to/invoice.pdf');
$read->data->number; // "RE-2026-0101"
$read->data->seller->name; // the supplier
$read->profile; // e.g. "en16931" or "xrechnung"
$report = $quittio->validateFile($uploadedContent); // all validation levels on an incoming invoiceWiring it into your project
Create the instance once at start-up and reuse it everywhere. In Symfony, Slim or another framework, register it as a service; without a framework, a small factory is enough:
final class InvoiceFactory
{
private static ?Quittio $quittio = null;
public static function quittio(): Quittio
{
if (self::$quittio === null) {
self::$quittio = Quittio::create([], [new CountryDE]);
InvoiceBuilder::useDefaultVat(new CountryDefaultVat(self::$quittio->countries()));
}
return self::$quittio;
}
}What only works with Laravel
- Facade, config file and Artisan commands
- Blade templates, queue, events, mail and storage integration
- Quittio::fake() and the test assertions (factories and scenarios also work without Laravel)
- Quittio Managed (quittio/managed) with database, numbering and archive requires Laravel 13
Everything that makes a valid e-invoice lives in the core. The complete reference for use without Laravel is in the documentation.