Skip to content

Refactor of document generation

Refactor of document generation

INFO

This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. You can find the original version here

Context

Features for Accessible Documents (HTML) and E-Invoices (Zugferd) were implemented under tight legal deadlines and constrained by backward compatibility with SW6.6 and no breaks in SW6.7. This resulted in a technically complex and challenging-to-maintain implementation, where these additional requirements were patched in rather than being addressed with the proper architecture.

Some of the issues with the current implementation

  • The codebase is hard to reason about and extend. This can be subjective.
  • There is no clear separation of concerns between the different document types and their formats.
    • Each Zugferd document is implemented as an extra document type, rather than as a format of an existing document type.
    • Each document entity can have an accessibility media file attached, rather than having it represented as its own format of a document type.
    • This results in hard-coded file and document types in multiple locations such as the UI, where all invoice types need to be handled uniformly.
  • The public API surface for third-party extensions is overly complicated. This can be subjective.
    • Adding new document types or formats requires disproportionate effort.
    • We think we can provide a better API surface than the existing one.
  • Apps cannot currently provide custom document generators or hook into existing ones to modify their output (see shopware#9676 and shopware#10478).
  • The Zugferd library we use, horstoeko/zugferd, has been declared legacy.
    • This means that in the future we will likely have to migrate to a successor or something else.
    • Additionally, exposing its builder pattern to third-party extensions is not the cleanest interface, especially for apps. Right now it is wrapped behind a Shopware ZugferdBuilder, but that also does not expose all features.

Goals for the new implementation

  • [ ] One document type (e.g. invoice) can have one or more “formats” (e.g. PDF, HTML, Zugferd-XML, Zugferd-Embedded-PDF, ...). The merchant can choose which formats to generate.
  • [ ] Different formats of the same document (same generation call) should all be based on the same data (e.g. all have the same document number) and are just different representations of the same data.
  • [ ] Document formats can depend on one another (e.g. Zugferd-PDF can reuse the content of the PDF format + Zugferd-XML format to merge them into a single PDF file, or the PDF format can be based on the HTML content using DomPDF to convert it).
  • [ ] Mail attachments should work on the "document level", meaning they should include all available formats automatically with the future possibility to select them precisely.
  • [ ] Document types and formats should be extendable (e.g. a plugin might want to add more data to an e-invoice, provide another format, or even another type).
  • [ ] For merchants, document generation can mostly be configured as it is now. However, the merchant should only be able to configure things that are actually used by the document type (e.g. why specify payment due date for delivery notes, and optionally also extract company details into their own entity instead of duplicating that data in every config).
  • [ ] Merchants, integrations, and extensions should still be able to upload document files for a type and bypass our generation, as is possible now (see document.static DB field).
  • [ ] This new implementation should be "opt-in" during 6.7, so third parties have time to adjust, and it will replace the old implementation in 6.8 (which means that the old one should be deprecated).

Decision

We will refactor the document generation codebase to make it more maintainable and extensible. It will lead to better separation of concerns between document types and their formats, as well as a better API surface for extensions, so they do not rely on PHP class decoration, break easily with each Shopware major release, and block future internal improvements.

The new implementation will be opt-in during 6.7 and will become the default in 6.8, replacing the existing (old) implementation. More details on the concrete migration strategy will be described in a separate ADR, after the actual implementation is mostly done.

With the new implementation we will also generate Zugferd XML data via Twig templates, which will provide better extendability, and we remove the horstoeko/zugferd dependency.

The architecture of the new implementation will be described in a separate ADR, which you can find here: 2026-03-18-new-document-generation-architecture.md

The new extension points are described here: 2026-03-19-new-document-generation-extension-points.md

Consequences

We expect:

  • Only minor changes to the merchant UX when generating documents, configuring them, or building flows for them. But we are not limiting ourselves to this and might adjust things to provide a better UX overall.
  • That all existing (already generated) documents will be migrated and will still be accessible untouched in the new implementation UI.
  • All extensions and integrations that did anything document-related will have to be updated to use the new implementation.

Estimated impact on customers

We scanned the codebases of plugins available in our extension store and summarized the results below.

We make the following assumptions:

  • In total, 204 of the 3,204 scanned plugins use some part of the existing document generation API surface. That is 6.4% of the plugin ecosystem.
  • The 84 plugins that touch only the Twig templates are probably unaffected, since we will continue using the same templates and will try not to introduce breaking changes to them. This reduces the estimated number of affected plugins to 120.
  • If we keep the current configuration database schemas backward compatible, we could reduce that number by a further 51 plugins, bringing the estimated number of affected plugins down to 69.
    • It turns out that some plugins use only our configuration schemas and do not actually use our rendering system, but instead build their own.

With these assumptions, we estimate that this refactor would break 69 plugins, which is 2.2% of our plugin ecosystem. Of course, these numbers are only estimates, and not every plugin is published in our extension store.

Overview

metricvalue
plugins scanned3204
plugins using any extension point204
unique plugin/extension-point usages553
average extension points per matching plugin2.71

Extension Point Groups

groupcontainspluginsgroup-onlyshared with other groups/pointsshare of matching plugins
twig templates (any)@Framework/documents/base.html.twig, credit_note.html.twig, delivery_note.html.twig, invoice.html.twig, storno.html.twig117843357.4%
renderers/builders (any)document.renderer, AbstractDocumentRenderer, CreditNoteRenderer, DeliveryNoteRenderer, InvoiceRenderer, StornoRenderer, ZugferdBuilder, ZugferdRenderer3923719.1%
type renderers (any)document_type.renderer, AbstractDocumentTypeRenderer, HtmlRenderer, PdfRenderer3042614.7%
config entities (any)document_base_config, document_base_config_sales_channel77512637.7%

Per Extension Point

extension pointpluginsexclusivesharedshare of matching plugins
document.renderer191189.3%
document_type.renderer4042.0%
AbstractDocumentRenderer190199.3%
AbstractDocumentTypeRenderer4132.0%
CreditNoteRenderer7073.4%
DeliveryNoteRenderer100104.9%
DocumentFileRendererRegistry110115.4%
DocumentGenerateOperation44113321.6%
HtmlRenderer4042.0%
InvoiceRenderer2802813.7%
PdfRenderer2822613.7%
StornoRenderer7073.4%
ZugferdBuilder0000.0%
ZugferdRenderer0000.0%
@Framework/documents/base.html.twig52203225.5%
credit_note.html.twig2802813.7%
delivery_note.html.twig4824623.5%
invoice.html.twig76215537.3%
storno.html.twig3303316.2%
document_base_config77126537.7%
document_base_config_sales_channel5405426.5%
Was this page helpful?
UnsatisfiedSatisfied
Be the first to vote!
0.0 / 5  (0 votes)