Versions and Release Notes
Here you'll find the most important changes in the current and upcoming FactoorSharp versions.
Version 20.0.0
CurrentVersion 20.0 is a major release: it makes decimal places configurable, adds serial numbers and batches per line item, renders invoices in German, English and French, and tidies up the API. Some changes affect existing code – they are marked “Breaking change” below, each with a migration example.
What's new in detail:
-
Tax rate per line is now optional (BT-152) Breaking change CoreThe property
TradeLineItem.TaxPercentis now of typedecimal?. If the invoice has no rate, it returnsnullinstead of 0, and the element is omitted when writing – which is mandatory for tax category O (not subject to VAT). When reading,?? 0mis enough to keep the previous behavior. InAddTradeLineItem(),taxPercentnow defaults tonull: lines with 0 % tax (for example categories E, AE and Z) must state the rate explicitly with0m.
// Read the tax rate of a line: if the invoice has none, TaxPercent is now null decimal taxPercent = tradeLineItem.TaxPercent ?? 0m; // Lines with 0 % tax: state the rate explicitly, it is no longer pre-set to 0 invoice.AddTradeLineItem( name: "Consulting", netUnitPrice: 100m, unitCode: QuantityCodes.C62, billedQuantity: 1m, taxType: TaxTypes.VAT, categoryCode: TaxCategoryCodes.E, taxPercent: 0m); ' Read the tax rate of a line: if the invoice has none, TaxPercent is now null Dim taxPercent As Decimal = If(tradeLineItem.TaxPercent, 0D) ' Lines with 0 % tax: state the rate explicitly, it is no longer pre-set to 0 invoice.AddTradeLineItem( name:="Consulting", netUnitPrice:=100D, unitCode:=QuantityCodes.C62, billedQuantity:=1D, taxType:=TaxTypes.VAT, categoryCode:=TaxCategoryCodes.E, taxPercent:=0D) -
Saving as PDF returns a result and produces PDF/A-3B Breaking change PDF
SaveToPdf()andSaveToPdfAsync()now return aPdfSaveResultthat lists what was repaired in the source PDF or remained open. The generated PDF declares itself as PDF/A-3B instead of PDF/A-3A. Source code compiles unchanged; applications that were already compiled must be recompiled because the signature changed. All overloads also accept optionalInvoiceFormatOptions, which apply to the embedded XML as well. For details see the documentation “Working with PDF Files”.
// SaveToPdfAsync() now returns a PdfSaveResult PdfSaveResult result = await FacturXInvoicePdfProcessor.SaveToPdfAsync( "rechnung-zugferd.pdf", ZUGFeRDVersion.Version25, Profile.Comfort, ZUGFeRDFormats.CII, "rechnung.pdf", invoice); // What was repaired in the source PDF or remained open foreach (PdfSourceIssue issue in result.Issues) { Console.WriteLine(issue); } ' SaveToPdfAsync() now returns a PdfSaveResult Dim result As PdfSaveResult = Await FacturXInvoicePdfProcessor.SaveToPdfAsync( "rechnung-zugferd.pdf", ZUGFeRDVersion.Version25, Profile.Comfort, ZUGFeRDFormats.CII, "rechnung.pdf", invoice) ' What was repaired in the source PDF or remained open For Each issue As PdfSourceIssue In result.Issues Console.WriteLine(issue) Next -
Obsolete functions removed Breaking change CoreThe functions previously marked
[Obsolete]have been removed. The example shows the replacement for each; migrating is a rename with no change in results. The classesInvoiceValidatorandValidationResultof the core package are removed as well – validation is available in theFactoorSharp.FacturX.Validatorpackage.
// AddTradeeCharge() (with the typo) -> AddTradeCharge() invoice.AddTradeCharge(null, CurrencyCodes.EUR, 25m, null, "Verpackung", TaxTypes.VAT, TaxCategoryCodes.S, 19m); // SetTaxTotalInAccountingCurrency() -> SetTaxCurrencyExchange() invoice.SetTaxCurrencyExchange(175.08m, CurrencyCodes.EUR, 0.9215m, new DateTime(2026, 9, 1)); // SellerElectronicAddress / BuyerElectronicAddress -> Seller.ElectronicAddress / Buyer.ElectronicAddress invoice.Seller.ElectronicAddress = new ElectronicAddress { Address = "rechnung@verkaeufer.de", ElectronicAddressSchemeID = ElectronicAddressSchemeIdentifiers.ElectronicMailSmtp }; // TradeLineItem.SetOrderReferencedDocument() -> SetBuyerOrderReferencedDocument() lineItem.SetBuyerOrderReferencedDocument("PO-2026-00417", new DateTime(2026, 2, 1), "10"); // InvoiceVisualizer.Render() / RenderToBytes() -> RenderPdf() byte[] pdf = InvoiceVisualizer.RenderPdf(invoice); // InvoiceValidator / FactoorSharp.FacturX.ValidationResult -> FactoorSharp.FacturX.Validator package ValidationResult result = FacturXValidator.Validate("invoice.pdf", extendedValidation: false); ' AddTradeeCharge() (with the typo) -> AddTradeCharge() invoice.AddTradeCharge(Nothing, CurrencyCodes.EUR, 25D, Nothing, "Verpackung", TaxTypes.VAT, TaxCategoryCodes.S, 19D) ' SetTaxTotalInAccountingCurrency() -> SetTaxCurrencyExchange() invoice.SetTaxCurrencyExchange(175.08D, CurrencyCodes.EUR, 0.9215D, New DateTime(2026, 9, 1)) ' SellerElectronicAddress / BuyerElectronicAddress -> Seller.ElectronicAddress / Buyer.ElectronicAddress invoice.Seller.ElectronicAddress = New ElectronicAddress With { .Address = "rechnung@verkaeufer.de", .ElectronicAddressSchemeID = ElectronicAddressSchemeIdentifiers.ElectronicMailSmtp } ' TradeLineItem.SetOrderReferencedDocument() -> SetBuyerOrderReferencedDocument() lineItem.SetBuyerOrderReferencedDocument("PO-2026-00417", New DateTime(2026, 2, 1), "10") ' InvoiceVisualizer.Render() / RenderToBytes() -> RenderPdf() Dim pdf As Byte() = InvoiceVisualizer.RenderPdf(invoice) ' InvoiceValidator / FactoorSharp.FacturX.ValidationResult -> FactoorSharp.FacturX.Validator package Dim result As ValidationResult = FacturXValidator.Validate("invoice.pdf", extendedValidation:=False) -
Configurable decimal places CorePrices, quantities, percentages and exchange rates can now be written with more decimal places – separately for each data type. Amounts stay at two places; only line amounts have an explicit opt-in, which is not EN 16931 conformant. Without options the output does not change. All details in the documentation “Configuration”.
InvoiceFormatOptions options = InvoiceOptionsBuilder .Create() .SetUnitPricePrecision(6) // Prices: 1.1234567 -> 1.123457, 20 -> 20.00 .SetQuantityPrecision(6) // Quantities: 3.5 -> 3.5, 3 -> 3 .SetPercentPrecision(4) // Tax rates, allowance/charge and discount percentages .SetExchangeRatePrecision(6) // Exchange rate of the tax currency .Build(); invoice.Save("factur-x.xml", ZUGFeRDVersion.Version25, Profile.Comfort, ZUGFeRDFormats.CII, options); Dim options As InvoiceFormatOptions = InvoiceOptionsBuilder _ .Create() _ .SetUnitPricePrecision(6) _ .SetQuantityPrecision(6) _ .SetPercentPrecision(4) _ .SetExchangeRatePrecision(6) _ .Build() invoice.Save("factur-x.xml", ZUGFeRDVersion.Version25, Profile.Comfort, ZUGFeRDFormats.CII, options) -
Serial numbers and batches per line (BG-X-84) CoreIndividual delivered units can be stated on the invoice line with serial number (BT-X-307) and batch number (BT-X-306), for example one entry per machine. The data is preserved when reading (EXTENDED profile, ZUGFeRD 2.4 and 2.5). See “Serial Numbers and Batches”.
// Individual delivered units with serial number (BT-X-307) and batch (BT-X-306), EXTENDED profile only item.AddIndividualTradeProductInstance(supplierAssignedSerialId: "KS40-2026-000117", batchId: "L2607-A"); item.AddIndividualTradeProductInstance(supplierAssignedSerialId: "KS40-2026-000118", batchId: "L2607-A"); ' Individual delivered units with serial number (BT-X-307) and batch (BT-X-306), EXTENDED profile only item.AddIndividualTradeProductInstance(supplierAssignedSerialId:="KS40-2026-000117", batchId:="L2607-A") item.AddIndividualTradeProductInstance(supplierAssignedSerialId:="KS40-2026-000118", batchId:="L2607-A") -
Source PDFs are sanitized for PDF/A PDFSource PDFs from report generators often bring PDF/A violations that used to be carried over unchanged. FactoorSharp now sets missing annotation flags (rule 6.3.2), embeds missing fonts and repairs the encoding of symbolic TrueType fonts as produced by barcode generators (rule 6.2.11.6). Every repair appears in the
PdfSaveResult. -
Invoice rendering in German, English and French VisualizationThe PDF and HTML output can be produced in German, English or French through the
Cultureof the render options. Labels, document titles, units as well as number and date formats follow the chosen language. Without a setting the output stays German. See “Visual representation of invoices”.
// Render the invoice in English or French – language, number and date formats follow the culture InvoiceVisualizer.RenderPdf(invoice, "invoice-en.pdf", options: new PdfRenderOptions { Culture = CultureInfo.GetCultureInfo("en-GB") }); string html = InvoiceVisualizer.RenderHtml(invoice, options: new HtmlRenderOptions { Culture = CultureInfo.GetCultureInfo("fr-FR") }); ' Render the invoice in English or French – language, number and date formats follow the culture InvoiceVisualizer.RenderPdf(invoice, "invoice-en.pdf", options:=New PdfRenderOptions With {.Culture = CultureInfo.GetCultureInfo("en-GB")}) Dim html As String = InvoiceVisualizer.RenderHtml(invoice, options:=New HtmlRenderOptions With {.Culture = CultureInfo.GetCultureInfo("fr-FR")}) -
More details in the invoice rendering VisualizationThe rendering now shows the despatch advice (BT-16), the buyer reference and the free texts of a line, lists document references (BT-17, BT-18, BT-122, BT-128) in readable form and chooses a fitting document title for every invoice type. Prices, quantities and percentages appear with all significant decimal places, so the rendering does not differ from the embedded XML.
-
Validation report in your language ValidationFor the extended validation through the online service you can set the language of the validation report: German, English or French. Without a setting it stays German; for purely local validation the setting has no effect. Error responses of the service are also translated more reliably into understandable exceptions.
// Set the language of the validation report: "de", "en" or "fr" ValidationResult result = await FacturXValidator.ValidateAsync("invoice.pdf", language: "en"); ' Set the language of the validation report: "de", "en" or "fr" Dim result As ValidationResult = Await FacturXValidator.ValidateAsync("invoice.pdf", language:="en") -
Missing optional data stays empty CoreOptional codes that are missing in an invoice used to be replaced by a default value when reading and written back when saving again – for example the account type (BT-X-290), the tax currency (BT-6) or reasons for allowances and charges. They now stay empty (
null). Please note: code that relied on the former default values must check fornull. -
Further fixes CoreFormat 616 (calendar week, CCYYWW) rejects weeks that the year does not contain. The subject code AAG for instructions of the party has also been added.
Version 19.1.1
Version 19.1.1 fixes several bugs in writing ZUGFeRD 2.0/2.5 and UBL invoices and extends validation for Spanish and Italian invoices.
What's new in detail:
-
Line total amount now calculated correctly for ZUGFeRD 2.5 (BT-131) CoreWhen the line total amount was left unset, ZUGFeRD 2.5 used to write 0 instead of calculating it from unit price and quantity like 2.0, 2.4 and UBL do. Invoices with an explicitly set amount were always correct.
-
VAT in accounting currency now correct in UBL too (BT-111, BT-6) CoreThe VAT amount in the accounting currency was never written to UBL and not reliably recognized when reading it back. Both now work correctly, including for XRechnung in UBL format.
-
No more disallowed exemption reasons in the VAT breakdown (BT-120, BT-121) CoreEN 16931 forbids an exemption reason in the VAT breakdown for the tax categories standard rate, zero rated, IGIC and IPSI. Until now this was only observed for zero rated, and not at all in UBL.
-
ZUGFeRD 2.0 catches up on electronic address and line exemption (BT-34, BT-49, BT-X-96, BT-X-97) CoreThe electronic address was not written for any party in ZUGFeRD 2.0, and the line-level exemption reason was missing as well. Both are now written and read, just like in 2.4 and 2.5.
-
Spain: calculation checks and FACe mode for Facturae ValidationLocal validation now checks Facturae invoices for correct line, batch and invoice totals, the NIF check digit and the signature policy. The new
CheckFACeRequirementsoption additionally checks the stricter reception requirements of the Spanish public-sector entry point FACe.
// Additionally check a Facturae invoice against the reception requirements of FACe // (the entry point for invoices to Spanish public administrations) ValidationResult result = await Validator.ValidateAsync("invoice.xsig", new ValidationOptions { CheckFACeRequirements = true }); ' Additionally check a Facturae invoice against the reception requirements of FACe ' (the entry point for invoices to Spanish public administrations) Dim result As ValidationResult = Await Validator.ValidateAsync("invoice.xsig", New ValidationOptions With { .CheckFACeRequirements = True }) -
Italy: FatturaPA schema updated ValidationThe schema for the "fattura ordinaria" was updated to version 1.2.3; the simplified "fattura semplificata" invoice is now validated as well.
-
Crash after QuestPDF update fixed VisualizationAfter updating QuestPDF, outdated native libraries left behind in the build output could cause a crash while rendering. They are now removed automatically.
Version 19.1.0
Version 19.1.0 renders invoices as HTML, supports advance payments and VAT in a foreign currency, extracts text from PDFs together with its position, and detects the invoice format even more reliably.
What's new in detail:
-
Display invoices as HTML VisualizationInvoices can now be rendered as HTML as well as PDF – either as a standalone page or as a building block you embed directly into your web or desktop application. Delivery note, order reference and product characteristics are now shown too.
// Invoice as a standalone HTML page string html = InvoiceVisualizer.RenderHtml(invoice); // Or as a fragment to embed into an existing page string fragment = InvoiceVisualizer.RenderHtml(invoice, options: new HtmlRenderOptions { Standalone = false }); // The PDF output is now called RenderPdf() InvoiceVisualizer.RenderPdf(invoice, "invoice.pdf"); ' Invoice as a standalone HTML page Dim html As String = InvoiceVisualizer.RenderHtml(invoice) ' Or as a fragment to embed into an existing page Dim fragment As String = InvoiceVisualizer.RenderHtml(invoice, options:=New HtmlRenderOptions With {.Standalone = False}) ' The PDF output is now called RenderPdf() InvoiceVisualizer.RenderPdf(invoice, "invoice.pdf") -
Itemize advance payments (BG-X-45) CoreAdvance payments already received can be listed individually in the EXTENDED profile – with amount, payment date, included VAT and a reference to the related prepayment invoice.
// Advance payment with included VAT and a reference to the prepayment invoice invoice.AddAdvancePayment(2975m, new DateTime(2026, 6, 7)) .SetInvoiceReferencedDocument("PI-2026-0601", new DateTime(2026, 6, 1), InvoiceType.PartialInvoice) .AddIncludedTradeTax(475m, 19m, TaxTypes.VAT, TaxCategoryCodes.S); ' Advance payment with included VAT and a reference to the prepayment invoice invoice.AddAdvancePayment(2975D, New DateTime(2026, 6, 7)) _ .SetInvoiceReferencedDocument("PI-2026-0601", New DateTime(2026, 6, 1), InvoiceType.PartialInvoice) _ .AddIncludedTradeTax(475D, 19D, TaxTypes.VAT, TaxCategoryCodes.S) -
VAT in a foreign currency with exchange rate (BG-X-41) CoreFor invoices in a foreign currency, you now set the VAT accounting currency (BT-6), the VAT amount in that currency (BT-111) and the exchange rate in a single step – exactly as EN 16931 expects.
// Invoice in USD, VAT additionally stated in euros invoice.SetTaxCurrencyExchange(taxTotalInAccountingCurrency: 175.08m, accountingCurrency: CurrencyCodes.EUR, conversionRate: 0.9215m, conversionRateTimestamp: new DateTime(2026, 9, 1)); ' Invoice in USD, VAT additionally stated in euros invoice.SetTaxCurrencyExchange(taxTotalInAccountingCurrency:=175.08D, accountingCurrency:=CurrencyCodes.EUR, conversionRate:=0.9215D, conversionRateTimestamp:=New DateTime(2026, 9, 1)) -
Electronic address for every party (BT-34, BT-49, BT-X-160) CoreNot just seller and buyer, but also the ship-to party and other parties can now carry an electronic address, such as an email address. The information is fully preserved when reading invoices.
// Electronic address of the ship-to party invoice.ShipTo.ElectronicAddress = new ElectronicAddress { Address = "goods-in@customer.com", ElectronicAddressSchemeID = ElectronicAddressSchemeIdentifiers.ElectronicMailSmtp }; ' Electronic address of the ship-to party invoice.ShipTo.ElectronicAddress = New ElectronicAddress With { .Address = "goods-in@customer.com", .ElectronicAddressSchemeID = ElectronicAddressSchemeIdentifiers.ElectronicMailSmtp } -
Extract text from PDFs with its position PDFYou can now extract text runs from the visible content of a PDF together with page, position and font size, for example to pick a value from a specific table column. Plain text extraction also reproduces the page layout much more faithfully.
foreach (PdfTextFragment fragment in PdfUtils.ExtractTextFragments("invoice.pdf")) { // Page, position in points and text Console.WriteLine($"Page {fragment.PageNumber}, x={fragment.X:0}, y={fragment.Y:0}: {fragment.Text}"); } For Each fragment As PdfTextFragment In PdfUtils.ExtractTextFragments("invoice.pdf") ' Page, position in points and text Console.WriteLine($"Page {fragment.PageNumber}, x={fragment.X:0}, y={fragment.Y:0}: {fragment.Text}") Next -
More details for invoice lines (BT-158-1, BT-X-98) CoreItems can now be classified using every classification scheme defined by EN 16931, such as UNSPSC or eCl@ss. In the EXTENDED profile you can also state the total of allowances and charges per line.
// Classify the item by UNSPSC item.AddDesignatedProductClassification(DesignatedProductClassificationClassCodes.TST, classCode: "43211503"); // Total of allowances and charges on this line (EXTENDED only) item.TotalAllowanceChargeAmount = 12.50m; ' Classify the item by UNSPSC item.AddDesignatedProductClassification(DesignatedProductClassificationClassCodes.TST, classCode:="43211503") ' Total of allowances and charges on this line (EXTENDED only) item.TotalAllowanceChargeAmount = 12.5D -
More reliable invoice format detection CoreWhether a file is a ZUGFeRD/Factur-X or a UBL invoice is now determined unambiguously from the structure of the document. As a result, invoices from senders that state no or a not yet known specification identifier (BT-24) can be read as well.
-
Current XRechnung validation rules 2.6.0 ValidationLocal validation now uses the current KoSIT rules 2.6.0 for XRechnung 3.0.2. Please note: a violation of rule BR-TMP-2 is now reported as an error instead of a warning. In addition, invoices whose XML is embedded uncompressed in the PDF are validated, and unreadable attachments show up as a clear validation finding.
-
More thorough checks of amounts and totals ValidationThe built-in invoice check now verifies line amounts, allowances and charges, VAT amounts and the amount due much more precisely against the EN 16931 rules – including advance payments, rounding amounts and price base quantities (BT-149).
-
Bug fixes for valid invoices CoreSeveral bugs that could lead to invalid or incomplete invoices have been fixed: no exemption reason is written for zero-rated supplies (category Z) anymore (BR-Z-10), MINIMUM invoices no longer contain an electronic address that is not allowed there, empty IBAN details are omitted, UBL prices keep all their decimal places, and the receiving advice reference (BT-15) is read and written correctly in UBL.
Version 19.0
Version 19.0 introduces free, local invoice validation, improves how XRechnung price details and PDF attachments are handled, and hardens FactoorSharp against crafted documents.
Highlights
Version 18.5
Version 18.5 introduces support for invoicing periods, improves how payment terms are handled, and fixes several bugs around ZUGFeRD 2.0 and 2.5.
Highlights
Version 18.4
Version 18.4 introduces a new visualization component and fixes a number of bugs in working with ZUGFeRD 2.5.
Highlights
Version 18.3
Version 18.3 adds support for ZUGFeRD 2.5 and extends validation to Italian and Spanish invoices.
Highlights
Version 18.2
Version 18.2 extends PDF processing further and improves quota management for validation.
Highlights
Version 18.1
Version 18.1 extends FactoorSharp's PDF processing further and adds signing support for all .NET target platforms.
Highlights
Version 18.0
Version 18.0 is the first published release of FactoorSharp. It forms the stable foundation for working with ZUGFeRD, Factur-X and XRechnung in modern .NET applications.