GDPR Compliance in Magento 2: Export, Erasure, Consent
AI generated
M2
di.xml
Magento 2 · GDPR · Data Protection · PHP 8.4
GDPR Compliance in Magento 2
Data export, erasure concept and consent management implemented technically

Unlike Adobe Commerce, Magento Open Source ships no built-in tool for the right of access or erasure, even though both rights are clearly regulated under the GDPR. A custom data export service, an anonymization concept that respects retention obligations, and traceable consent management can be cleanly anchored in the shop with PHP 8.4, service contracts and a dedicated cron job.

17 min read Art. 15 · Art. 17 · Art. 20 GDPR · anonymization Magento 2.4.8-p4 · PHP 8.4

1. Legal framework and the gap in Magento Open Source

GDPR compliance in a Magento shop centers on three articles: Art. 15 governs the right of access, meaning the complete export of all data stored about a person. Art. 17 governs the right to erasure, with important exceptions for statutory retention obligations. Art. 20 governs data portability, meaning delivering the data in a structured, machine-readable format. These three rights are legally unambiguous; their technical implementation in a grown Magento shop with years of order and customer data is not.

The decisive difference between Adobe Commerce and Magento Open Source: Adobe Commerce ships a built-in interface for data export and erasure with its "Compliance Tool." Magento Open Source, the foundation of most mid-sized shops, ships no built-in GDPR functionality whatsoever. Anyone working with Magento Open Source as an agency or developer has to implement the right of access, erasure concept and consent management entirely themselves; there is no admin button that handles it.

This article deliberately doesn't cover pure legal theory but the technical implementation: which tables contain personal data, how to build an export service with PHP 8.4, why anonymization instead of hard-delete is the right strategy for order data, and how to concretely implement consent tracking as well as automated erasure deadlines. A shop's GDPR compliance is ultimately always a combination of technical implementation and organizational process; this article covers the technical side.

2. Identifying personal data in Magento

Before an export or erasure concept can even be designed, it has to be established where personal data in Magento actually lives. The most obvious tables are customer_entity with name, email and address, as well as customer_address_entity for shipping and billing addresses. Less obvious but equally relevant: sales_order, sales_order_address and sales_order_payment contain personal data even for guest orders without a customer account, as do quote and quote_address for carts not yet checked out.

Frequently overlooked are customer_log and customer_visitor, which store login timestamps and IP addresses, as well as report_event and report_viewed_product_index, which track user behavior for product recommendations and also store personal identifiers in the process. Newsletter data in newsletter_subscriber is just as much part of the scope as stored payment information at payment providers, which is usually not held in Magento itself but referenced via a customer ID in the payment provider's database and must be considered in the erasure concept. Full GDPR compliance requires a documented, complete list of all these tables before a single export or erasure command is written.

3. Implementing the right of access technically

For the technical implementation of Art. 15, a custom service is recommended that bundles the relevant repository interfaces and delivers a structured export array that can then be output as JSON. The service should deliberately build on CustomerRepositoryInterface, OrderRepositoryInterface, and CartRepositoryInterface instead of writing SQL directly against the tables from section 2, because repositories automatically handle authorization checks and store scope filtering.

It's also important that the export works both for logged-in customers via the customer_id and for guest orders via the linked email address, since Art. 15 makes no distinction between registered and non-registered persons. A cleanly structured export simultaneously simplifies fulfilling Art. 20, because an already machine-readable JSON format directly satisfies the data portability requirement as well.


declare(strict_types=1);

namespace Vendor\GdprTools\Model;

use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;

/**
 * Builds a structured personal data export for a customer or guest, per Art. 15 GDPR.
 */
final class PersonalDataExportService
{
    /**
     * @param CustomerRepositoryInterface $customerRepository Provides registered customer master data
     * @param OrderRepositoryInterface $orderRepository Provides order history for the requested identity
     * @param SearchCriteriaBuilder $searchCriteriaBuilder Builds filtered order search criteria
     */
    public function __construct(
        private readonly CustomerRepositoryInterface $customerRepository,
        private readonly OrderRepositoryInterface $orderRepository,
        private readonly SearchCriteriaBuilder $searchCriteriaBuilder,
    ) {
    }

    /**
     * Assembles the full export structure for a customer, ready to be encoded as JSON.
     *
     * @param int $customerId Entity ID of the registered customer
     * @return array<string, mixed> Structured export payload covering master data and order history
     */
    public function exportForCustomer(int $customerId): array
    {
        $customer = $this->customerRepository->getById($customerId);

        $criteria = $this->searchCriteriaBuilder
            ->addFilter('customer_id', $customerId)
            ->create();
        $orders = $this->orderRepository->getList($criteria)->getItems();

        return [
            'master_data' => [
                'email' => $customer->getEmail(),
                'firstname' => $customer->getFirstname(),
                'lastname' => $customer->getLastname(),
                'created_at' => $customer->getCreatedAt(),
            ],
            'orders' => array_map(
                static fn ($order): array => [
                    'increment_id' => $order->getIncrementId(),
                    'created_at' => $order->getCreatedAt(),
                    'grand_total' => $order->getGrandTotal(),
                    'shipping_address' => $order->getShippingAddress()?->getData(),
                ],
                $orders,
            ),
        ];
    }
}

4. Erasure concept and the conflict with retention obligations

Art. 17 GDPR grants the right to erasure, which however isn't absolute. German commercial and tax law, specifically HGB and AO, mandate retaining invoice and bookkeeping records for six to ten years. A shop that immediately hard-deletes order data from sales_order after an erasure request thereby violates statutory retention obligations, even if it formally complies with the data subject's erasure request.

The solution established in practice for this conflict is anonymization instead of hard-delete: name, address, email, and all directly identifying fields get replaced with placeholders, while order_increment_id, amounts, tax rates, and payment method are fully preserved for bookkeeping. After anonymization, the order remains fully traceable for tax purposes but can no longer be attributed to an identifiable person, thereby satisfying the underlying intent of Art. 17 without violating the retention obligation. This strategy is the central building block of any realistic GDPR compliance for order data in Magento.

5. Anonymization implementation

The concrete anonymization class should specifically overwrite only the identifying fields and should never delete the complete order entity. Personal free-text fields such as a company name in the address field or a phone number belong to the anonymization scope just as much as first and last name, while numeric and tax-relevant fields must remain untouched. A clean approach additionally marks a custom flag gdpr_anonymized_at on the order entity, so later processes, for example a repeated export attempt, can recognize that the order has already been anonymized.

Important for the implementation: anonymization must not forget to cascade to related tables like sales_order_grid, which contains a denormalized copy of the customer data for the admin grid display. A common mistake in custom GDPR implementations is anonymizing only the main table and overlooking the grid table, which exists separately for performance reasons, causing personal data to remain visible in the admin panel despite a supposedly completed erasure.


declare(strict_types=1);

namespace Vendor\GdprTools\Model;

use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Framework\App\ResourceConnection;

/**
 * Anonymizes personally identifiable fields on an order while preserving
 * amounts and tax data required for statutory retention (HGB/AO).
 */
final class OrderAnonymizer
{
    private const string PLACEHOLDER = 'anonymized';

    /**
     * @param OrderRepositoryInterface $orderRepository Loads and saves the order entity
     * @param ResourceConnection $resourceConnection Direct connection for the denormalized sales_order_grid table
     */
    public function __construct(
        private readonly OrderRepositoryInterface $orderRepository,
        private readonly ResourceConnection $resourceConnection,
    ) {
    }

    /**
     * Replaces identifying fields with placeholders, keeps totals and tax data untouched.
     *
     * @param int $orderId Entity ID of the order to anonymize
     * @return void
     */
    public function anonymize(int $orderId): void
    {
        $order = $this->orderRepository->get($orderId);
        $order->setCustomerEmail(self::PLACEHOLDER . '@example.invalid');
        $order->setCustomerFirstname(self::PLACEHOLDER);
        $order->setCustomerLastname(self::PLACEHOLDER);
        $order->setData('gdpr_anonymized_at', date('Y-m-d H:i:s'));
        // Totals, tax rates and order_increment_id are intentionally left untouched
        // to preserve statutory bookkeeping requirements.
        $this->orderRepository->save($order);

        // The order grid is a denormalized read model and must be anonymized separately.
        $connection = $this->resourceConnection->getConnection();
        $connection->update(
            $this->resourceConnection->getTableName('sales_order_grid'),
            ['customer_email' => self::PLACEHOLDER . '@example.invalid', 'customer_name' => self::PLACEHOLDER],
            ['entity_id = ?' => $orderId],
        );
    }
}

Demonstrating consent under Art. 7(1) GDPR requires more than just a checked box at checkout. For newsletter delivery via Magento\Newsletter, double opt-in is already technically prepared, but the timestamp and the specific version of the consent text that was agreed to are not documented by Magento by default. A custom table storing timestamp, IP address, consent version, and a hash of the consent text closes this gap and provides the required proof in case of a dispute.

The same principle applies to cookie consent: simply storing a cookie "consent given: yes" isn't enough if the consent banner text or the queried categories later change. Every consent should be stored linked to the version of the consent text valid at the time of agreement, so that even months later it's traceable exactly what a person agreed to, not just that some agreement occurred.


<?xml version="1.0"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
    <table name="vendor_gdpr_consent_log" resource="default" engine="innodb" comment="GDPR consent audit trail">
        <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true"/>
        <column xsi:type="varchar" name="email" nullable="false" length="255" comment="Identifying email at consent time"/>
        <column xsi:type="varchar" name="consent_type" nullable="false" length="64" comment="newsletter, cookies, marketing"/>
        <column xsi:type="varchar" name="consent_version" nullable="false" length="32" comment="Version of the consent text shown"/>
        <column xsi:type="varchar" name="ip_address" nullable="false" length="45"/>
        <column xsi:type="timestamp" name="consented_at" nullable="false" default="CURRENT_TIMESTAMP"/>
        <constraint xsi:type="primary" referenceId="PRIMARY">
            <column name="entity_id"/>
        </constraint>
    </table>
</schema>

7. Automated erasure deadlines via cron

Manual erasure processes don't scale across many thousands of customer records, which is why a dedicated cron job, registered via crontab.xml, should implement fixed rules for automated erasure deadlines. Typical rules are: guest orders without an associated customer account get automatically anonymized once the statutory retention period expires, inactive customer accounts without a login for several years get deleted or anonymized after prior notification, and cart data in quote without a completed order gets cleaned up after a few weeks.

Important for the implementation: the cron job should never delete immediately and without logging, but should first only flag records and actually anonymize them after a lead time, so that an accidentally too-aggressively configured job doesn't irrevocably destroy data before the mistake is noticed.


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="default">
        <job name="vendor_gdpr_anonymize_expired_orders" instance="Vendor\GdprTools\Cron\AnonymizeExpiredOrders" method="execute">
            <!-- Runs nightly at 2 AM, well outside typical business hours -->
            <schedule>0 2 * * *</schedule>
        </job>
    </group>
</config>

8. Audit trail and proof obligation

When a supervisory authority makes an inquiry, a shop must not only prove that it processes erasure or export requests, but also when, by whom, and with what result. A dedicated logging table that records every export and anonymization operation with a timestamp, the executing admin user, and the affected entity ID is the central building block for this proof obligation. Without such an audit trail, it can't be demonstrated in a dispute that an erasure request was actually processed on time.

This logging itself should not be subject to the erasure concept, but should be retained beyond the regular retention period, since it precisely provides the proof of proper GDPR compliance. A contradiction that seems surprising at first glance: the logs about deleted data must themselves be retained longer than the original data, because they represent the only evidence of correct implementation.

9. Comparison: requirement vs. technical implementation vs. risk

The overview below maps the most important GDPR articles to their technical implementation in Magento and the risk of non-compliance.

Requirement Technical implementation Risk of non-compliance Priority
Art. 15 right of access Export service bundling repositories Fine, complaint to supervisory authority High
Art. 17 erasure Anonymization instead of hard-delete Violation of retention obligation or GDPR High
Art. 20 portability Structured JSON export format Formally incomplete fulfillment of the claim Medium
Art. 7(1) proof Consent log with version and timestamp Missing proof in case of a complaint Medium
General proof obligation Audit trail for export and erasure No evidence under regulatory review Medium

This prioritization shows: export and erasure concept are the two most urgent building blocks of any GDPR compliance effort, because they can be directly demanded by data subjects, while consent log and audit trail rather secure long-term demonstrability.

10. Summary

GDPR compliance in Magento Open Source is only achievable through custom development, since unlike Adobe Commerce no built-in compliance tool exists. An export service based on repository interfaces covers Art. 15, an anonymization concept instead of hard-delete resolves the conflict between Art. 17 and commercial and tax law retention obligations, and a structured JSON format simultaneously satisfies Art. 20.

Consent management with versioned consent texts, automated erasure deadlines via a documented cron job, and a dedicated audit trail for all export and erasure operations round out a solid GDPR compliance setup. It remains important: this technical implementation does not replace legal advice; a data protection officer or lawyer should review the concrete erasure concept before going live, especially for industry-specific retention periods.

GDPR Compliance in Magento 2: The Essentials at a Glance

No built-in solution

Magento Open Source ships no compliance tool, unlike Adobe Commerce. Custom development is mandatory.

Anonymization instead of hard-delete

Anonymize order data due to HGB/AO retention obligations rather than fully deleting it. Don't forget sales_order_grid either.

Log consent with versioning

Store timestamp, IP and version of the consent text, not just a simple yes/no flag.

Keep the audit trail long-term

Retain logs about export and erasure longer than the original data itself, as proof.

11. FAQ: GDPR Compliance in Magento 2

1Built-in GDPR tool in Open Source?
No, unlike Adobe Commerce this has to be developed as a custom module.
2Which tables contain personal data?
customer_entity, sales_order, quote, customer_log, report_event, and newsletter_subscriber, among others.
3Why no hard-delete for orders?
HGB and AO mandate a retention period of six to ten years.
4Correct alternative to hard-delete?
Anonymization: replace identifying fields, keep amounts and tax data.
5Handle sales_order_grid separately?
Yes, it's a denormalized copy and isn't automatically anonymized.
6Is a simple consent cookie enough?
No, timestamp, IP, and text version must be stored as well.
7Automate erasure deadlines?
Via a custom cron job in crontab.xml with defined rules and a lead time.
8Why an audit trail?
To prove to authorities that erasure requests were handled properly.
9How long retain the audit trail?
Longer than the original data, since it represents the only proof.
10Does this replace legal advice?
No, a data protection officer or lawyer should review the concept before going live.

Mironsoft

Magento data protection modules, anonymization concepts and consent management

Still missing GDPR compliance for your Magento shop?

We build data export services, anonymization concepts, and consent management as a custom Magento 2 module, tailored to your retention obligations and data models.

Data audit

Fully document all personal data in your shop

Erasure concept

Anonymization instead of hard-delete, coordinated with your bookkeeping

Consent module

Versioned consent tracking with a complete audit trail