How to Write GDPR-Compliant Code: A Developer's Guide to Privacy by Design

Privacy by Design is a legal requirement under Article 25 of the GDPR: data protection must be built into a system's architecture from the start, not bolted on afterward. For developers, this translates into concrete coding decisions — not just legal paperwork. This guide covers what those decisions actually look like in practice.

 

What "Privacy by Design" Actually Means for Code

Article 25 requires that both the technical measures and the default settings of a system protect personal data automatically, without requiring the user to take action. In code terms, this means the compliant behavior has to be the default behavior — not an option a user has to discover and enable, and not a checklist item handled outside the codebase after the fact.

 

Core Coding Principles for GDPR Compliance

1. Data Minimization

Only collect and store fields your application actually uses. Every additional column in a `users` table is additional legal exposure and additional surface area in the event of a breach. If a field isn't read anywhere in the codebase, it shouldn't exist in the schema.

 

2. Encryption at Rest and in Transit

Personal data should be encrypted both in the database and over the network. This isn't just TLS on your endpoints — sensitive fields (passwords, tokens, financial identifiers) should be encrypted or hashed at the storage layer itself, so a raw database dump doesn't expose usable data.

 

3. Pseudonymization Where Possible

Where you need data for analytics or debugging but not the raw identity behind it, replace direct identifiers with a pseudonymous key. This is explicitly called out in the GDPR as a recommended technical measure, and it means a breach of that dataset alone doesn't directly expose who the data belongs to.

 

4. Building the Right to Erasure Into Your Data Layer

This is where architecture choices directly determine how hard compliance is. If personal data is scattered across dozens of tables, caches, logs, and third-party integrations with no single point of control, implementing a real "delete this person's data" operation becomes a fragile, manually-maintained checklist. This is exactly the kind of problem a proper repository layer is built to solve: if all access to a given entity's data goes through one repository, that repository is also the natural place to implement a complete, auditable deletion (or anonymization) routine — one method, one source of truth, instead of hunting down every place a user's data might be referenced.


Also worth deciding deliberately: soft-delete versus hard-delete. A `SoftDeletes` pattern (a `deleted_at` timestamp instead of removing the row) is convenient for application logic, but it does not, on its own, satisfy a genuine erasure request — the data is still physically present. Compliant erasure means either a real hard delete or an irreversible anonymization of the personal fields, triggered explicitly, not assumed to happen as a side effect of a soft delete.

 

5. Consent as Data, Not as a Checkbox

Consent needs to be stored, timestamped, and scoped to a specific purpose — not just a boolean flag with no history. If a user later disputes whether they consented to something, or withdraws consent, your system needs a record to act on, not just a current state.

 

6. Automated Data Retention

Personal data should have a defined lifecycle. Rather than relying on someone remembering to purge old records, retention policies should be enforced in code — a scheduled job that actually deletes or anonymizes data past its justified retention period, tied to the same repository-level deletion logic mentioned above.

 

7. Access Logging and Auditability

Being able to answer "who accessed this person's data, and when" is both a compliance requirement in some contexts and a genuinely useful debugging tool. Logging access to personal data (not just modifications) at the repository or service layer, rather than trying to reconstruct it from application logs after the fact, makes this answerable rather than guessed at.

 

Why This Connects to Architecture, Not Just Legal Checklists

The common thread through all of the above is that GDPR compliance gets dramatically easier or dramatically harder depending on decisions made at the architecture level — specifically, whether data access is centralized and consistent or scattered and ad hoc. This is the same underlying reasoning covered in our breakdown of the core components of a modern PHP framework: a repository layer, a clear service layer, and consistent patterns for data access aren't just clean-code preferences, they're what make privacy-by-design actually implementable rather than aspirational.

 

The Legal Side: What the Rights Actually Require

The coding practices above exist to serve real legal rights that data subjects hold. We've covered these in full detail in our complete guide to GDPR rights — note that this article is written in French, since it's aimed at our French audience, but here are two of the most relevant passages for developers specifically, with translations:


"Vous pouvez demander la suppression de vos données personnelles, notamment lorsque celles-ci ne sont plus nécessaires au regard des finalités pour lesquelles elles ont été collectées, ou lorsque vous retirez votre consentement."

Translation: "You can request the deletion of your personal data, particularly when it is no longer necessary for the purposes for which it was collected, or when you withdraw your consent."
 


"Vous avez le droit de récupérer les données que vous avez fournies à un organisme, dans un format structuré et lisible par machine, afin de les réutiliser ou de les transmettre à un autre organisme."

Translation: "You have the right to retrieve the data you provided to an organization, in a structured, machine-readable format, in order to reuse it or transmit it to another organization."

 

Read literally as engineering requirements, these two passages map directly onto two of the coding practices above: the first is the right to erasure section, requiring the real deletion/anonymization logic described earlier. The second is the right to portability, which requires your data layer to be able to export a given user's data in a structured, machine-readable format (typically JSON) — another task made straightforward by a repository that already knows how to fetch everything belonging to that user.
 

A Practical Checklist

  • Does every personal-data field in your schema actually get used somewhere in the application?
  • Is sensitive data encrypted at the storage layer, not just in transit?
  • Is there a single, testable method that fully deletes or anonymizes a user's data — not a manually maintained list of tables to clear?
  • Does a soft-delete anywhere in your codebase get mistaken for a compliant erasure?
  • Is consent stored with a timestamp and a specific purpose, not just a boolean?
  • Is data retention enforced by a scheduled job, or by hoping someone remembers?
  • Can you export a single user's full data set in a structured format on request?
     

Frequently Asked Questions

Is Privacy by Design only relevant to EU-based companies?
No. The GDPR applies to any organization processing the data of EU residents, regardless of where the company itself is based. Building these practices in from the start is also simply good security hygiene, independent of jurisdiction.


Does using a soft-delete pattern violate GDPR?
Not by itself — soft deletes are fine for normal application logic. The issue is treating a soft delete as equivalent to a compliant erasure request. A genuine erasure needs an explicit, separate operation that actually removes or anonymizes the personal data.


Where should retention and deletion logic live in the codebase?
Ideally at the repository or service layer, close to where the data is defined and accessed — not duplicated across controllers, jobs, and scripts. Centralizing it is what makes it auditable and testable.

 

Need help architecting a system that's compliant by design, not by afterthought? Get in touch.

Contact Pierre Miniggio →