GDPR Basics Developers Actually Need
29 Apr 2026GDPR compliance is mostly a legal question, but a real chunk of it is decided in code you write without a lawyer in the room. Here’s the part that’s actually your job.
Data minimization: stop collecting what you don’t use
The single highest-leverage thing you can do is delete fields from your signup form and your database schema. Every column you collect is something you have to justify, secure, and eventually delete correctly. Before adding a new field, ask: does this feature break without it? If not, don’t collect it.
Common offenders: storing full IP addresses in application logs indefinitely, collecting a phone number “just in case,” keeping analytics events with personally identifiable fields when an anonymized ID would do the job.
The right to deletion is a real feature, not a policy line
“Users can request deletion” needs to actually work when someone emails you. Two ways to build it:
Hard delete, the honest default for small apps:
DELETE FROM users WHERE id = $1;
Cascade this properly — check every table with a foreign key to users, not just the obvious ones. Orphaned rows in a comments or audit_log table are still personal data if they contain a name or email, even after the user row is gone.
Soft delete with anonymization, when you need to keep the row for referential integrity (an order history, a paid invoice):
UPDATE users
SET email = 'deleted-' || id || '@deleted.local',
name = 'Deleted User',
deleted_at = now()
WHERE id = $1;
Whichever you pick, it needs to reach backups too, eventually. You don’t have to purge deletions from every historical backup immediately, but your retention policy should say when old backups age out, so a deletion request isn’t undone by a restore six months later.
Logging is where most accidental violations happen
It’s easy to log a full request body “for debugging” and accidentally persist emails, passwords, or tokens in plaintext log files that get shipped to a third-party log aggregator with its own retention and access policies you haven’t reviewed.
// don't
logger.info('signup request', { body: req.body })
// do
logger.info('signup request', { email: redact(req.body.email) })
Set a log retention window (30-90 days is typical) and actually enforce it — most log platforms let you configure this once and forget it.
Consent needs a record, not just a checkbox
If you rely on consent as your legal basis for processing (as opposed to “necessary to perform the contract,” which covers most core app functionality), you need to be able to show what a user agreed to and when:
CREATE TABLE consent_log (
user_id INT REFERENCES users(id),
consent_type TEXT, -- 'marketing_email', 'analytics', etc.
granted BOOLEAN,
version TEXT, -- which privacy policy version
recorded_at TIMESTAMPTZ DEFAULT now()
);
A checkbox that isn’t logged is a UI element, not evidence.
Where you actually need a lawyer
Whether you need a Data Processing Agreement with each vendor, whether you need an EU representative, and cross-border transfer mechanisms are legal calls, not engineering ones. Don’t try to self-serve those with a blog post — this one included.
The concrete next action
Grep your logging calls for req.body, req.user, or anything that dumps a full object. That’s the fastest way to find where you’re accidentally persisting personal data somewhere with no deletion path at all.