Polski for WooCommerce is a free WooCommerce plugin for Polish online shops. It issues numbered VAT invoices, and helps you organise GPSR product data, the Omnibus lowest-price history, GDPR consents, the right of withdrawal, VAT ID (NIP) handling, unit prices, hooks for KSeF processes, DSA reports and storefront modules.
The plugin is modular. You can enable only the features a given shop needs, for example GPSR, Omnibus, cart consents, withdrawals, unit prices, food data, a wishlist, a product comparison or AJAX search.
Polski helps you configure the technical shop processes related to the Polish and EU market. It is not legal advice and does not guarantee regulatory compliance. Your shop configuration, terms, products and obligations always have to be verified for your specific business.
Documentation and links
Why Polski for WooCommerce?
- One plugin, many modules - GPSR, Omnibus, GDPR, right of withdrawal, product data and storefront modules in one place.
- Built for Polish shops - features designed for WooCommerce stores selling in Poland or to Polish customers.
- Free and open source - the core product, checkout and storefront tools are available at no cost.
- Modern code - PHP 8.1+, a React admin panel, REST API and WP-CLI support.
- WooCommerce blocks support - compatible with both the classic and block-based cart and checkout.
- HPOS compatible - supports WooCommerce High-Performance Order Storage.
Key modules
- GPSR product fields - manufacturer, importer and EU responsible person, each with a postal address and the electronic contact art. 19 asks for, plus product identifiers, safety warnings and instructions, split into a responsibility group and a safety group, including CSV import and export.
- Omnibus price history - records and displays the lowest price from the last 30 days on discounted products.
- Product markings - per-product flags that print the statutory notice on the product page: medical device, and goods sold to adults only such as alcohol or tobacco.
- Deposit scheme (system kaucyjny) - adds the statutory deposit to beverages in covered packaging, untaxed and on its own checkout line.
- GDPR consents and checkboxes - configurable consents at checkout, registration and reviews, with a consent log.
- Right of withdrawal and returns - requests from the customer account, e-mail confirmations and a request log.
- VAT ID (NIP) and KSeF hooks - detection of orders with a VAT ID, a KSeF flag and hooks for invoicing integrations.
- EU VAT ID check (VIES) - confirms a customer's EU VAT number against the European Commission's register and records the consultation number on the order. With the NIP module on, checkout and My Account accept a VAT number from any EU country, entered with its country code.
- VAT margin scheme - the art. 120 annotation on invoices for second-hand goods, works of art, collectors' items and antiques.
- GTU markings - one of the thirteen JPK_V7 goods and services groups per product, printed against its own line on the invoice.
- DSA reports - a point of contact, an illegal-content report form and an admin panel.
- Shop health monitor - passive monitoring of frontend errors, checkout issues and sales anomalies.
- Security incident log - an internal log of incidents, outages, vulnerabilities and follow-up actions.
- Product environmental fields - a basis for green claims, certificates and expiry dates.
- Verified purchase badge - a badge on reviews from customers who bought the product.
- AI transparency (AI Act art. 50) - a clear "classified by AI" marker on return reasons processed by the optional AI classifier, filterable in the admin withdrawals list. The storefront disclosure of AI-generated product copy is a Polski PRO feature, since it labels copy from the Pro AI description generator.
Checkout, consents and returns
- Consent checkboxes (beta on the block checkout) - consents at order, registration and reviews, with the option to enable only selected fields. On the block checkout the labels show without links and the conditional boxes are not shown.
- Omnibus price history - automatic recording and display of the lowest price from 30 days.
- Right of withdrawal - withdrawal/return forms and requests from the customer account.
- Double e-mail confirmation (beta) - e-mail address confirmation during customer registration. An account created at checkout stays logged in for that session.
- Shop pages - link the terms, privacy policy and withdrawal content into WooCommerce notices.
- Dispute resolution - a notice about out-of-court complaint and redress options (consumer ombudsman, Trade Inspection) on the cart and checkout pages and as a shortcode.
- Consent log - logging of consents with date, context, IP address and content version.
Product data and labelling
- Unit prices - price per kg, litre, metre, piece or a custom unit.
- GTIN and barcode validation - automatic GS1 check-digit validation (GTIN-8, GTIN-12, GTIN-13, GTIN-14) in the product editor and during CSV import, alerting merchants to invalid barcodes without blocking saves.
- Delivery time - estimated delivery time on product pages and product lists.
- Tax information - the VAT rate notice and the small business exemption (Art. 113).
- Price display - configuration of how prices are presented in the shop.
- Food data - composition, nutrition values, allergens, origin, distributor and other fields for grocery shops.
Storefront modules
- Wishlist - save products for later.
- Product comparison - compare products side by side.
- Waitlist - back-in-stock notifications for simple products and for each variation of a variable product.
- Quick view - preview a product without opening the product page.
- Gallery zoom - enhanced product image zoom.
- Product video - add a video on the product page.
- Product slider - a carousel of products and collections.
- Infinite scroll - automatic loading of more products.
- Product tab manager - configure the tabs on the product page.
- AJAX filters - filter products without reloading the page.
- AJAX search - live product search in a search box placed with a shortcode or block, matching the title, the SKU, category names and the values of the global attributes you choose to index.
- Product badges - sale, new, featured and custom labels.
- Promotional popups - popup campaigns in the shop.
Admin and developer tools
- React panel - manage modules and settings.
- REST API - an API for settings, checkboxes, legal pages, withdrawals and search.
- WP-CLI - commands to manage selected features from the terminal.
- CSV import and export - bulk management of product data, including GPSR and GTIN validation warnings.
- Shortcodes - embed GPSR information, withdrawal forms, DSA reports and other elements.
- Database migrations - versioned and safe updates of data structures.
- Integration hooks - filters and actions for KSeF, invoicing and integrations with other plugins.
- Audit scope - DPA, DSA, KSeF readiness, environmental-claim control, verified reviews and security incidents.
You may also like these plugins
More free WooCommerce plugins from WPPoland:
- Plogins Tiers - quantity and volume pricing tiers with a server-rendered price table.
- Plogins Waitlist - back-in-stock waitlist that emails shoppers the moment a product returns.
- Sieve - Search & Filter - fast AJAX product search and filtering for WooCommerce, with no jQuery.
Browse the full catalogue at
plogins.com/ .
Reporting a security issue: email hello@wppoland.com, and under our
coordinated disclosure policy we confirm within two business days, assess within five, and patch a critical issue within seven days of confirming it.
1.42.0
- Fixed: On block themes, Product Collection grids outside the shop archive (a homepage grid, related products, cart cross-sells) showed no unit price, no Omnibus 30-day lowest price, no delivery time and no brand. WooCommerce fires the classic loop hooks there only for the shop grid, so these now follow the price block of every other grid, once per card.
- Fixed: With "Omnibus: Cart" on, a block-theme shop page and the product page price showed the Omnibus notice twice. Cart cross-sells now follow the "product lists" Omnibus setting like every other grid; cart items keep the "Cart" setting.
- Fixed: Block themes: product badges and a featured video set to "before summary" landed above the breadcrumbs on the product page. Both now sit with the image gallery. Badges also show on Product Collection cards outside the shop archive, and replace the Product Sale Badge block where Polski shows its own sale badge.
- Fixed: Wishlist, Compare and Quick View buttons were missing from Product Collection grids outside the shop archive, and their scripts did not load there. Each card gets one button per feature.
- Fixed: Social login buttons now show above the Checkout block, which fires none of the classic checkout hooks.
- Fixed: Product authors now show on Product Collection cards outside the shop archive.
1.41.8
- Fixed: The iworks Omnibus import read the raw WooCommerce sale field, so a sale that was only scheduled counted as a price the product sold at, and its sale rows were never closed. It now takes the selling price, imports only what lies before Polski's own history for a product, and on upgrade removes the rows 1.41.7 imported wrongly. On some products these made the lowest price equal the running sale price.
1.41.7
- Fixed: The Omnibus lowest price now shows on single product pages built with an Elementor Pro template, which replaces the WooCommerce summary hook the notice was attached to. It is placed under the price widget, once, also when the template carries the Polski Omnibus widget.
- Added: Import of the price history kept by the Omnibus plugin by iworks (v2 meta and v3 price log), run once on upgrade and on demand with
wp polski import-iworks, so a shop switching to Polski keeps its 30-day window.
1.41.6
- Added: GS1 modulo-10 check-digit validation for GTIN-8, GTIN-12, GTIN-13, and GTIN-14 barcodes in product editor and CSV importer, warning merchants of invalid barcodes without blocking saves.
1.41.5
- Added: Borlabs-style Content Blocker for third-party media embeds (YouTube, Vimeo, Google Maps, OpenStreetMap, SoundCloud) with placeholder opt-in and dynamic runtime unblocking when marketing consent is granted.
- Added: Swiss UID (CHE) VAT number format validation and SOAP integration for cross-border DACH trade.
- Fixed: Omnibus archive price cache key alignment between windowed lookup and cache warmer invalidation.
1.41.4
- Added: native administrator email notification ("New withdrawal declaration (Admin)") registered in WooCommerce > Settings > Emails, alerting the shop whenever a customer files a return.
- Fixed: guest withdrawal lookup form now auto-detects pages with the [polski_withdrawal_lookup] shortcode even when unselected in settings, wraps magic link emails in WooCommerce HTML styling with proper From headers, and provides feedback if an order is not eligible.
- Fixed: guest order lookup now supports sequential order number plugins and trims hash/whitespace prefixes.
1.41.3
- Security (low): the [polski_withdrawal_form order_id=N] shortcode showed any order's number, date and withdrawal eligibility to whoever opened the page, so anyone able to place a shortcode (a contributor, or a comment or form that renders shortcodes) could read other customers' orders by number. It now shows the order only to its owner or a shop manager, and everyone else gets the same answer as for an order that does not exist. The product shortcodes (unit price, GPSR, Omnibus price and the rest) also rendered draft, private and password-protected products when given product=N; they now render only what the visitor could already see.
1.41.2
- Fixed: the withdrawal form lost its "Step 1. Choose the items to withdraw from" heading as soon as the settings were saved once. The settings default said "Order items", so saving wrote that over the template's heading; both defaults now match the form.
- Fixed: the KSeF badge on the orders list stayed amber for an invoice KSeF had accepted. Polski PRO records an accepted invoice as "accepted", and the badge only turned green for "sent"; it is green for both now.
1.41.1
- Fixed: the Elementor widgets (unit price, Omnibus price, tax, delivery time, GPSR, food, AJAX search, filters, product slider) never appeared in Elementor. The class that registers them was defined but had not been booted since April 2026. They now appear in the "Polski for WooCommerce" category and render through the same code as their shortcodes.
- Fixed: with Google for WooCommerce active, Polski now adds the product's GTIN (when Google for WooCommerce has none), manufacturer as brand, and unit pricing to the Google product. This integration had also not been booted since April 2026, and when switched back on it would have stopped every product sync with a type error; it is corrected to the filter's real arguments and to the attribute names Google expects.
- Changed: the CartFlows compatibility class is booted again and only acts when the Legal checkboxes module is on.
- Fixed: the daily maintenance job (Omnibus history pruning, double opt-in cleanup, scheduled review requests) was scheduled only on activation, so an update that copied files without reactivating left it unscheduled. It is now restored on every load when missing.
- Removed: an empty Dynamic Pricing compatibility class whose two callbacks did nothing.
1.41.0
- Security (high): a logged-out visitor could open and submit the withdrawal form for any guest order, because a guest order's customer id and a logged-out visitor's user id are both 0. Guests now go through the existing order lookup and email link only.
- Security (medium): Quick View, compare and wishlist returned or stored draft, private and password-protected products, including disabled variations. They now accept only products a visitor could open themselves.
- Security (low): product Q&A accepted questions and answers on any post id. Questions now go only to published products, and an answer must reply to an approved question on the same product.
- Added: the waitlist works on variable products. The form appears when an out-of-stock variation is selected, and the restock email goes out when that variation is back in stock, however the stock changed. No longer beta.
- Added: the price history chart shows the selected variation's history on variable products. No longer beta.
- Added: with the VIES module on, checkout and My Account accept an EU VAT number from another country (for example DE811569869). A plain 10-digit NIP keeps its checksum and GUS lookup. No longer beta.
1.40.1
- Security (medium): three read-only withdrawal abilities (eligibility, remaining items, deadline) let any logged-in customer read them for another customer's order by guessing its id. They now require the order's own customer or a shop manager.
1.40.0
Every one of the 84 modules was switched on, configured and used in a live shop, then switched off again. This release fixes what that found, and marks as beta what does not fully work yet.
- Fixed: the unit price dropped the base quantity, so a price per 1000 ml or per 100 g was labelled per ml or per g. It now shows the base ("15,00 zl / 100 g").
- Fixed: the lowest-price (Omnibus) notice quoted the sale price itself for an ordinary unscheduled sale, missed same-day price changes, ignored the price still in force when the 30-day window opens, and never showed on variable products. A scheduled sale is no longer recorded before it starts. The notice now follows the WooCommerce tax display.
- Fixed: the cart threshold discount added VAT on top of the discount, so 5% came out as 6.15% in shops that show prices including tax.
- Fixed: the VAT notice said "incl. 23% VAT" next to net prices. It now shows only when prices are displayed including tax.
- Fixed: the VAT margin scheme invoice still printed per-line VAT, and a merchant warning about mixed orders appeared on the customer's invoice.
- Fixed: saving the billing address in My Account always failed with "NIP not valid" while the NIP module was on. The GUS autofill on the block checkout filled a hidden billing form.
- Fixed: switching the withdrawal module off hid orders that already had a withdrawal status from Orders and from My Account.
- Fixed: saving the withdrawal settings page switched the digital-content consent on at checkout although it was off, and physical-only orders showed a consent line.
- Fixed: variable products were added to the cart twice by AJAX add to cart, and a failed add showed "Added to cart".
- Fixed: custom checkout field values from block orders disappeared from the order screen and emails; select fields showed the option key instead of its label.
- Fixed: switching a module off could silently delete its product data on the next product save (medical device and 18+ flags among others).
- Fixed: product JSON-LD replaced WooCommerce's sale and list price with the Omnibus figure; it is now added next to them. No invalid nutrition on Product, no QAPage for several questions, no second Product node for expert reviews, and the manufacturer follows its setting.
- Fixed: CSV import did not map Polski columns back from an export.
- Fixed: AJAX filters, compare, infinite scroll, gallery zoom, product slider, social proof, featured video, badges and tabs misbehaved on Storefront (duplicated tables and pagination, lost lightbox, duplicate products).
- Fixed: security incidents, store health, site audit, CRA, DPA, SBOM, data layer and exports: wrong dates, false audit results, reports reachable with the module off.
- Changed: the dispute resolution texts no longer send consumers to the EU ODR platform, which closed on 20 July 2025.
- Removed: two switches nothing read, the gross/net display mode (WooCommerce owns it) and the Checkout Toolkit integration.
- Beta: VIES (non-Polish VAT numbers cannot be entered at checkout), cookie consent (only Polski's own tags wait for consent), double opt-in (accounts created at checkout stay logged in), custom checkout fields and legal checkboxes on the block checkout, waitlist on variable products, the price history chart on variable products, and the AI bridge. Each module card now says exactly what does not work yet.
- Corrected: module descriptions that promised more than the code does (legal page drafts are empty, the complaint template has no REST route, the business block has no REGON, live search needs its shortcode or block).
1.39.0
- Fixed: nine settings on the modules screen changed nothing when edited. The withdrawal form drew its own headings and ignored the order items heading, the product and quantity column labels and the exemption notice; it never printed the legal notice at all. The two order notes the module writes ignored their wording settings. Editing any of them looked like it worked, saved, and did nothing.
- Fixed: the dashboard status cards ignored all thirteen wording settings offered for them, and the VAT and double opt-in cards were missing entirely while the page still worked out what they would have said. Both cards are back, and every card now uses the wording from the screen.
- Fixed: the DSA report form ignored its success message setting.
- Removed: the withdrawal "Price column" setting named a column the form does not have.
- Note for anyone who edited those fields before this release: your wording was stored the whole time and takes effect now, so check it says what you want.
1.38.4
- Fixed: the lowest-price notice could quote a figure in a currency the shop no longer uses. The price history records the currency of every snapshot, but the lookups ignored that column, so a shop that switched currency, or a multi-currency plugin that switches it per request, would show "lowest price in the last 30 days" in one currency beside a selling price in another. Two amounts in different currencies do not compare, and that notice is a comparison, so the window, the archive lookup and the price history chart now all read one currency: the shop's current one. A shop that switches starts a fresh 30-day window.
1.38.3
- Fixed: a single timed-out call to an external register was reported to the customer as a definite answer. A GUS lookup that never got through said "no data found for that VAT ID", and a VIES check that timed out read as a failed check. Calls that are safe to repeat, the GUS lookup, the VIES check, the legal-page fetch and the social-login profile read, are now retried once after a timeout or a 5xx. Writes are not retried, so nothing is posted, charged or consumed twice.
1.38.2
- Fixed: the NIP field accepted anything that was not a number. Sanitising ran before validation and stripped every non-digit, so "abcdef" reached the check as an empty string, and an empty string means "left blank". The customer saw their text accepted, the shop received no NIP. Letters, a too-short number and a wrong checksum are now all rejected, on the checkout and in My Account. Reported by strid3rr on the support forum.
- Fixed: the NIP field was registered as an address field, so WooCommerce rendered one copy in the billing address and a second in the shipping address, and nothing compared the two numbers. A VAT ID belongs to the buyer rather than to a place, so it is now a contact field and appears once. Values saved under the old address key are moved on first save.
- Fixed: with both the NIP and the B2B checkout modules on, the NIP field was registered by nobody. The NIP module stepped aside "because B2B handles it" and B2B never had a NIP field, so it vanished from block checkout and from My Account.
- Fixed: a NIP saved in My Account had to be typed again at checkout, and the address summary could show a different number than the edit form. The two screens wrote to two different customer meta keys and nothing bridged them. Every screen now reads and writes the same number.
- Fixed: entering a NIP on the block checkout did not fill in the company details. The lookup wrote to the DOM ids the shortcode checkout uses, and block checkout renders its inputs from a data store, so anything written that way was discarded on the next render. It now goes through the store, which is what the block fields read.
- Fixed: My Account > Addresses saved an invalid NIP without a word. WooCommerce checks a custom field's required flag and its type there and nothing else, so the checksum is now checked on that form too.
1.38.1
- Fixed: a product found by the AJAX search dropdown could be missing from the full results page. The dropdown and the results page share the same extra matching (SKU, GTIN, manufacturer, category, ingredients), but on the results page the extra product IDs were being attached to the wrong part of the SQL WHERE clause, the one WordPress adds for logged-out visitors to exclude password-protected posts. For a logged-out shopper, which is nearly every shopper, the results page therefore searched titles and descriptions only, and a password-protected product matching the term would have been listed. Reported by strid3rr on the support forum.
- Fixed: "View all results" carried the phrase from the response that was on screen, not the phrase in the search box. Typing on and clicking it immediately led to the results for the previous keystroke. The link, and Enter on it, now always use the current contents of the box.
- Fixed: a keystroke landing while a request was in flight could leave the dropdown a phrase behind the box; the search now re-runs for the current value.
- Fixed: the AJAX dropdown listed products protected with a password, with their name, price and link. The storefront results page has always hidden them; the dropdown queried products without any notion of a password. Found while reproducing the report above.
1.38.0
- Added: Product markings module. Two per-product flags that print the statutory notice on the product page: medical device (Regulation 2017/745 and the Medical Devices Act) and goods sold to adults only, such as alcohol or tobacco. Both notices are editable in the module settings.
- Added: five new privacy policy checks. The page is now also read for things that must NOT be there: the repealed 1997 data protection act, the invalidated Privacy Shield, GIODO instead of PUODO, unfilled template placeholders, and sentences left over from a chat with an AI assistant.
- Added: four new terms and conditions checks: telephone number (required since the Omnibus implementation), the rights of a sole trader buying outside their profession, unfilled placeholders, and AI leftovers.
- Added: both legal pages are now checked for links to somebody else's terms or privacy policy, which is what a copied template looks like.
- Added: two site audit checks. Gravatar (avatars send a hash of the commenter email to a US service, and it is on by default even when comments are closed), and a published accessibility statement, which the European Accessibility Act has required of online shops since 28 June 2025.
- Fixed: an empty legal page scored 23% instead of zero, because it trivially passed every "must not contain" check. Nothing to check now counts as not checked.
1.37.11
- Fixed: from WooCommerce 8.6 on, three modules stopped registering their classic checkout fields, on the belief that WooCommerce renders additional checkout fields on the shortcode checkout as well. It does not: core renders those on the block checkout, the order confirmation and My Account only. On a shop using the classic (shortcode) checkout this silently removed the B2B company toggle, REGON and IBAN fields; every custom checkout field defined in the Custom Checkout Fields module, including its validation and its save; and the digital-content consent checkbox required before performance begins (Art. 16(m)). All three register both paths again.
- Fixed: the duplicate-field guard is now one generic filter (
AddressFormFieldDedupe) covering NIP, REGON and IBAN on My Account > Addresses, instead of the NIP-only one added in 1.37.10.
1.37.10
- Fixed: with the "NIP - Verification and Autocomplete" module on, My Account > Addresses > Billing address showed two NIP fields: the plugin's own one above "Country / region" and a second, inert one below the email field. The second came from the WooCommerce additional-checkout-fields copy of the field, which WooCommerce also renders on that form. Both registrations are still needed (the classic checkout renders only the first, the block checkout only the second), so the duplicate is now dropped from the edit-address form only. Reported by strid3rr on the support forum.
- Fixed: the lowest-price notice in the cart, mini-cart and order review was prefixed with the technical label "Omnibus:", e.g. "Omnibus: Lowest price in the last 30 days: 20.00". WooCommerce renders cart item data as "label: value" and the whole sentence was being passed as the value. The notice is now split, so the customer sees only "Lowest price in the last 30 days: 20.00". Reported by strid3rr on the support forum.
- Fixed (PRO): the PRO NIP validator registered a second
billing_nip billing field on the same filter and priority as the FREE module, overwriting it. The field lost the attribute the GUS autocomplete binds to, and it appeared even with the NIP module switched off. PRO now leaves the checkout field to the FREE module and only adds the editable NIP row on the admin order screen, gated on that module.
1.37.9
- Fixed: turning the "Right of withdrawal (14 days)" module off left most of it running. The withdrawal call to action kept appearing in customer order emails, the "Polski withdrawal" box kept rendering on the order edit screen, the plugin's own withdrawal order statuses stayed registered, and the per-product withdrawal exemption field stayed on the product editor. Eighteen services belonging to the module now refuse to register their hooks while it is off. This matters to any shop using the WooCommerce 11.1 native Order Withdrawal feature, which until now ran alongside ours. Reported by strid3rr on the support forum.
- Note: the privacy exporter and eraser for withdrawal data deliberately keep running with the module off, so a GDPR request still reaches data the shop already collected.
Only the most recent releases are listed here. The full history is on the plugin's changelog
page, plogins.com/polski/changelog/,
and in changelog.txt inside the plugin folder. WordPress.org silently truncates
a changelog over 5000 words, which is why this one is kept short on purpose.