Adam Innes · Blog

GDPR for Web Developers: What the Regulation Means in Code

· 7 min · privacy, gdpr, security, web development

Last Friday, May 25, the General Data Protection Regulation started to apply across the EU. It’s easy to think of it as a job for lawyers and whoever writes the privacy policy, but a surprising amount of it lands on the people who build the systems: where data gets stored, how long it sticks around, and whether you can find it again when someone asks.

So this is a developer’s reading of it. To be plain up front, this is not legal advice. I’m not a lawyer, and whether a given obligation applies to your organisation, and how, is a question for your legal team or data protection officer. What I can do is paraphrase what the text says about the things developers touch, and suggest what that looks like in code and infrastructure.

Where to read it

The regulation is Regulation (EU) 2016/679, and the official text on EUR-Lex is more readable than its reputation suggests, especially the recitals at the front, which is where a lot of the reasoning lives. For interpretation, the Article 29 Working Party (the group of EU data protection authorities under the old directive) published guidelines over the last two years, and on May 25 its successor, the European Data Protection Board, formally endorsed them. Two of them are very practical for engineers.

Personal data is broader than a users table

Article 4 defines personal data as information relating to an identified or identifiable person, and names an online identifier as one way someone can be identified. Recital 30 spells out what that means for the web: IP addresses and cookie identifiers can leave traces that, combined with other information, can be used to profile and identify people. Recital 26 adds that pseudonymised data, which can be linked back to a person with additional information, still counts.

So personal data is not just the email and name columns. It’s the IP address in your access log, the user ID in your error tracker, the device identifier in your analytics events, and the rows in last month’s database dump.

The principles, translated

Article 5 lists the principles, and several map straight onto engineering decisions. Purpose limitation says data is collected for specified, explicit and legitimate purposes and not further processed in incompatible ways. Data minimisation says it should be limited to what the purpose needs. Storage limitation says you keep it in identifiable form no longer than necessary. Integrity and confidentiality means appropriate security. And the accountability clause says the controller has to be able to demonstrate compliance, which is a polite way of saying you’ll need records.

Article 6 is the lawful basis article. At a high level, processing needs at least one of six grounds: consent, a contract, a legal obligation, vital interests, a public task, or legitimate interests that aren’t overridden by the person’s own interests and rights. Picking the basis isn’t a developer’s call, but it changes what you build. Article 7 says consent has to be as easy to withdraw as to give, so a consent flag needs a working off switch that actually stops the processing.

Article 25 asks for data protection by design and by default. By design means building those principles into systems when you decide how processing will work, with pseudonymisation given as an example. By default means that out of the box only the data necessary for each purpose is processed, which the article applies to how much you collect, how far you process it, how long you store it, and who can access it.

My practical reading: collect less. Every optional form field, every event property, every “we might want this later” column is something you now have to secure, export, delete, and explain. If you don’t need a date of birth, don’t ask for it.

Know where the data lives

You can’t export or delete what you can’t find, so the first real engineering task is an inventory. Start with the primary database, then keep going through replicas, caches, logs, error monitoring, analytics, email tools, and backups. Article 30 asks many organisations to keep records of processing activities, including categories of data, recipients, and where possible time limits for erasure. Smaller organisations may be exempt, but a data map is what makes everything below doable anyway.

Build access, export and deletion paths

Chapter III, the rights of the data subject, is the part that turns into tickets. Article 15 gives people the right to confirm whether you process their data, get a copy, and learn things like the purposes and recipients. Article 17 is the right to erasure, which applies on several grounds (the data is no longer needed, consent is withdrawn, the person objects and there’s no overriding reason, and a few more) and has its own exceptions, including legal obligations and legal claims. Article 20 is data portability: for data a person provided, where processing is based on consent or contract and carried out by automated means, they can receive it in a structured, commonly used and machine readable format. Article 12 says you respond without undue delay and within one month, extendable by two further months for complex or numerous requests.

The Working Party’s guidelines on data portability are the most engineering flavoured document in the set. They read “provided by” broadly, covering observed data like activity logs and search history, while inferred or derived data such as a profile the service computes is out of scope. Where no format is standard for your industry, they suggest open formats like XML, JSON or CSV with useful metadata, and say a PDF of an email inbox is unlikely to be structured enough. They also say you must authenticate the requester, and that portability doesn’t oblige you to keep data past your normal retention periods just in case.

A self service export behind the normal authenticated account session is the obvious shape. For deletion, the hard part is everything outside the main table. Here’s a sketch, with the database and vendor calls as placeholders:

def erase_user(user_id):
    with db.transaction():
        db.execute("DELETE FROM sessions WHERE user_id = %s", [user_id])
        db.execute("DELETE FROM users WHERE id = %s", [user_id])
        db.execute("INSERT INTO erasures (user_id, erased_at) VALUES (%s, now())", [user_id])
    for processor in processors:
        processor.request_deletion(user_id)

The vendor loop is there because Article 19 says erasure should be communicated to each recipient the data was disclosed to, unless that proves impossible or disproportionate.

Deletion and backups

The regulation doesn’t mention backups by name, but storage limitation applies to every copy, while Article 32 separately wants you able to restore data after an incident. One reasonable approach is to keep backup retention short and documented, so deleted data ages out on a known schedule, and to keep a small erasure ledger like the one above holding only internal IDs. If you ever restore a backup, replay that ledger before the restored data goes back into use, so a restore can’t quietly bring back someone who asked to be erased. Whether a retention window is acceptable is a legal question; making the behaviour predictable is an engineering one.

Keep logs from hoarding personal data

Logs are where personal data piles up without anyone deciding it should: request bodies with emails, query strings with tokens, full IP addresses kept forever. Recital 49 treats processing that’s strictly necessary for network and information security as a legitimate interest, so security logging isn’t off the table, but minimisation and storage limitation still apply. Set a retention period on log storage, don’t log request bodies by default, and scrub obvious identifiers before they’re written. With Python’s standard logging module, a handler filter can do that for every record passing through:

import logging
import re

EMAIL = re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+")

class RedactEmails(logging.Filter):
    def filter(self, record):
        record.msg = EMAIL.sub("[email]", record.getMessage())
        record.args = ()
        return True

handler = logging.StreamHandler()
handler.addFilter(RedactEmails())
logging.getLogger().addHandler(handler)

A regex won’t catch everything, so treat it as a safety net rather than the plan.

Third party scripts and processors

Every analytics snippet, chat widget and ad tag you embed can receive personal data from your visitors’ browsers. Article 28 says controllers should only use processors that provide sufficient guarantees, under a contract that covers, among other things, acting only on documented instructions, helping with data subject requests, and deleting or returning data at the end. On the code side, keep a list of every third party that touches personal data, remove the ones nobody uses, check what each script actually sends in your browser’s network tab, and switch off optional collection you don’t need.

Encrypt, restrict access, and plan for a breach

Article 32 asks for security appropriate to the risk and names pseudonymisation and encryption, ongoing confidentiality, integrity, availability and resilience, timely restoration, and regular testing. In practice that means TLS everywhere, encryption at rest, and least privilege on production data.

Article 33 sets the breach clock. A controller notifies the supervisory authority without undue delay and, where feasible, within 72 hours of becoming aware, unless the breach is unlikely to result in a risk to people, and a processor tells the controller without undue delay. Every breach gets documented either way. Article 34 adds that high risk breaches are communicated to the people affected, unless measures such as encryption made the data unintelligible. The Working Party’s breach notification guidelines say a controller is “aware” once it has a reasonable degree of certainty that a security incident compromised personal data, and that a confidentiality breach of properly encrypted data, where the key wasn’t compromised and a backup exists, may not need notifying. You can’t meet 72 hours without alerting, audit logs, and a clear incident owner, so build those first.

Where I’d start

I’d begin with the inventory, because everything else depends on it, then work outward to export, deletion, log retention and the scripts on your pages. The regulation is deliberately general about what’s “appropriate”, and that’s for your organisation to decide with proper advice. But collecting less and knowing where your data lives are good engineering whether a regulation asks for them or not.

← all posts