Digital Product Passport for WooCommerce

Description

Digital Product Passport for WooCommerce adds the product data the EU Ecodesign for Sustainable Products Regulation (ESPR / EU 2024/1781) asks for, and publishes it three ways: as a passport people can open on the product page, as a QR code that links to it, and as JSON-LD a machine can read through the REST API.

Starting in 2025, the ESPR regulation requires many product categories sold in the EU to carry a Digital Product Passport covering sustainability, material composition, repairability, and end-of-life information. This plugin helps you meet those requirements today.

Key Features

  • Full ESPR / EU 2024/1781 Compliance — all mandatory data fields included
  • QR Code Generation — one code per product, generated inside the plugin as SVG. No external service, no API key, nothing leaves the site
  • GS1 Digital Link — QR codes follow the /01/{gtin}/21/{serial} standard
  • JSON-LD Structured Data — schema.org + W3ID DPP ontology (https://w3id.org/dpp/)
  • REST API — GET /wp-json/dpp/v1/passport/{id} with HTTP caching headers
  • Tab or Button Display — show the passport in a product tab or via a modal button
  • Completeness Score — live progress bar showing how complete each passport is
  • WooCommerce HPOS Ready — compatible with High Performance Order Storage
  • Cart & Checkout Blocks — full Block Editor compatibility declared
  • Fully Translatable — all strings use the digital-product-passport-for-woocommerce text domain

Passport Data Fields

Identity & Traceability
– GTIN / EAN-13 barcode
– Serial number & batch number
– Brand name & manufacturer name
– Manufacturer address
– Production country & production date

Sustainability & Environmental Impact
– Carbon footprint (kg CO₂eq) + measurement methodology
– Energy efficiency class (A+++ to G)
– Material composition (name, % weight, % recycled content)
– Recyclability percentage
– Recycling instructions / end-of-life info

Repairability & Durability
– Repairability index (0–10)
– Spare parts availability & availability period
– Durability / expected lifespan (years)
– Warranty duration (months)
– Repairability documentation available (yes/no)

Compliance
– Substances of concern (REACH / RoHS)
– Applicable regulation reference
– Compliance document URL

Free add-on

Nothing in this plugin is locked or limited. Everything described above works, for every
product, with no key and no account.

A separate add-on, also free, adds work that only larger catalogues need. It is a second
plugin you download from us; this one does not change when you install it, it simply
finds it:

  • Analytics Dashboard — track passport views per product with Chart.js charts
  • PDF Passport Export — print-optimized HTML passport with your branding
  • Bulk Passport Editor — edit all products in a spreadsheet-like grid
  • CSV & JSON Import/Export — migrate data or sync with external systems
  • Email Notifications — notify admins on passport updates + weekly digest reports
  • ACF Integration — sync Advanced Custom Fields with passport data
  • WPML & Polylang Support — multilingual passport data
  • Branded PDFs — custom logo, company name, and accent color in exports

Developer Hooks

Actions

  • dpp_passport_saved( $product_id ) — fired after passport data is saved
  • dpp_passport_viewed( $product_id ) — fired when a passport is viewed (used by Pro analytics)
  • dpp_passport_content_end( $product_id ) — fired at the end of passport content (used by Pro PDF button)
  • dpp_before_passport_display( $product_id ) — before passport HTML output
  • dpp_after_passport_display( $product_id ) — after passport HTML output

Filters

  • dpp_passport_data( $data, $product_id ) — modify passport data array before display
  • dpp_json_ld_output( $json_ld, $product_id ) — modify JSON-LD structured data
  • dpp_qr_code_url( $url, $product_id ) — modify QR code URL
  • dpp_rest_passport_response( $response, $product_id ) — modify REST API response

REST API

  • GET /wp-json/dpp/v1/passport/{id} — passport by product ID
  • GET /wp-json/dpp/v1/passport/gtin/{gtin} — passport by GTIN
  • GET /wp-json/dpp/v1/passports?search={term}&per_page={n} — search passports (max 100)

Support

Screenshots

Installation

  1. Upload the plugin to /wp-content/plugins/digital-product-passport-for-woocommerce/ or install via the WordPress plugin directory
  2. Activate through the Plugins menu
  3. Ensure WooCommerce 8.0+ is installed and active
  4. Navigate to any product edit page — a Digital Passport tab appears in the product data panel
  5. Fill in at least the manufacturer name and save

Requirements

  • WordPress 6.4 or higher
  • WooCommerce 8.0 or higher
  • PHP 8.0 or higher

FAQ

Is this plugin GDPR compliant?

All passport data stays in your WordPress database, and the plugin contacts no external service at all — QR codes are generated on your own server. No analytics, no telemetry, nothing sent anywhere. Whether your site as a whole is GDPR compliant depends on the rest of it, but this plugin adds no data flow you would have to declare.

Does it work with variable products?

Yes. Each product (including variations) can have its own complete passport. HPOS and legacy order storage are both supported.

How are QR codes generated?

Inside the plugin, with no network request. The encoder writes the symbol as SVG and stores it in your uploads folder so the work is done once per product. Addresses longer than 213 characters do not fit a version 10 symbol; there you get a visible note instead of an unreadable code.

Can I customize the display?

Yes. Use the display_position setting to choose between a modal button or an inline product tab. CSS custom properties (--dpp-primary, --dpp-radius, etc.) let you restyle without editing plugin files. The add-on adds PDF branding options.

What shortcodes are available?

  • [dpp_button] — renders the “View Passport” button
  • [dpp_passport] — renders the full passport inline
  • [dpp_qr_code] — renders the product’s QR code

All shortcodes accept a product_id attribute. When omitted, the current product is used.

Is the REST API public?

GET requests to /wp-json/dpp/v1/passport/{id} are public by default (configurable in settings). The API returns JSON-LD with Cache-Control and X-Robots-Tag: noindex headers.

Does it support HPOS?

Yes. The plugin explicitly declares custom-order-tables compatibility, so it works whether you use HPOS or the legacy posts table.

How does the completeness score work?

Ten key fields are checked (manufacturer name, GTIN, country, CO₂, energy class, materials, recyclability, repair index, warranty, compliance). Each filled field adds weight to the score displayed as a progress bar in the product panel.

Reviews

There are no reviews for this plugin.

Contributors & Developers

“Digital Product Passport for WooCommerce” is open source software. The following people have contributed to this plugin.

Contributors

Changelog

1.6.3

  • Fixed: the passport button did nothing, and several admin controls were dead. The prefix rename in 1.6.1 renamed the stylesheet and the scripts but not the class names that PHP prints into the markup. Twenty-two class names went out as dpp-* while the CSS and the JavaScript listened for dppass-*. The visible result: clicking “Digital Product Passport” on a product page opened nothing, and in the admin the buttons for adding and removing a material, opening the material library, saving and applying the company profile, the template buttons and the supplier edit and delete buttons had no handler. Everything is on one prefix now.
  • Fixed: three links in the admin led to a page that does not exist. The menu slugs are registered as dppass-settings and dppass-upgrade, but the “Settings” link WordPress shows next to the plugin, the add-on button in the settings sidebar and the link from the AOS Hub all pointed at dpp-settings and dpp-upgrade.
  • Fixed: decimal numbers on the passport always used English separators. The material shares, the carbon footprint, the repair index and the recyclability figure went through number_format(), which is not locale-aware. A German or French visitor saw 12.40 where 12,40 belongs. Now number_format_i18n(). The REST and JSON-LD output is untouched and stays machine-readable.
  • Neither of these was caught by Plugin Check, PHPCS or PHPStan: none of them looks at CSS class names, and a link to a missing admin page is valid PHP. They were found by taking screenshots of the plugin in a browser.

1.6.2

  • Fixed the same security issue on three more paths. 1.6.0 closed the REST route, and that was not enough: passport data leaves the site through five doors, and only one of them had been checked. The admin-ajax endpoint was the wide one, because it is guarded by a single site-wide nonce that every anonymous visitor of any passport page receives; with it, any product ID could be requested, drafts included. The three shortcodes accepted a product_id attribute without a check, and the free add-on served the same data as a printable page. All five now ask one shared guard (Utils\Access), so the next door cannot be forgotten. A nonce answers whether a request came from our page, never whether the visitor may see the data.

1.6.1

  • Changed: the CSS custom properties in the stylesheet still carried the short --dpp- prefix; the rename in 1.6.0 was anchored on quotes and dots, and a custom property is preceded by a hyphen, so no rule matched it. They are now --dppass-, in step with the free add-on, which reads two of them.
  • Fixed: the add-on described its custom CSS field as automatically scoped to a wrapper class that the plugin does not render. The CSS is inserted as written; the help text now says so and names the classes that actually exist.

1.6.0

  • Fixed a security issue: unpublished products could be read through the REST API. The passport route checked the post status, but it read its cache before that check and filled it after. So an editor opening a draft passport put the draft into the shared cache, and every anonymous request afterwards was answered from that cache without ever reaching the check. Measured on WordPress 7.1: a draft product was handed out in full, manufacturer and GTIN included, status 200. Authorisation now runs first, an unpublished passport is never written to the shared cache, and its response carries Cache-Control: private, no-store so no proxy in between can hand it on either.
  • Fixed: the QR symbol was printed without filtering. The markup is generated inside the plugin, but generate() may also return it from a file in wp-content/uploads — anything able to write there could have put arbitrary markup on the page. Both output paths now run through wp_kses() against an allowlist of the five elements a QR symbol consists of. Verified that the generated symbol passes through byte-identical and that script, event handlers, foreignObject, use and links are removed.
  • Changed: every option, meta key, hook and handle moved from the dpp_ prefix to dppass_. WordPress.org discards any prefix shorter than four characters. Existing installations are migrated automatically on the first page load after the update — options, the material library, the supplier list and all 29 passport fields per product. The migration runs on plugins_loaded, not on activation, because a plugin update does not activate anything. Nothing has to be re-entered.
  • Changed: the Pro add-on moves with it. The two share five meta keys, two options, three hooks and one nonce — please update both together, otherwise the add-on writes under the old names and the free plugin reads under the new ones.
  • Fixed: the two global helper functions were still declared as dpp_is_pro() and dpp_upgrade_url() while their function_exists() guards already asked for the new names — so the guards could never match.
  • Fixed: the support section linked to a repository that does not exist.
  • Added: the three public REST routes now document why they carry no authentication gate, and what limits what they hand out.
  • Fixed: deleting the plugin left data behind. Only three of the seven options were removed, so the material library, the supplier list, the company profile and the add-on settings stayed in the database. The QR cache directory survived too, because the routine that emptied it skipped dot files and then failed to remove a folder that was not empty — with a PHP warning.
  • Fixed: uninstalling now also removes data written under the old prefix, for anyone who deletes the plugin before the migration has had a chance to run.

1.5.6

  • QR codes are now generated inside the plugin. Until 1.5.5 every symbol was fetched from api.qrserver.com — an external request that was never declared, that sent the address of each passport to a third party, and that left the feature dead whenever the service did not answer (the fallback was a grey box, not a scannable code). The encoder now runs locally: no network call, no API key, no third party. Verified by decoding the generated symbols with an independent QR reader.
  • Fixed: the description called the plugin “the complete solution”; it now says what it does.
  • Fixed: the readme still described the add-on as a “Pro Version” that “unlocks” features. It has been free since 0.11, and nothing in this plugin is locked — the wording now matches the admin screens, which have said “free add-on” since 1.5.4.

1.5.5

  • Fixed: structured data. WooCommerce already prints a schema.org Product node on the product page; the plugin printed a second one with the passport fields, so search engines saw two products per page. The passport data (manufacturer, country of origin, GTIN, dpp: blocks) now merges into WooCommerce’s own Product node via the woocommerce_structured_data_product filter, and the dpp/un namespaces join the context. Measured: two Product nodes before, one after.

1.5.4

  • Removed: every artificial limit in the free plugin. Custom materials were capped at 10 and suppliers at a fixed number, product fields could be marked “Pro only”, and a 30-day notice plus several “Upgrade” links pointed at an add-on page — while the code to lift each limit sat in this very plugin. WordPress.org guideline 5/6 forbids exactly that, and the add-on being free does not change it. The free plugin is now complete for what it does; the add-on adds separate features (analytics, PDF, bulk import/export, compliance checker, e-mail, WPML).
  • Removed: a “Download PDF (Pro)” link shown to shop visitors that pointed into wp-admin.
  • Removed: the last Freemius branch in dpp_upgrade_url().
  • Added: the plugin registers with the AOS Hub and shows a footer line on its own admin screens only.

1.5.3

  • Changed: the text domain is now digital-product-passport-for-woocommerce, matching the
    plugin name. WordPress.org derives a plugin’s permalink from the plugin name, and language
    packs are looked up by text domain — with the two out of step, no translation would ever
    have loaded. Nothing users store changes: every option, hook and meta key keeps its dpp_
    prefix.
  • Removed: the runtime .po .mo compiler. It wrote into the plugin’s own directory, which is
    overwritten on every update and is not writable on many hosts — and it could never do
    anything in a released package anyway, because .po files are not shipped (WordPress.org
    builds translations itself). With it went the load_plugin_textdomain() call it required;
    WordPress has loaded language packs on its own since 4.6.
  • Fixed: prefixed the seven plugin constants (DPP_ DPPASS_). Plugin Check discards any
    prefix shorter than four characters, so all seven were reported as unprefixed globals.
  • Fixed: fgetcsv()/fputcsv() now pass $escape explicitly. PHP 8.4 deprecates the implicit
    default, and the plugin declares PHP 8.0, which includes 8.4.
  • Fixed: the QR cache protection files are written through WP_Filesystem. On installations
    that write over FTP the previous direct write silently did nothing — and that is exactly
    where the directory would have been left unprotected.

1.5.2

  • The “Powered by” badge is off by default. Until now it appeared on every passport unless the site owner bought the Pro add-on to switch it off. A credit on the visitor-facing page has to be opt-in — that is plugin guideline 11, and gating the off-switch behind a paid upgrade made it worse. The setting is still there under Settings General; it simply starts switched off.

1.5.1

  • Fixed: every request to /dpp/v1/passport/{id} ended in a fatal error on PHP 8. The route used bare ‘is_numeric’ as its validate_callback; WordPress calls that with three arguments and PHP-internal functions reject the surplus.
  • Security: the passport route served unpublished products. A draft was handed out in full — name, permalink, manufacturer — to anyone walking the IDs. Only published products are public now; users who may edit the product still get it, so previewing from the admin keeps working.
  • Fixed: six links in the plugin and the readme pointed at pages that do not exist (/docs/… and /plugins/…). They now point at the product page.
  • Added: “Requires Plugins” header, so WordPress blocks activation without WooCommerce and offers to install it.

1.5.0

  • Fix: Fatal error when re-activating the Pro plugin after deactivation (undefined constant DPP_PRO_VERSION at activation time — now uses DPP_PRO_VERSION_RAW)
  • Fix: Replaced direct PHP is_writable() with wp_is_writable() in the MO generator for full WP Filesystem API compliance
  • Fix: Added missing translators comment for the material library counter string
  • Fix: Supplier manager $_POST access now fully phpcs-annotated for WordPress.Security compliance
  • Fix: uninstall.php global variable names annotated for Plugin Check compliance
  • Fix: load_plugin_textdomain() phpcs-annotated (kept intentionally for MO auto-compilation fallback)
  • Code: All Plugin Check ERRORs and WARNINGs resolved to pass WP.org automated review

1.4.0

  • New: Bulk editor expanded with Brand, Serial No., CO₂ Methodology, and Durability columns
  • New: Supplier modal rebuilt as sticky flex layout (header/body/footer scroll independently)
  • New: Apply Template always overwrites existing values with a yellow-flash confirmation
  • New: QR code block spans full width in multi-column frontend layouts
  • Fix: Analytics chart now loads reliably via wp_add_inline_script (timing fix)
  • Fix: jsDelivr CDN fallback for Chart.js when local file is missing
  • Fix: Import/Export button dashicons aligned correctly on all screen sizes
  • Fix: Custom CSS & Branding Pro feature card added to upgrade page

1.2.0

  • New: Full ESPR / EU 2024/1781 field set — brand name, manufacturer address, CO₂ methodology, substances of concern, repairability index, spare parts, durability, compliance fields
  • New: GS1 Digital Link QR code URLs (/01/{gtin}/21/{serial})
  • New: QR codes switched from deprecated Google Charts API to qrserver.com (reliable SVG, locally cached)
  • New: Completeness score progress bar in product panel
  • New: Display position setting — modal button or inline product tab
  • New: Repairability section in frontend passport display
  • New: ESPR compliance footer badge in passport
  • New: dpp_passport_viewed and dpp_passport_content_end action hooks for Pro extensions
  • New: CSS custom properties for easy theme integration
  • New: ARIA focus trap and keyboard navigation in passport modal
  • New: Cart and Checkout Blocks compatibility declaration
  • New: Dismissible admin upgrade notice
  • Improved: Admin panel UI — section icons, locked Pro fields, panel-level CTA
  • Improved: Material rows now include a recycled content % column
  • Improved: Settings page redesigned with Upgrade page feature cards
  • Improved: REST API adds transient caching and rate limiting
  • Improved: JSON-LD output extended with all new ESPR fields and W3ID DPP ontology
  • Improved: Frontend CSS fully redesigned with mobile bottom-sheet animation
  • Requires: WordPress 6.4+, WooCommerce 8.0+, PHP 8.0+

1.0.0

  • Initial release
  • ESPR compliance fields
  • QR code generation
  • REST API
  • WooCommerce product tab integration