EU Directive 2023/2673, Article 11a

WooCommerce withdrawal button, built for the shops that have to prove it

Since 19 June 2026 an EU shop has had to offer an electronic withdrawal function. This plugin adds one, records the declaration, and sends the customer a timestamped receipt they can keep.

Free, GPL, on WordPress.org. FDesign sells the setup, the legal wording review and the ongoing care, not the plugin.

What the law asks for

Four things the withdrawal function has to do

Article 11a is short, and each sentence of it turns into a requirement a shop can either meet or not. These are the four that decide whether what you have counts.

01

Exist, and be findable

An electronic function, reachable from the shop, that a consumer can use to declare withdrawal. A contact form or an email address is not one.

02

Work without an account

Most orders are placed as a guest. If the function needs a login, the customers who most need it cannot reach it.

03

Confirm receipt on a durable medium

The shop has to acknowledge the declaration, without undue delay, in a form the consumer can keep. An on-screen message disappears. An email does not.

04

Be no harder than buying

The principle behind the whole article. If checkout takes three clicks, withdrawing should not take a phone call.

The stake

The penalty is not a fine. It is time.

National authorities set their own fines, and those vary. The consequence that bites first is written into the directive itself and applies everywhere in the EU.

Fourteen days becomes twelve months EU wide

Where the required information or function is not provided, the cooling-off period extends from fourteen days to twelve months and fourteen days. An order from last autumn stays withdrawable.

The burden of proof sits with the shop

If a customer says they withdrew and the shop has no record, the shop is the one without evidence. A timestamped acknowledgement is what closes that argument.

It is already in force

19 June 2026 has passed. A shop selling to EU consumers without a withdrawal function is not preparing for a deadline, it is already exposed to the extended period.

Article 6 asks you to say it exists

The pre-contractual information has to disclose the function. Installing the plugin is half the job; the terms and returns pages are the other half.

Section by section

What the plugin actually does, start to finish

Every part of this has been run against a live WooCommerce shop, not only read. The numbers further down say how.

Three ways in

The customer reaches it however they arrive

A single form serves three different people, because a withdrawal function that only works for one of them is not a withdrawal function.

  • Signed in. The link sits on the order in My Account, next to View and Order again, and only on orders still within the window with something left to withdraw.
  • Guest. Order number plus the email used for the order. A one time link goes to the address on the order, never to the address typed in, so guessing an order number buys nothing.
  • Fallback. A free text declaration for the customer whose link expired or who no longer has that inbox. Recorded unverified and flagged for the shop to match by hand.

Two steps

Declaring is a deliberate act, not a stray click

The customer sees exactly what they are declaring, then confirms as a separate action. The two step shape is what makes the record defensible: nobody withdrew by accident.

  • Step one lists the order and lets the customer choose.
  • Step two restates the choice and asks for confirmation.
  • A double click cannot file two declarations. The session is claimed before anything is written, so one submission wins and the other lands on the same confirmation.

Partial withdrawal

Per item, per quantity, and aware of refunds

Consumers withdraw from part of an order more often than from all of it. The plugin counts what is genuinely left rather than what was originally bought.

  • Choose which lines, and how many of each.
  • Refunds you have already issued reduce what is left. Withdraw one of three, refund another through WooCommerce, and the customer is offered one, not two.
  • Accepting a partial withdrawal puts the order back to where it was rather than stamping the whole thing withdrawn, so every integration reading the status still reads something true.

The acknowledgement

The artefact the directive actually asks for

An email the customer can keep, carrying the exact time of receipt and an integrity code derived from the declaration itself. It is the evidence, and it is worded to be evidence.

  • Says received, and never accepted. Those are different legal moments and conflating them costs the shop its position.
  • Lists what was declared, so a customer with several requests on one order can tell them apart.
  • Sent to the address on the order.

The queue

Where the shop does the work

One screen under WooCommerce, with the pending count in the menu so nothing sits unnoticed.

  • Filter by status, search by reference, customer or order.
  • Every request carries a full audit trail: who did what, and when.
  • Accept or reject, with a note that goes to the customer. Optional when accepting. When rejecting it is worth the thirty seconds, because a refusal with no reason is one the customer cannot answer and you cannot defend later.
  • The declared items are written onto the order as an order note, so the merchant sees the request where the money is.

Five emails

The customer is told what happened, in your words

Access link, acknowledgement, shop notification, accepted, rejected. All five appear under WooCommerce, Settings, Emails alongside the rest.

  • Every one carries WooCommerce's standard Additional content box.
  • The three the customer reads also let you replace the closing paragraph, the one that says what happens next in your shop. Clear the field and the built in wording comes back, in the customer's language.
  • The default acceptance text promises that you will write separately about returning the goods and the refund. True of most shops, not of all. If your process differs, say what yours is.

Deletion

Records can go, but never without a trace

Spam, duplicates and the test entries every shop leaves behind while setting up should be removable. A genuine declaration is a different thing: it is the record that a customer exercised a legal right on a date, and that is your evidence as much as theirs.

  • Before the row goes, the whole record is written to the WooCommerce log, with the reason you typed and who removed it.
  • The order gets a note naming the reference, the time it was received and the user.
  • Neither can be deleted from inside the plugin. There is deliberately no bulk delete.

Privacy

GDPR tools, with erasure off by default

Declarations appear in Tools, Export Personal Data and Tools, Erase Personal Data, and the plugin contributes suggested wording to your privacy policy draft.

  • Erasure is off by default and says so in the report rather than failing quietly. A withdrawal declaration is the record of a legal act and your evidence that you honoured it, which is a recognised ground for keeping it. WooCommerce treats orders the same way.
  • Switch it on and the name, address and free text are replaced while the declaration itself survives, so you keep the proof without keeping the person.
  • Visitor IP addresses are used for rate limiting and are never stored against a declaration.

Engineering

The parts nobody sees until they go wrong

A withdrawal function fails quietly. These are the failures that would not announce themselves, and what was done about each.

Access links are one way hashes 30 min

Thirty two bytes from a cryptographic source, stored only as a SHA-256 hash, single purpose, and expired by the query rather than by a check afterwards. A leaked database backup hands over nothing usable.

The token does not sit in the address bar

Opening the emailed link exchanges it for an HttpOnly cookie scoped to the withdrawal page, then redirects to a clean URL. Without that, the credential reaches every third party asset the page loads, in the Referer header.

The page excludes itself from caching

Using the signal WP Rocket, LiteSpeed Cache, W3 Total Cache, WP Super Cache and WP Fastest Cache all respect, and on every translation of the page, not only the original. A cached withdrawal page hands one customer another customer's session.

The lookup cannot be used to fish

Rate limited per address and per visitor, with a honeypot and a signed timing check. A wrong order number and a wrong email produce exactly the same message as a correct pair.

Rate limiting survives a busy site

Kept in the plugin's own table, not in a transient. A transient can be evicted under memory pressure, so a limiter built on one fails open exactly when the site is busiest. WooCommerce reached the same conclusion for its own limiter.

Nothing is stored that would be wrong later

No monetary amount, because what you owe depends on delivery costs, apportioned discounts and the condition of the goods. A figure stored at declaration time would look authoritative and be wrong.

0

errors in
Plugin Check

0

PHPStan level 5
findings

80

attack checks,
all passing

8

languages
shipped

3

ways into
the form

Alternatives

How it compares, checked in their source

Six comparable plugins exist on WordPress.org and all six are free. The rows below were verified by reading their code on 29 August 2026, not their marketing.

  FDesign Vendidero WebToffee Four others
Active installsnew5,0001,00050 to 500
Guest path without an accountyesyesyesvaries
Access links stored hashedyesyesnovaries
Rate limiting on the lookupyesnonono
Refunds reduce what is leftyesnonono
Page cache exclusionyesnonono
Reason attached to a rejectionyesyesyes, requiredno
Shop can reword the emailsclosing paragraph toostandard blocknotwo of four
Greek interfacecompletenopartialno

Install counts are WordPress.org's own figures, read on 29 August 2026. Vendidero is the most widely used and the most complete of the six; where it does something better, this plugin borrowed it. The rejection reason came from WebToffee, which will not let an administrator reject without typing one.

Security

Tested against what is actually being exploited

Reviewing a plugin against a mental list of vulnerabilities means reviewing it against the ones you happen to remember. The list came from the National Vulnerability Database instead.

2,257 WordPress plugin CVEs

Every one published between 1 May and 29 August 2026, sorted twice: by how often each class appears, and by how badly it ends. The two orders are almost opposite.

151 of the 156 critical ones need no login

Cross site scripting leads the count with 545 records and appears zero times among the critical. Unrestricted file upload is eighth by count and first by severity.

A guest link is a recovery mechanism

Weak password recovery sits nineteenth by count and seventh among the critical. An emailed access link is one of those in everything but name, so every classic failure of one was tested for.

What that produced

Two attack suites, eighty checks between them, run against a live WooCommerce shop rather than a mock. The emailed link is built from the site's own address and never from a request header, so it cannot be pointed at somebody else's server. Expiry is enforced inside the query. Fifty issued tokens are fifty distinct values, and the raw token appears nowhere in the database.

Verified against NIST National Vulnerability Database, WordPress Plugin Check, PHP_CodeSniffer with the WordPress standards, and PHPStan at level 5. Figures read on 29 August 2026.

Languages

Eight locales, and a deliberate rule about the legal text

What ships

Greek, German, French, Italian, Spanish, Polish, Romanian, Czech

The interface is translated in all eight. Greek is complete at 299 of 299 strings, including every admin screen, because a Greek merchant should not meet a Greek shop front and an English dashboard.

  • Statutory link labels are the researched ones, not machine translations: Vertrag widerrufen for German, Odstąp od umowy for Polish, Recesso dal contratto for Italian.
  • Customer facing legal wording stays English outside Greek. Wrong legal phrasing in a language nobody on the team can check is worse than English, and a shop can translate it once with its own lawyer.
  • Works with Loco Translate, WPML and Polylang. On a multilingual shop the withdrawal link resolves to the visitor's own language, and the cache exclusion covers every translation of the page.

The plugin

Free, GPL, and the same code described above

There is no held back version. What is on WordPress.org is what this page describes, and FDesign does not sell a licence for it.

What FDesign sells is the part a plugin cannot do for you: choosing the withdrawal window and the status that starts it, wiring the disclosure into your terms and returns pages, checking the wording against how your shop actually handles returns, and being there when a customer's declaration arrives on a Friday evening.

Download

FDesign Withdrawal Button

Install from the WordPress plugins screen, or download and upload the zip.

Licence GPL-2.0 or later Requires WordPress 6.9, PHP 7.4 WooCommerce 8.0 and later, HPOS ready Sends data to no external server
Get it on WordPress.org

Services

Installed, worded and looked after

Two families. The one off gets the function live and defensible. The monthly one keeps it that way as WooCommerce, WordPress and your own catalogue change.

One off

Compliance setup

One shop

from €140

A single WooCommerce shop, live and tested.

  • Install, configure the window and the status that starts it
  • Withdrawal page created, linked and excluded from your cache
  • Guest path tested end to end on your own staging
  • Email wording adapted to how your shop handles returns
  • Exclusions reviewed against Article 16
Ask about setup

Larger or unusual

on assessment

Multisite, marketplaces, or a heavily customised checkout.

  • Multisite and multi vendor setups
  • Non standard order numbers or external fulfilment
  • Custom templates matched to your theme
  • Migration from another withdrawal plugin
Ask for an assessment

Monthly

Compliance care

One shop

from €29 / month

The function keeps working while everything around it changes.

  • Plugin, WooCommerce and WordPress updates applied and checked
  • The guest path retested after every WooCommerce major release
  • Queue watched so nothing sits unanswered
  • Email deliverability monitored
Ask about care

Larger or unusual

on assessment

Volume, or an obligation that reaches past WooCommerce.

  • More than five shops
  • Agreed response times
  • Reporting your own compliance team can file
Ask for an assessment

Prices exclude VAT. Greek VAT of 24% is added where it applies.
The plugin itself is free and stays free. These prices are for the work around it.

Who built it

FDesign

FD

FDesign

WordPress and WooCommerce, Athens, since 2015

FDesign builds and looks after WooCommerce shops, and writes the plugins those shops turn out to need. This one started as a requirement on a client's own shop and became the thing that was missing from every alternative: a withdrawal function that treats the declaration as evidence rather than as a form submission.

WooCommerce HPOS ready GPL-2.0 or later No external calls Eight languages

Questions

Before you install it

Is the plugin really free, with nothing held back?

Yes. It is GPL and published on WordPress.org, and there is no premium build with the useful parts in it. FDesign earns from the setup, the wording review and the monthly care, which are the parts a plugin cannot do for you. That also keeps the incentives honest: nothing gets crippled to sell an upgrade.

Does installing it make my shop compliant?

No, and any plugin that claims otherwise is overselling. The plugin provides the electronic function and the timestamped acknowledgement. What it cannot do is write your terms and returns pages, disclose the function as Article 6 requires, or decide how your team handles a return when it arrives. Confirm both against the law as transposed in your country.

Can I label the link Cancel or Returns instead?

Not safely. The directive points to unambiguous wording along the lines of withdraw from contract. A label a customer does not recognise as the withdrawal function is unlikely to discharge the obligation, and Cancel reads as abandoning an order that has not shipped yet.

Does it issue refunds?

No, deliberately. Refunds stay in your normal WooCommerce flow, through your existing gateway, decided by you after you have inspected the returned goods. The plugin also stores no monetary amount, because what a shop owes depends on delivery costs, apportioned discounts and the condition of the goods. A figure stored at declaration time would look authoritative and be wrong.

What about sealed hygiene or cosmetic products?

Sealed health, hygiene and cosmetic products lose the right of withdrawal only once the customer has opened them. An unopened item is still fully withdrawable. The plugin therefore does not let you exclude such products automatically, because no software can tell whether a package has been opened. That check belongs to your team when the return arrives.

Does it work behind Cloudflare and with a caching plugin?

Yes. The withdrawal page excludes itself from caching using the standard signal that WP Rocket, LiteSpeed Cache, W3 Total Cache, WP Super Cache and WP Fastest Cache all honour. Behind a CDN there is one setting to turn on, because otherwise every visitor appears to arrive from the same address and shares one rate limiting budget. If you run a cache everything rule at the CDN, add a bypass for that page.

A customer asked to be deleted. What happens to their withdrawal?

By default it is kept, and the erasure report says so and why. The declaration is the record of a legal act and your evidence that you honoured it. If you would rather it went, switch on erasure under Withdrawal, Advanced: the name, address and free text are then replaced with anonymised values while the declaration, its reference and its date survive.

The deadline was June. The exposure is ongoing.

Install it yourself in ten minutes, or have FDesign put it in, word it for your shop, and test the guest path on your own staging before it meets a real customer.

Setup includes the guest path tested end to end on your staging, before go live.