PDF Invoice for WooCommerce
Creates a PDF invoice for WooCommerce orders when they reach the statuses you choose, attaches it to the order emails you choose, and lets customers and shop staff download invoices and packing slips. PDFs are made on your own server.
Version 1.6 WooCommerce plugin Free plugin Requires WordPress 6.5 Requires PHP 7.4 Tested up to 7.1 Updated 6 Oct 2026
1. Overview
PDF Invoice for WooCommerce creates a PDF invoice for WooCommerce orders and a PDF packing slip on request. An invoice is made when an order reaches one of the statuses you choose, and also whenever it is attached to an email or downloaded, so it always shows the order as it is now. You choose which WooCommerce emails carry the invoice. Customers can download it from their order pages, guests through the secure order link, and shop staff can download invoices and packing slips from the WooCommerce Orders list.
The plugin is free. The PDFs are made on your own server with a PDF library that is bundled in the plugin's vendor/ folder (Dompdf). That library turns the plugin's HTML templates into PDF files. It makes no calls to an outside service. The plugin is listed on the Plugins screen as "PDF Invoice for Woocommerce" and in the WordPress.org directory under that name.
A separate paid plugin, Advance Invoices for WordPress & WooCommerce, covers PDF invoices and estimates that customers pay online with Stripe or PayPal. This plugin has no such features.
How it works
- You enter your company details and choose three things under WooCommerce → Settings → PDF Invoice: the order statuses that generate an invoice, the statuses that mark it as paid, and the emails it is attached to.
- When an order moves to a "Generate Invoice" or "Mark as Paid" status, the plugin builds the invoice HTML from a template, turns it into a PDF and saves it in
wp-content/uploads/wphub-invoice/under a file name that cannot be guessed. - When WooCommerce sends one of the emails you selected, the plugin makes the invoice again for the order's current state and attaches the file.
- Customers and staff download through links that check who is asking. The files are only a cache: a daily job deletes PDFs older than 7 days, and they are made again when needed.
At a glance
Automatic invoices
Made at the order statuses you choose. Default: Processing. Always re-made from the current order when attached to an email or downloaded.
Email attachments
Attach the invoice to any WooCommerce email that belongs to an order. Default: New order, Processing order, Order details and Completed order.
PAID badge
Orders in the statuses you mark as paid show a green PAID badge and an amount due of zero. Default: Processing.
Sequential numbers
Optional, off by default. Without them the WooCommerce order ID is the invoice number. The starting number defaults to 1000.
VAT number field
Optional, off by default. Added to the classic checkout and the Checkout block, printed on the invoice and the packing slip.
Packing slips
A PDF with items, quantities, addresses and shipping and payment method, without prices, from the Orders list.
What it does not do
- It does not take or record payments. "Paid" and "amount due" follow the order status you chose, not the payments on the order. Refunds do not change the amount due.
- It does not offer other designs. There is one invoice design (Template 1, A4). You can replace its markup by copying the template into your theme.
- It does not add a prefix such as INV- to invoice numbers. The number is the plain order ID or the sequential number. A filter can add a prefix.
- It does not print the shipping address on the invoice (only on the packing slip), and it has no estimates, reminders or recurring invoices.
- It does not keep a history of invoices. Only the latest PDF of an order is stored, for up to 7 days.
- It has no shortcodes, blocks, REST routes or WP-CLI commands.
2. Requirements
| Item | Requirement | Notes |
|---|---|---|
| WordPress | 6.5 or later | From the plugin header (Requires at least). Tested up to 7.1. This page was checked against WordPress 7.1.2. |
| PHP | 7.4 or later | From the plugin header (Requires PHP). WordPress refuses to activate a plugin on an older PHP version. |
| WooCommerce | 10.0 or later, active | The header declares WC requires at least: 10.0 and WC tested up to: 11.1, and Requires Plugins: woocommerce. WordPress will not activate the plugin while WooCommerce is missing or inactive; in WordPress 7.1.2 it shows Error: PDF Invoice for Woocommerce requires 1 plugin to be installed and activated followed by the plugin's name. The plugin has no WooCommerce version check of its own. |
| If WooCommerce is switched off later | The plugin does nothing | The plugin checks on every request whether WooCommerce is active (per site, or network-wide on multisite). If not, it loads none of its features and shows no message. |
| PHP extensions for the PDF library | dom, mbstring, iconv | Required by the bundled libraries. The library also suggests gd (images such as your logo) and zlib (compressed PDF streams). The plugin does not check for any of them and shows no message of its own. A missing extension appears as a PHP error in the request that makes the PDF (a status change, an email or a download); look in your PHP error log. |
| Uploads folder | Writable by PHP | The PDFs and the library's font cache are written to wp-content/uploads/wphub-invoice/. If a PDF cannot be written, no attachment is added and a download shows Invoice could not be generated. |
| Web server | Apache and LiteSpeed honour the plugin's .htaccess file | The plugin writes an .htaccess file that blocks direct access. nginx ignores .htaccess; see Where PDFs are stored. |
| Compatibility declared | HPOS, Cart and Checkout blocks, product block editor | The plugin declares compatibility with High-Performance Order Storage (custom_order_tables), cart_checkout_blocks and product_block_editor at before_woocommerce_init. |
| Advance Partial Shipment for WooCommerce | Optional | The plugin's classes are used by that plugin to make its packing slips and delivery notes with your PDF Invoice settings (see Classes other plugins use). |
3. Installation
- Make sure WooCommerce is installed and active.
- In WordPress open Plugins → Add New Plugin, search for "PDF Invoice for Woocommerce", click Install Now and then Activate. You can also upload the plugin zip under Plugins → Add New Plugin → Upload Plugin.
- Open WooCommerce → Settings → PDF Invoice. A Settings link on the Plugins screen goes there too.
- Enter your company details and choose when invoices are generated, marked as paid and attached to emails (see Settings).
- Click Preview Template to check the layout, then Save changes.
What activation creates
- The folder
wp-content/uploads/wphub-invoice/with a protection fileindex.phpand an.htaccessfile that denies direct access. If the folder cannot be created or written, the plugin tries again whenever it needs it. - The daily WP-Cron event
wphub_invoice_daily_cleanup, which deletes old PDFs. It is also re-created on any request if it is missing. - The option
wphub_invoice_version(written on the first request after activation).
It creates no database tables, roles or pages. The settings option _wphub_invoice is written the first time you save the settings tab. Until then the plugin uses its defaults. The invoice number counter _wphub_last_invoice_number appears when the first sequential number is issued.
Updating
Update from the Plugins screen as usual. The plugin compares the stored version with the code on every request and runs its upgrade once. When it upgrades from a version before 1.6, it deletes all PDF files in uploads/wphub-invoice (version 1.6 and later protect that folder; earlier versions stored PDFs there under names built from the invoice number, which could be guessed) and removes a stray option called wphub-invoice. Your settings, the invoice counter and the invoice numbers stored on orders are kept.
Multisite
The plugin loads on a site of a network when WooCommerce is active on that site or network-wide. Settings, the counter and PDFs are per site (each site's own uploads folder). The cleanup event is created on each site on its first request. uninstall.php works only for the site on which it runs.
4. Quick start
- Open WooCommerce → Settings → PDF Invoice.
- Fill in Company Name, Company Address, Company Email and Company Phone, and paste the address of your logo into Company Logo.
- Under Generate Invoice and Mark as Paid add Completed next to Processing if you attach the invoice to the Completed order email or want customers to keep the Download Invoice button after the order is completed (see the note below).
- Under Attach to Emails keep the emails you want (the defaults are New order, Processing order, Order details and Completed order).
- Click Save changes, then Preview Template to open a sample invoice with your company details in a new tab.
- Place a test order and move it to Processing. Check the email and the Invoice icon in WooCommerce → Orders.
5. Features
When an invoice is made
The plugin makes (or re-makes) an order's PDF invoice in these cases:
- A status change. When an order moves to a status listed under Generate Invoice or Mark as Paid, the plugin renders the invoice (on
woocommerce_order_status_changedat priority 999). If an email attachment already rendered it for that status earlier in the same request, it is not rendered twice. The actionswphub_before_pdf_invoiceandwphub_after_pdf_invoicefire around this rendering, and only here. - An email attachment. When WooCommerce prepares one of the emails you selected, the invoice is re-rendered for the order's current state (unless it was already rendered for this status in this request) and attached. Because the invoice is re-made at that moment, an email never carries an old PAID or due state.
- A download. Every explicit download re-renders the invoice so it reflects the order as it is now.
The rendering happens inside the request that triggers it: a status change during checkout, an email being sent or a download. There is no queue and no background job, and the plugin has no error handling around the PDF library, so a PDF library error is a PHP error in that request.
If you clear every choice under Generate Invoice, Mark as Paid or Attach to Emails and save, the plugin stores an empty list for it and does not silently restore the defaults. Defaults apply only to settings that have never been saved.
Email attachments
Attach to Emails lists every email that WooCommerce knows, by its title. Select the ones that should carry the invoice. The plugin hooks woocommerce_email_attachments at priority 999 and attaches the invoice only when the email's ID is one you selected and the email belongs to an order. Account emails (for example a new account or a password reset) have no order, so nothing is attached to them even if you select them.
| Email ID | Title in WooCommerce 11.1.2 | Selected by default |
|---|---|---|
new_order | New order | Yes |
customer_processing_order | Processing order | Yes |
customer_invoice | Order details | Yes |
customer_completed_order | Completed order | Yes |
customer_on_hold_order, cancelled_order, failed_order, customer_refunded_order, customer_note | Order on-hold, Cancelled order, Failed order, Refunded order, Customer note | No (can be selected) |
The titles are the ones WooCommerce 11.1.2 gives these emails with its default settings; when WooCommerce's Block Email Editor (alpha) feature is switched on, some titles differ (for example Processing order becomes Order confirmation), but the IDs stay the same. The Refunded order choice only covers full refunds: for a partial refund WooCommerce sends the email with the ID customer_partially_refunded_order (WooCommerce 11.1.2, class-wc-email-customer-refunded-order.php), which is not in the list and so never gets the invoice.
The attachment path is the invoice file for that order. If the file cannot be made or written, the email is sent without an attachment and nothing is reported.
Selecting an email does not check the order's status. An invoice is attached to a Cancelled or Failed order email in whatever state the order is in at that moment, and with sequential numbers on it takes a number (see below).
Invoice numbers
- Sequential Invoice Numbers off (default). The invoice number is the WooCommerce order ID (the internal post or order ID, not the order number a plugin may show to customers). The invoice also prints Order Number: with the order's number as WooCommerce reports it.
- Sequential Invoice Numbers on. Each order gets one number the first time an invoice is made for it, stored on the order as
_wphub_invoice_num. The first invoice gets exactly the Starting Invoice Number (default 1000) and each new invoice the next number. The number never changes for an order once stored.
How the counter works, from the code: the option _wphub_last_invoice_number holds the last number issued. It is created with the starting number minus 1. Each new number is made by one database statement, UPDATE of the option to the larger of "last number plus 1" and "starting number". Raising the starting number later therefore takes effect with the next invoice, and lowering it never re-issues a number. The new value is then read back with a separate query. A start value that is blank or not a number is treated as 1000 when numbers are issued.
A number is taken the first time an invoice is made for an order, by any of the routes under When an invoice is made. That includes a download by an administrator from the Orders list, which has no status check, and an email you attached for a status such as Cancelled. Only customer downloads are limited by status.
The filter wphub_invoice_number can change the number that is printed and used in file names, for example to add a prefix (see Filters). The settings screen gives INV-1001, INV-1002 as an example of sequential numbering, but the plugin itself adds no prefix.
Paid invoices and amount due
If the order's status is one of the Mark as Paid statuses, the invoice shows a green PAID badge and the amount due is zero. Otherwise there is no badge and the amount due is the order total. This follows the status only: the plugin does not look at payments or refunds. The amount due appears twice: in the header as Amount Due: and as the last row of the totals.
VAT number field
With VAT Number Field ticked (off by default) the plugin adds an optional field labelled VAT Number to checkout:
- Classic checkout. A billing field named
company_vat, not required, shown at priority 130 of the billing section. The value is saved as the order metawphub_company_vatwhen the order is processed. - Checkout block. A field registered through WooCommerce's Checkout Fields API with the ID
wphub/company-vat, in the Contact information step, labelled VAT Number (optional), plain text, at most 40 characters, autocomplete off. WooCommerce stores it as_wc_other/wphub/company-vat, and the plugin copies the value towphub_company_vattoo, including when it is edited in the admin, so invoices and integrations read one key. If registering the field fails (for example the ID is already registered), checkout is left alone.
The value is printed on the invoice and the packing slip as VAT NO: value (the classic key first, then the block key) and, for the classic field, on the admin order screen as VAT Number: value under the billing address (WooCommerce lists block fields there itself). Turning the setting off removes the field from checkout but keeps values already saved on orders, and they still print.
What the invoice shows
Template 1 prints, in this order:
- Header. Your logo on the left and the word INVOICE on the right, then your company name, address, phone and email on the right.
- Bill to. The customer's first and last name, company, billing address (without repeating the name and company), phone, email and VAT number.
- Invoice details. Invoice No:, Order Number:, Invoice Date: (the date the order was created, formatted
d/m/Yunless you change it withwphub_invoice_date_format), the PAID badge when it applies, and Amount Due:. - Items. A table with #, Item, Qty, Unit Price and Total. Each item shows its name, its SKU when it has one, and its item details (variation attributes, product add-ons and other item meta). Unit Price is the line subtotal divided by the quantity, before discounts. Total is the line subtotal as WooCommerce formats it. A line whose product has since been deleted is still printed (without a SKU). Lines that are not products, such as fees, are not listed in the item table, and appear in the totals instead.
- Totals. The rows WooCommerce returns for the order's totals (as in its own emails: subtotal, discounts, shipping, fees, taxes, payment method, refunds and total, as they apply), then Amount Due:.
- Footer. Your Footer Note and Copyright Text, and Page X of Y at the bottom right of every page (a filter changes or removes that text).
The paper is A4 portrait (the filter wphub_invoice_paper_size changes it). Amounts use the order's currency. The bundled DejaVu Sans font is used for currency symbols and for non-Latin text such as Cyrillic and Greek, which print correctly. All customer and company values are escaped before they go into the PDF HTML, and PHP and JavaScript inside templates are switched off in the PDF library.
Company logo
Paste the full address of an image into Company Logo. The settings screen recommends PNG or SVG, at most 200 pixels wide. The plugin accepts any image type it can read, including PNG, JPG and SVG, and embeds the picture in the PDF so the PDF library does not have to fetch it. It is printed at no more than 250 by 120 pixels.
- If the address is a Media Library file of this site, the file is read from disk.
- Otherwise, if it points to this site's URL, it is mapped to a file below the WordPress folder and read (never outside it).
- Otherwise, if it begins with
http://orhttps://, the plugin downloads it with the WordPress HTTP API (15 second timeout, 5 MB limit; only an HTTP 200 answer is used).
The image type is detected from the file (a text or XML file is treated as SVG), and the picture is turned into a data: address. When that address is under 700 KB (an image of roughly half a megabyte) it is cached in a transient for one day; larger images are read or downloaded again for every PDF. If the picture cannot be read, the address itself is put in the page and the PDF library tries to fetch it. If the logo field is empty, nothing is printed in its place.
Packing slips
A packing slip is a PDF without prices. Staff download it with the packing slip icon in the Orders list (see Downloads). It shows:
- the title Packing Slip and your company details,
- BILL TO with name, company, billing address, phone, email and VAT number,
- SHIP TO with the shipping name, company and address, only when the order has a shipping address,
- Order Number: and Order Date:,
- a table with #, Item (name, SKU, item details) and Qty,
- Shipping Method : (only when the order has one) and Payment Method :, and
- your Copyright Text (the footer note is not printed on packing slips) and Page X of Y.
A packing slip is made fresh for each download and streamed to the browser. It is never saved as a file in the uploads folder. The Packing Slip Button setting (on by default) switches the icon in the Orders list on or off. It does not affect the download endpoint itself.
Downloads: who can get a PDF
PDFs are never served by a direct file address. Every download goes through admin-ajax.php with a link that carries a nonce and, for customers, the order key. The link checks, in this order: the nonce (Security check failed., HTTP 403), an order ID (Invalid request., 400), that the order exists and is a real order, not a refund (Order not found., 404), and then permission (You are not allowed to do this., 403).
| Where | Who sees it | What it gives |
|---|---|---|
| Icon buttons in the Orders list (WooCommerce → Orders), legacy and HPOS lists | Users who can edit_shop_orders | The Invoice icon (download, label "Download Invoice") on every order, whatever its status, and the Packing Slip icon (label "Download Packing Slip") unless the Packing Slip Button setting is off. |
| Download Invoice button under the order table on the View order and Order received pages | The logged-in customer who owns the order, or a visitor who has the order's key in the page address (guest order links) | The invoice PDF, only when the order is eligible (see below). |
| Invoice action in My Account → Orders | The customer who owns the order | The invoice PDF, only when the order is eligible. Its accessible label is "Download invoice for order N". |
There is no download button on the single order edit screen; use the Orders list.
Which orders customers can download
A customer sees the button, and can download, only when the order's status is in Generate Invoice or Mark as Paid, or the order already has a sequential invoice number. That stops customers pulling invoices for pending, failed or cancelled orders and using up sequential numbers. The filter wphub_invoice_customer_can_download can change the decision. Staff (edit_shop_orders) are not limited by status.
Link lifetime
The links contain a WordPress nonce. In WordPress 7.1.2 a nonce is valid for between 12 and 24 hours (the nonce_life filter changes the lifespan). The customer buttons are built each time the page is shown, so a button on a freshly loaded page works. A link that someone copied or bookmarked stops working after that time, and the customer opens the order page again. For a guest, the nonce is not tied to a login.
What the browser receives
The PDF is sent with Content-Type: application/pdf and Content-Disposition: attachment, the file name invoice-<number>.pdf or packing-slip-<order id>.pdf, no-cache headers and X-Robots-Tag: noindex, nofollow. The invoice is read from disk through WordPress's file system API; if that cannot be used (some hosts ask for FTP credentials), the download fails with Invoice could not be generated. If a packing slip cannot be made, the message is Packing slip could not be generated.
Preview Template
On the settings tab, the Preview Template link next to the template list opens a sample invoice (a made-up customer, Blue Sky Marketing Limited, two products and a PAID badge) in a new tab, shown inline in the browser, with your company details, logo, footer note and copyright text filled in. It uses the settings that are saved, not unsaved changes in the form, so save first. It needs the manage_woocommerce capability and a valid nonce, and works only for logged-in users. If you copy template-1.php into your theme (see Templates), the preview loads your copy too and passes it no order, so a copy that uses $order without checking it will not render in the preview.
Where PDFs are stored and who can reach them
- Folder.
wp-content/uploads/wphub-invoice/(the site's own uploads folder on multisite), with anindex.phpand an.htaccessfile. The.htaccessdenies all direct access on Apache 2.4 (Require all denied) and on 2.2 (Deny from all); LiteSpeed reads it too. - File name.
invoice-<invoice number>-<16 characters>.pdf. The 16 characters are taken from a hash that WordPress makes with your site's secret keys from the order ID and the invoice number, so a name cannot be worked out from the invoice number alone. - Font cache. The PDF library's font folder and font cache are a sub-folder,
wphub-invoice/fonts, and its temporary files go to WordPress's temp folder. That keeps them out of the plugin folder, which can be read-only and is replaced on update. - Retention. The daily event deletes
.pdffiles older than 7 days, going through sub-folders, judged by the file's modification time (the filterwphub_invoice_cleanup_dayschanges the 7, never below 1). Fonts are left. PDFs are re-made on demand, so deleting them is safe. Because PDFs hold customer name, address, phone, email and VAT number, you can lower the number of days. - On nginx. nginx does not read
.htaccess, so there the unguessable file name is the only barrier. To block the folder entirely, add a rule such aslocation ^~ /wp-content/uploads/wphub-invoice/ { deny all; }to your server configuration. This is a suggestion; the plugin does not do it. - A recorded address. After each invoice is written, the plugin saves its direct file address as the order meta
wphub_invoice_url. On Apache or LiteSpeed that address is blocked by design. It is not used for downloads; it is there for other code to read.
The plugin never stores a packing slip file.
When each feature arrived
From the readme's changelog:
- 1.1: a packing slip for the Advance Partial Shipment plugin.
- 1.4: packing slip PDFs, the Invoice and Packing Slip icons in the Orders list, the Download Invoice button on the order details page, and the daily cleanup of old PDFs.
- 1.5: safe placeholder replacement, an atomic invoice-number increment, and a capability check on the preview.
- 1.6: protected PDF folder and unguessable file names, escaped customer data, VAT Number field for the Checkout block, the Invoice action in My Account → Orders, customer downloads limited to eligible orders, invoices re-made when attached or downloaded, item details on invoices, page numbers, non-Latin text, the first sequential invoice getting exactly the starting number, the new filters and the placeholders
{billing_address_only}and{shipping_address_only}.
Safety measures
- Customer and order values placed into the PDF HTML are escaped, so checkout input cannot inject markup into the PDF renderer. Placeholder values are replaced as plain text (
str_replace), so characters such as$1in an address cannot corrupt the document. - Embedded PHP and JavaScript are disabled in the PDF library. Remote resources are enabled (so a custom template can load an image by address). The standard templates load none, and your logo is embedded.
- The preview checks the capability and the nonce and only accepts a template name that exists as a file in the plugin's
preview/folder, else it usestemplate-1. - All settings are sanitised as they are saved: the logo address as a URL, the email as an email address, the starting number as a whole number (or empty), the template name restricted to letters, digits, hyphens and underscores, and every other text with
sanitize_textarea_field, which removes HTML tags and keeps line breaks.
6. Settings
All settings are on WooCommerce → Settings → PDF Invoice (admin.php?page=wc-settings&tab=wphub_invoice), under the heading "PDF Invoice for Woocommerce". The tab needs the manage_woocommerce capability. They are saved with WooCommerce's Save changes button into one option, _wphub_invoice (not autoloaded). Fields appear in the order of the table. Until the first save, the defaults below apply; the form shows them too.
| Setting (label) | Key | Default | What it does |
|---|---|---|---|
| Invoice Template | template | template-1 | Only one choice, "Template 1 (A4 Size)". The Preview Template link beside it shows a sample invoice (see Preview Template). |
| Sequential Invoice Numbers | sequential_number | Off | Gives each order its own running invoice number. When off, the invoice number is the WooCommerce order ID. See Invoice numbers. |
| Starting Invoice Number | start_number | 1000 | A whole number from 0, step 1. The number given to the first sequential invoice. Raising it later makes the next invoice continue from the new number; lowering it never re-uses a number already issued. Only used when Sequential Invoice Numbers is on. A value that is not a number is saved as empty, which counts as 1000. |
| VAT Number Field | vat_number | Off | Adds an optional VAT Number field to the classic checkout and the Checkout block. The value is shown on the order and on the invoice and packing slip. |
| Generate Invoice | generate_invoice | Processing | Order statuses (a multi-select of every WooCommerce order status) at which the invoice is generated. Also one of the two lists that decide whether customers may download an invoice. |
| Mark as Paid | paid_invoice | Processing | Order statuses at which the invoice shows PAID and an amount due of zero. Also counted when deciding whether customers may download. |
| Attach to Emails | emails | New order, Processing order, Order details, Completed order (the IDs new_order, customer_processing_order, customer_invoice, customer_completed_order) | The WooCommerce emails the invoice is attached to. Emails without an order are ignored. |
| Company Logo | logo | Empty | The full address of your logo image. Saved as a URL. See Company logo. |
| Company Name | company_name | Empty | Your business name on invoices and packing slips. Also used as the logo's alternative text. |
| Company Address | company_address | Empty | A text area (5 rows). Line breaks are kept. |
| Company Email | company_email | Empty | Contact email on invoices. Saved with email sanitising, so an invalid address is saved as empty. |
| Company Phone | company_phone | Empty | Contact phone on invoices. |
| Footer Note | footer_note | Thank you for your order. | A short note at the bottom of every invoice. Not printed on packing slips. |
| Copyright Text | copyright_info | © Copyright {your_company_name} {current_year}, All Rights Reserved. | A copyright line at the bottom of invoices and packing slips. {your_company_name} becomes your company name and {current_year} the current year (UTC). The footer note may use the same two placeholders. |
| Packing Slip Button | admin_packing_slip | On | Shows the Packing Slip icon in the WooCommerce Orders list. Unticking it saves an explicit "no". |
The option also stores the lists as arrays of status keys without the wc- prefix (for example processing), and checkboxes as 1 (checked) or yes; older versions stored yes or no, and the plugin accepts 1, yes and true as on. Everything read from the option passes through sanitize_textarea_field again.
get_default_settings() listed above. The Footer Note and Copyright Text fields also carry other default texts in their definitions (Thank you for your business! and © {current_year} {your_company_name}. All rights reserved.), but the form always passes the stored or default value explicitly, so those texts are never shown or used.7. Reference for developers
AJAX endpoints
All three are admin-ajax.php actions. There are no REST routes, shortcodes, blocks or WP-CLI commands.
| Action | Who | Parameters | What it does |
|---|---|---|---|
wphub_download_invoice (wp_ajax_ and wp_ajax_nopriv_) | Owner of the order (logged in, or with the order key), or edit_shop_orders with admin=1 | order_id, order_key (customers; key is also accepted), admin (1 for staff), nonce wphub_invoice_nonce for the action wphub_download_invoice | Re-makes the invoice and streams it as a download. Customers are also limited to eligible orders. |
wphub_download_packing_slip (priv and nopriv) | As above, without the eligibility rule | order_id, order_key, admin, nonce wphub_packing_nonce for the action wphub_download_packing_slip | Makes the packing slip and streams it. The plugin builds links for this action only in the admin Orders list. |
wphub-preview (priv only) | manage_woocommerce | template (default template-1), nonce invoice-pdf-preview for the action wphub-preview | Streams a sample invoice inline. |
Helper methods that build the links: wexphub_invoice()->get_invoice_download_url( $order, $admin = false ) and get_packing_slip_download_url( $order, $admin = false ).
Filters
| Filter | Parameters and default | What it does |
|---|---|---|
wphub_invoice_placeholders | array $replacements, WC_Order $order, array $settings | The map of {placeholder} to HTML-safe value used to fill a template. Add your own entries; values must already be escaped. |
wphub_invoice_number | $invoice_num, $order_id, array $settings | The invoice number that is printed, used in file names and in the download file name. The number stored on the order is not changed. |
wphub_invoice_paper_size | 'A4' | The PDF paper size (a name the PDF library knows, such as letter). Portrait. |
wphub_invoice_date_format | 'd/m/Y' | The date format of Invoice Date and Order Date, passed to wc_format_datetime(). |
wphub_invoice_page_number_text | Page {PAGE_NUM} of {PAGE_COUNT} (translated) | The page number text. Keep {PAGE_NUM} and {PAGE_COUNT}. Return an empty string for no page numbers. |
wphub_invoice_customer_can_download | bool $allowed, WC_Order $order | Whether a customer may see and use the download button of an order. |
wphub_invoice_dompdf_options | \Dompdf\Options $options | The PDF library's options for invoices and packing slips. Return the object. Set before it is used: font folder, cache, temp folder, remote files off and on. |
wphub_invoice_cleanup_days | 7 | Age in days after which PDFs are deleted (never below 1). |
wphub_invoice_settings | array $settings | The array of WooCommerce settings-field definitions for the PDF Invoice tab, so you can add or change fields. |
The plugin also applies two core filters itself: woocommerce_order_formatted_billing_address and woocommerce_order_formatted_shipping_address (the same ones WooCommerce applies) when it prints an address without the name lines, and active_plugins when it checks whether WooCommerce is active.
// Add a prefix to invoice numbers.
add_filter( 'wphub_invoice_number', function ( $number, $order_id, $settings ) {
return 'INV-' . $number;
}, 10, 3 );
// US Letter paper.
add_filter( 'wphub_invoice_paper_size', function () {
return 'letter';
} );
// Let customers download invoices of completed orders as well.
add_filter( 'wphub_invoice_customer_can_download', function ( $allowed, $order ) {
return $allowed || $order->has_status( 'completed' );
}, 10, 2 );
// Add a placeholder that your copy of the template can print.
add_filter( 'wphub_invoice_placeholders', function ( $replacements, $order ) {
$replacements['{customer_note}'] = esc_html( $order->get_customer_note() );
return $replacements;
}, 10, 2 );
// Keep PDFs for 2 days only.
add_filter( 'wphub_invoice_cleanup_days', function () {
return 2;
} );
These work because each filter runs when the PDF is made (or, for the cleanup, when the daily event runs). For the placeholder example, the text {customer_note} must appear in your template copy.
Actions
| Action | Parameters | When |
|---|---|---|
wphub_before_pdf_invoice | WC_Order $order | Just before the invoice is rendered on an order status change (the generation described under When an invoice is made). Not fired for email attachments or downloads. |
wphub_after_pdf_invoice | WC_Order $order | Just after that rendering and saving. |
wphub_invoice_daily_cleanup | none | The daily cleanup event (see below). |
WooCommerce and WordPress hooks the plugin attaches to
| Hook | Priority | What the plugin does |
|---|---|---|
woocommerce_loaded | 10 | Loads the saved settings. |
woocommerce_order_status_changed | 999 | Generates the invoice for statuses in Generate Invoice or Mark as Paid. |
woocommerce_email_attachments (filter, 4 arguments) | 999 | Adds the invoice path to the selected emails. It runs late so it sees the attachments others added, and it only adds. |
woocommerce_settings_tabs_array (filter), woocommerce_settings_tabs_wphub_invoice, woocommerce_admin_field_wphub_invoice_setting, woocommerce_update_options_wphub_invoice | 50 for the tab list, 10 for the others | The PDF Invoice settings tab: showing it, the three multi-selects, and saving to _wphub_invoice. |
woocommerce_checkout_fields (filter) | 10 | Adds the classic VAT Number field when the setting is on. |
woocommerce_checkout_order_processed | 10 | Saves wphub_company_vat. |
woocommerce_init | 10 | Registers the block checkout VAT field. |
woocommerce_set_additional_field_value | 10, 4 arguments | Mirrors the block VAT value into wphub_company_vat on orders. |
woocommerce_admin_order_data_after_billing_address | 10 | Shows the classic VAT number on the admin order screen. |
woocommerce_admin_order_actions_end | 10 | Adds the Invoice and Packing Slip icons to the Orders list. |
woocommerce_order_details_after_order_table | 10 | Adds the Download Invoice button on the view order and order received pages. |
woocommerce_my_account_my_orders_actions (filter) | 10 | Adds the wphub-invoice action (label Invoice) to My Account → Orders. |
before_woocommerce_init | 10 | Declares the compatibility features listed under Requirements. |
init | 5 and 10 | At 5: the one-time upgrade. At 10: self-healing cleanup schedule, and (admin only) the settings class. |
admin_enqueue_scripts | 999 | Loads the Orders-list icon style on the order list screens and the preview script on the PDF Invoice tab only. |
plugin_action_links_<basename> (filter) | 10 | Adds the Settings link. |
Templates and placeholders
The invoice and the packing slip are built from the template files templates/template-1.php and templates/packing-slip.php with WooCommerce's template loader. A copy in yourtheme/invoice-for-wc/ (child theme first) replaces the file. The templates receive $order (the WC_Order) and $settings (the settings array). After the template has run, the plugin replaces these placeholders in its output:
| Placeholder | Becomes |
|---|---|
{invoice_logo} | The logo image tag (empty if no logo). |
{your_company_name}, {your_company_address}, {your_company_phone}, {your_company_email} | Your company details, escaped (address with line breaks). |
{invoice_footer_note}, {invoice_copyright_note}, {current_year} | The footer note, the copyright text, and the year. |
{invoice_number} | Invoice No: N |
{invoice_order_number} | Order Number: N |
{invoice_date}, {order_date} | Invoice Date: … and Order Date: …, both the order's creation date. |
{invoice_status} | The PAID badge, or empty. |
{label_due_amount}, {amount_due} | Amount Due: price, and the price alone. |
{billing_first_name}, {billing_last_name}, {billing_company_name}, {billing_phone}, {billing_email} | Billing details, escaped. |
{billing_address}, {shipping_address} | The formatted address including name and company. |
{billing_address_only}, {shipping_address_only} | The formatted address without the name and company lines. The shipping one is empty when the order has no shipping address. (Since 1.6.) |
{shipping_first_name}, {shipping_last_name}, {shipping_company_name}, {shipping_phone} | Shipping details, escaped. The phone needs WooCommerce's shipping phone. |
{billing_vat_number} | VAT NO: value, or empty. |
Replacements run in the order of this map, so the footer note and copyright text may contain {your_company_name} and {current_year}. The filter wphub_invoice_placeholders can add to it. The preview file preview/template-1.php contains only the company placeholders ({invoice_logo}, {invoice_footer_note}, {invoice_copyright_note}, {your_company_name}, {your_company_address}, {your_company_phone} and {your_company_email}) and fixed sample text for the rest. The preview also fills {current_year}, which can appear inside the copyright text.
Only the file name template-1 can be chosen: the selected name must exist in the plugin's templates/ folder, otherwise template-1 is used. To restyle the invoice, override template-1.php in your theme.
Classes other plugins use
The plugin's code says these are kept under their names because other plugins, such as Advance Partial Shipment for WooCommerce, use them to make their own packing slips and delivery notes with your PDF Invoice settings. They are not documented as a stable API beyond that.
wexphub_invoice()returns the main object (WEXP_Hub_PDF_Invoice) when WooCommerce is active. Itsget_settings()returns the settings merged with defaults.WEXP_Hub_Invoice:render_pdf( $html )(static, returns the PDF as a string, A4 portrait with page numbers),get_dompdf()(static),preview_invoice( $template, $get_template = true, $filename = '' )(streams a PDF inline and exits),get_invoice_path( $order, $refresh = false ),get_invoice_num( $order_id ).WEXP_Hub_Invoice_Format::format_invoice( $html, $order, $settings )fills the placeholders above.WEXP_Hub_Packing_Slip::get_packing_slip_pdf( $order )returns the packing slip as a PDF string.WEXP_Hub_PDF_Invoice::get_pdf_dir()(static) returns the PDF folder path and creates it with its protection files.
Constants: WPHUB_INV_VER (the version) and WPHUB_INV_FILE (the main file), both defined only if not already defined, and the class constants PDF_DIR (wphub-invoice) and BLOCK_VAT_FIELD (wphub/company-vat).
Cron events
| Event | Schedule | What it does |
|---|---|---|
wphub_invoice_daily_cleanup | Daily (WP-Cron) | Deletes .pdf files older than 7 days (filter wphub_invoice_cleanup_days) from uploads/wphub-invoice and its sub-folders. Scheduled on activation and on any request where it is missing, and cleared on deactivation and uninstall. |
Options, order meta and transients
| Name | Kind | Contents |
|---|---|---|
_wphub_invoice | Option, not autoloaded | The settings: template, sequential_number, start_number, vat_number, generate_invoice, paid_invoice, emails, logo, company_name, company_address, company_email, company_phone, footer_note, copyright_info, admin_packing_slip. |
_wphub_last_invoice_number | Option, not autoloaded | The last sequential invoice number issued. |
wphub_invoice_version | Option, not autoloaded | The plugin version the upgrade routine last ran for. |
wphub-invoice | Option (legacy) | Written by versions before 1.6 by the settings screen; the upgrade removes it. |
_wphub_invoice_num | Order meta | The sequential invoice number of the order. |
wphub_company_vat | Order meta | The VAT number, from either checkout. |
_wc_other/wphub/company-vat | Order meta (WooCommerce) | The block checkout VAT number as WooCommerce stores it. |
wphub_invoice_url | Order meta | The direct file address of the invoice PDF, set when the invoice is written. |
wphub_invoice_logo_<md5 of the logo address> | Transient, 1 day | The logo as a data: address, when under 700 KB. |
The plugin creates no database tables. The bundled PDF library lives in vendor/ and is loaded only when a PDF is made. File layout: invoice-for-wc.php (hooks and downloads), classes/ (invoice, format, packing slip, settings), templates/, preview/, includes/setting-fields.php, assets/ (an Orders-list style and the preview script), languages/ (a translation template, text domain pdf-invoice-for-woocommerce), uninstall.php.
8. Privacy and data
The plugin sets no cookies, does no tracking and makes no calls to an analytics or invoicing service. PDFs are made on your own server. It does not register a privacy policy text, a personal data exporter or an eraser.
What is stored
- Settings (your company details and choices) in the option
_wphub_invoice, plus the invoice counter and the plugin version in two more options. - On each order: the sequential invoice number (
_wphub_invoice_num), the VAT number the customer typed (wphub_company_vatand, for the block checkout, WooCommerce's own_wc_other/wphub/company-vat) and the direct file address of the PDF (wphub_invoice_url). - PDF files containing personal data in
uploads/wphub-invoice/: the customer's name, company, billing address, phone, email and VAT number, the order contents and the totals. Each file stays until the daily cleanup deletes it, 7 days after it was last written by default, or until you uninstall the plugin. Packing slips are never saved. - Font cache files of the PDF library in
uploads/wphub-invoice/fonts. - A cached copy of your logo as a transient for one day.
What is sent anywhere
No customer, order or visitor data is sent to any service. The only request the plugin can make is to download your company logo with a plain GET request (15 second timeout, 5 MB limit) when the logo cannot be read from your own site's files: normally an address on another site, but also an address of your own site that does not map to a readable file. It goes to the host in that address, carries no order data, and happens when a PDF is made and the logo is not cached. WordPress adds its usual user agent to that request, which contains the WordPress version and your site address. The PDF library is allowed to fetch remote images that a custom template references; the standard templates reference none.
If you installed the plugin from the WordPress.org directory, WordPress itself contacts WordPress.org to look for updates, as it does for every plugin there. The plugin makes no update request of its own.
Deactivation and uninstall
- Deactivating clears the daily cleanup event. Everything else stays: settings, counter, order meta and the PDF files (nothing deletes them while the plugin is off).
- Deleting the plugin runs
uninstall.php, which clears the cleanup event, deletes the wholeuploads/wphub-invoicefolder with its PDFs, fonts and protection files (through the WordPress file system API, so it does nothing if that API needs FTP credentials), and deletes the optionwphub_invoice_version. On multisite this applies to the site where the uninstall runs. - It keeps on purpose the settings option
_wphub_invoice, the counter_wphub_last_invoice_numberand the invoice numbers and VAT numbers on orders, so that reinstalling never re-issues an invoice number that was already used. The logo transient expires within a day. To remove the settings and counter, delete those options yourself.
9. Troubleshooting
| Problem | Likely cause and fix |
|---|---|
| WordPress will not activate the plugin and says it requires a plugin to be installed and activated. | WooCommerce is missing or inactive. Install and activate WooCommerce first. If WooCommerce is deactivated later, the plugin silently stops working and its settings tab disappears. |
| No PDF is attached to the email. | The email is not selected under Attach to Emails, or it does not belong to an order (account emails never get an attachment), or the PDF could not be written to uploads/wphub-invoice (check that the folder is writable), or the PDF library failed (look in the PHP error log for a missing extension such as dom or mbstring). |
| The invoice shows no PAID badge and the full amount due. | The order's status is not under Mark as Paid. The default is only Processing, so an invoice attached to the Completed order email, or downloaded after the order is Completed, shows as unpaid. Add Completed to Mark as Paid. |
| Customers lose the Download Invoice button after the order is completed. | The button shows only for orders in a Generate Invoice or Mark as Paid status, or with a sequential invoice number. Add Completed to those lists, or turn on sequential numbers, or use the filter wphub_invoice_customer_can_download. |
| A download shows "Security check failed." | The link is older than its nonce (12 to 24 hours), or was copied to another browser session. Open the order page again and click the button. |
| A download shows "You are not allowed to do this." | The visitor is not the order's owner and has no valid order key, or the order is not eligible for customer downloads, or a staff member lacks the edit_shop_orders capability. |
| A download shows "Invoice could not be generated." | The PDF could not be written to the uploads folder, the PDF library failed, or WordPress's file system API needs FTP credentials on your host, so the plugin could not read the file back. Check folder permissions and the PHP error log. |
| The invoice number is the order ID, or numbers jumped. | Sequential Invoice Numbers is off (the order ID is used), or numbers were taken by earlier renders: a download by an administrator, or an email you attached for a status such as Cancelled, takes a number too. |
| Old orders got new invoice numbers after I switched sequential numbers on. | Orders without a stored number are numbered the next time their invoice is made. This cannot be undone for numbers already stored. Turn the option on before you issue the invoices you want to count. |
| The invoice number does not match the number I expect after lowering Starting Invoice Number. | Lowering the starting number never re-uses a number already issued; the next number continues from the last one issued. |
| The logo does not appear. | The Company Logo field is empty, the address is wrong, an external address returned something other than HTTP 200, took longer than 15 seconds or is over 5 MB, or the file is not an image. Use an address from your Media Library. |
| The PDF shows "?" instead of letters from another alphabet. | The bundled DejaVu Sans font covers Latin, Cyrillic and Greek. A script that font does not contain cannot be printed. A custom template can load another font through the PDF library's options (wphub_invoice_dompdf_options). |
| Preview Template shows old text. | The preview uses the saved settings. Click Save changes first. |
| Preview Template fails after I copied template-1.php into my theme. | The preview loads your copy of template-1.php too and gives it no order. Guard the code that uses $order, or test with a real order. |
| PDF files are reachable by address on my nginx server. | nginx does not read the .htaccess file the plugin writes. Add a deny rule for /wp-content/uploads/wphub-invoice/ to the server configuration. |
| Checkout feels slow when an order moves to Processing. | The PDF is made inside that request (on the status change, and again for the email attachment unless it was just made). A large logo that must be downloaded each time (over 700 KB when encoded, or an external address) adds to it. Use a smaller logo from your Media Library. |
| The VAT Number field does not appear. | VAT Number Field is off in the settings. For the Checkout block it appears in the Contact information step as "VAT Number (optional)". |
10. FAQ
When is an invoice created?
When an order reaches one of the statuses selected under Generate Invoice (Processing by default), and whenever the invoice is attached to an email or downloaded. It always reflects the current order.
Which emails can include the invoice?
Any WooCommerce email that belongs to an order. Choose them under Attach to Emails (New order, Processing order, Order details and Completed order are selected by default).
How do sequential invoice numbers work?
Enable Sequential Invoice Numbers and set the starting number. The first invoice gets exactly that number and each new invoice the next one. Raising the starting number later continues from the new number; lowering it never re-uses a number. When the option is off, the WooCommerce order ID is used. See Invoice numbers for the details and the warning about switching it on late.
How do I add my logo?
Upload it under Media → Library, copy its file URL and paste it into Company Logo. PNG, JPG and SVG work; the image is embedded in the PDF.
Can customers download their invoices?
Yes, from the order received page, the view order page and the My Account → Orders list, once the order has reached an invoice status. Guests use the secure link of their order. See Downloads.
Can I change the invoice layout?
Yes. Copy templates/template-1.php or templates/packing-slip.php from the plugin to yourtheme/invoice-for-wc/ and edit the copy. The placeholders are listed under Templates and placeholders.
Can I use US Letter paper?
Yes, with the wphub_invoice_paper_size filter, for example add_filter( 'wphub_invoice_paper_size', function () { return 'letter'; } );
Does it work with the Checkout block?
Yes. The VAT Number field is added to the Checkout block's Contact information step and to the classic checkout.
Where are the PDF files stored?
In wp-content/uploads/wphub-invoice/, with names that cannot be guessed. They are removed automatically after 7 days and re-created when needed, so deleting them is safe. See Where PDFs are stored for the nginx note.
Does it work with Advance Partial Shipment for WooCommerce?
Yes. With both plugins active, Advance Partial Shipment for WooCommerce uses this plugin's classes to make a packing slip and delivery note for each shipment, with your PDF Invoice settings.
Is a packing slip saved on the server?
No. It is made when a staff member clicks the icon and sent straight to the browser.
Does a refund change the invoice?
The invoice is made again each time it is attached or downloaded, so its totals follow what WooCommerce reports for the order, including refund rows. The amount due is not reduced: it is the order total, or zero when the status is a Mark as Paid status.
Does it contact an outside service?
No. PDFs are made on your server. The only outside request is the download of your logo when it cannot be read from your own site's files (normally an address on another site). See What is sent anywhere.
What happens when I delete the plugin?
The PDF folder, the cleanup event and the version option are removed. Settings, the invoice counter and the numbers on orders are kept on purpose. See Deactivation and uninstall.
Where can I get help?
Open a topic in the WordPress.org support forum, or email support@wpexpertshub.com.
11. Changelog
1.6 - 2026-09-29
- Fix - Checkout, order emails and other front-end requests could stop with a fatal error ("Call to undefined function wp_tempnam()") the first time a remote company logo was downloaded.
- Fix - The customer's name and company were printed twice on invoices and packing slips (once on their own and again inside the formatted address).
- Fix - Packing slips showed an empty "Ship to" block and "Shipping Method" row for orders without a shipping address or shipping method.
- Feature - Invoices now list item details (variation attributes, product add-ons and other item meta) under each line, like the packing slip.
- Dev - New template placeholders {billing_address_only} and {shipping_address_only} (the formatted address without the name and company lines).
- Tweak - Updated the bundled libraries: masterminds/html5 2.11.0 and sabberworm/php-css-parser 9.5.0. Dompdf 3.1.6, php-font-lib 1.0.2 and php-svg-lib 1.0.2 are the latest releases.
- Fix - Sequential numbering: the first invoice got the starting number + 1 instead of the configured starting number. Raising the starting number later now takes effect; lowering it never re-issues a used number.
- Security - Generated PDFs were stored as uploads/wphub-invoice/invoice-{number}.pdf, i.e. publicly downloadable by guessing the number. Files now carry an unguessable site-specific hash, the folder is protected (index.php + .htaccess) and old files are removed on update. PDFs are always delivered through the permission-checked download links.
- Security - Customer checkout data (company, name, phone, email, VAT number) was inserted into the PDF HTML unescaped, allowing markup (e.g. remote images) to be injected into the PDF renderer. All values are now escaped.
- Feature - VAT Number field now also works with the WooCommerce Checkout block (Checkout Fields API); the value appears on the invoice, packing slip and the admin order screen.
- Feature - "Invoice" download action in My Account > Orders.
- Fix - Customers could download an "invoice" for pending, failed or cancelled orders (and use up sequential numbers). Customer downloads are now limited to orders in a "Generate Invoice" / "Mark as Paid" status or that already have an invoice.
- Fix - Emails and downloads could deliver an outdated invoice (e.g. without the PAID label, or before an order edit) for up to 7 days; the PDF is now re-rendered for the current order when attached or downloaded.
- Fix - Fatal error when the email attachments filter ran for an email without an order (e.g. account emails) or was applied with three arguments.
- Fix - "Page X of Y" was never printed (embedded PHP is disabled in Dompdf); page numbers are now drawn natively on invoices and packing slips.
- Fix - Bold text in the templates fell back to a serif font; Cyrillic, Greek and other non-Latin customer names printed as "?" (DejaVu Sans fallback added).
- Fix - Fatal error on invoices containing a deleted product or a zero-quantity line.
- Fix - PHP warnings on fresh installs before the settings were saved; clearing all "Generate Invoice", "Mark as Paid" or "Attach to Emails" choices silently restored the defaults.
- Fix - Plugin did not load on multisite when WooCommerce was activated per site instead of network-wide.
- Fix - The settings screen also wrote a redundant autoloaded "wphub-invoice" option (removed on update).
- Fix - Advance Partial Shipment documents were streamed with an invalid file name; {shipping_phone} placeholder added.
- Tweak - Settings are sanitized per type (URL, email, number); the company logo is cached instead of being downloaded for every PDF.
- Tweak - Removed unused Dompdf files and the DejaVu Sans Mono / DejaVu Serif fonts (plugin size 11 MB to 7 MB). Bundled Dompdf is 3.1.6 (latest; includes the 2026 security fixes).
- Tweak - Tested with WordPress 7.1 and WooCommerce 11.1 (HPOS and Checkout block) on PHP 8.2 - 8.5. Minimum PHP lowered to 7.4 (the code and the bundled libraries support it).
- Dev - New filters: wphub_invoice_dompdf_options, wphub_invoice_placeholders, wphub_invoice_number, wphub_invoice_customer_can_download, wphub_invoice_page_number_text, wphub_invoice_paper_size, wphub_invoice_date_format, wphub_invoice_cleanup_days.
1.5 - 2026-09-16
- Fix - Placeholder replacement in invoice/packing-slip templates used order and customer data (billing address, company name, VAT number, etc.) as a preg_replace() replacement string; a value containing "$1" or similar could be interpreted as a regex backreference and silently corrupt the generated PDF. Switched to a plain, safe string replacement.
- Fix - Sequential invoice numbering read-then-wrote the "last invoice number" option non-atomically, so two orders changing status at nearly the same time (e.g. a bulk status update) could be assigned the same invoice number. The increment is now atomic.
- Fix - The admin-only invoice preview AJAX endpoint accepted a "template" value that was used to build a filesystem path without validation, and had no capability check beyond the nonce. Added a capability check and restricted the value to known template files.
- Fix - The CSS/JS cache-busting version constant was stuck at an older value than the plugin version across the last two releases, so updated assets could be served from a stale browser cache. It now matches the plugin version.
- Tweak - Removed a leftover unused debug variable from the invoice preview script.
1.4 - 19/08/2026
- Tweak - Text domain renamed to "pdf-invoice-for-woocommerce" to match the plugin slug and improve translation loading reliability.
- Tweak - Improved escaping, security, and WordPress coding standards for better Plugin Check compliance.
- Feature - Added daily background cleanup to automatically delete generated PDF invoices and packing slips older than 7 days from the uploads directory.
- Feature - Added Download Invoice and Download Packing Slip icons to the WooCommerce Orders list action column.
- Feature - Added a Download Invoice button to the front-end order details page for customers.
- Feature - Added Packing Slip PDF generation.
1.3 - 01/03/2026
- Fix - Packing slip PDF preview fix.
1.2 - 14/03/2024
- Fix - HPOS Compatibility.
1.1 - 25/03/2023
- Fix - Packing Slip added for advanced partial shipment plugin.
1.0 - 10/11/2022
- Initial Release.
12. Support
Open a topic in the WordPress.org support forum, or email support@wpexpertshub.com. Please include:
- your order ID, if you have one,
- the plugin version (1.6 or whichever you run), your WordPress, WooCommerce and PHP versions, and whether you use HPOS and the classic or block checkout,
- the order status, the email involved and the exact message shown, and
- what you already tried, and any error from your PHP error log.