Skip to content
StackPractices
intermediate By Mathias Paulenko

GDPR Compliance — A Practical Guide for Developers

A developer-focused guide to GDPR compliance: data subject rights, lawful basis, data minimization, and technical measures for privacy by design.

Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.

Overview

The General Data Protection Regulation (GDPR) is the EU’s data privacy law, but its reach extends to any organization processing EU residents’ data. For developers, compliance is not a legal checkbox — it is a set of technical and architectural requirements: pseudonymization, data minimization, encryption, consent tracking, and deletion capabilities. This guide translates GDPR articles into useful engineering practices.

When to Use

  • For alternatives, see Data Privacy and GDPR Compliance.

  • You process personal data of EU residents

  • You need to implement data subject rights (access, erasure, portability)

  • You are designing systems that handle PII (Personally Identifiable Information)

  • You need to demonstrate privacy by design to auditors

Key GDPR Concepts for Developers

Personal Data

Any information relating to an identified or identifiable natural person. Examples: name, email, IP address, cookie ID, device fingerprint.

Data TypeGDPR Coverage
Name, email, phoneDirect PII
IP address, cookie IDIndirect PII (can identify)
Aggregated analyticsNot PII if properly anonymized
Pseudonymized dataStill PII under GDPR

Lawful Basis for Processing

You must have a legal reason to process data:

BasisDeveloper Implication
ConsentUser explicitly opted in; must be revocable
ContractData needed to fulfill a service contract
Legal obligationRequired by law (e.g., tax records)
Legitimate interestBalanced against user rights; not override consent

Privacy by Design

Data Minimization

Collect only what you need, store only as long as necessary.

# Bad: collect everything
user = {
    "name": request.name,
    "email": request.email,
    "phone": request.phone,  # Do you actually need this?
    "birthdate": request.birthdate,
    "ssn": request.ssn,  # Never collect without explicit need
}

# Good: minimal required fields
user = {
    "name": request.name,
    "email": request.email,  # Needed for authentication
}

Pseudonymization

Replace identifying fields with pseudonyms to reduce risk.

import uuid
import hashlib

# Generate a pseudonym for analytics
pseudonym = hashlib.sha256(
    f"{user_id}:{secret_salt}".encode()
).hexdigest()[:16]

# Store pseudonym in analytics DB, real ID in separate DB
analytics.record(user_pseudonym=pseudonym, event="page_view")

Encryption at Rest and in Transit

# Encrypt PII before storing
from cryptography.fernet import Fernet

key = Fernet.generate_key()
cipher = Fernet(key)

encrypted_email = cipher.encrypt(user.email.encode())
db.store(encrypted_email=encrypted_email)

Data Subject Rights

Right to Access

Users can request a copy of all their data.

@app.get("/api/users/me/data-export")
def export_user_data(user: CurrentUser):
    data = {
        "profile": db.get_profile(user.id),
        "orders": db.get_orders(user.id),
        "logs": audit_log.get_entries(user.id),
    }
    return JSONResponse(content=data, headers={
        "Content-Disposition": f"attachment; filename=user-{user.id}-export.json"
    })

Right to Erasure (Right to be Forgotten)

Delete all user data, including backups (within a reasonable timeframe).

@app.delete("/api/users/me")
def delete_user(user: CurrentUser):
    # Soft delete first (grace period)
    db.users.update(user.id, deleted_at=now(), deletion_scheduled=now() + days(30))
    
    # Cascade to related tables
    db.orders.anonymize(user_id=user.id)
    db.sessions.revoke_all(user.id)
    audit_log.purge(user.id)  # Or anonymize
    
    # Queue hard delete from backups
    queue.enqueue("delete_from_backups", user_id=user.id, execute_after=days(30))

Right to Data Portability

Export data in a machine-readable format.

@app.get("/api/users/me/export/portable")
def portable_export(user: CurrentUser):
    data = db.export_user_data(user.id)
    return {
        "format": "JSON",
        "schema_version": "1.0",
        "data": data,
        "exported_at": datetime.utcnow().isoformat()
    }

Consent must be specific, informed, and freely given.

// Frontend: granular consent checkboxes
<form>
  <label>
    <input type="checkbox" name="marketing_email" />
    Send me marketing emails
  </label>
  <label>
    <input type="checkbox" name="analytics_cookies" />
    Allow analytics cookies
  </label>
  <label>
    <input type="checkbox" name="third_party_sharing" />
    Share data with partners
  </label>
</form>
class ConsentRecord(BaseModel):
    user_id: str
    purpose: str  # "marketing_email"
    granted: bool
    version: str  # "v2.1"
    timestamp: datetime
    ip_address: str
    user_agent: str

def record_consent(user_id: str, purpose: str, granted: bool):
    db.consent_log.insert(ConsentRecord(
        user_id=user_id,
        purpose=purpose,
        granted=granted,
        version=CURRENT_CONSENT_VERSION,
        timestamp=datetime.utcnow(),
        ip_address=request.client_ip,
        user_agent=request.user_agent
    ))

Breach Notification

GDPR requires breach notification within 72 hours.

# Automated breach detection and alerting
def detect_anomaly(access_log):
    if access_log.unauthorized_attempts > 100:
        alert_security_team(
            severity="high",
            description="Possible breach: mass unauthorized access attempts",
            affected_users=access_log.unique_user_ids,
            timestamp=datetime.utcnow()
        )

Common Mistakes

  • Assuming GDPR only applies in the EU — it applies to any data of EU residents, regardless of company location
  • Storing consent as a boolean without context — you need version, timestamp, and what was consented to
  • Hard deleting without audit trail — auditors need proof of erasure
  • Ignoring third-party processors — you are liable for your vendors’ compliance
  • Using legitimate interest as a catch-all — it must be balanced against user rights

FAQ

Do I need a Data Protection Officer (DPO)? Required if you process large-scale sensitive data or do systematic monitoring. Appoint one proactively if unsure.

What is the difference between anonymization and pseudonymization? Anonymization is irreversible — data can never be re-linked to a person. Pseudonymization uses a reversible mapping (kept separately), so it is still personal data under GDPR.

How do I handle backups for right to erasure? Delete data from active systems immediately. Remove from backups within a documented retention period (typically 30-90 days), with technical notes explaining why delay is necessary.

How do I get started with this in an existing project?

Start with a small, isolated part of your codebase. Apply the concepts from this guide to one module or service. Measure the impact, then expand to other areas.

What tools do I need?

The tools mentioned throughout this guide are listed in each section. Most are open-source and widely adopted. Check the related resources for setup instructions.

How do I measure success after implementing this?

Define clear metrics before starting: performance benchmarks, error rates, or maintainability indicators. Compare before and after. Iterate based on the data, not on assumptions.

Advanced Topics

Scenario: GDPR Implementation for B2C SaaS

System: B2C SaaS, users in EU, handles PII
Goal: Full GDPR compliance

GDPR principles:
  1. Lawfulness, fairness, transparency
  2. Purpose limitation
  3. Data minimization
  4. Accuracy
  5. Storage limitation
  6. Integrity and confidentiality
  7. Accountability

User rights (DSAR - Data Subject Access Request):
  | Right | Implementation | SLA |
  |-------|----------------|-----|
  | Access | Export all user data | 30 days |
  | Rectification | Edit personal data | 30 days |
  | Erasure (forget me) | Delete account and all data | 30 days |
  | Portability | Export in JSON/CSV format | 30 days |
  | Objection | Opt-out of marketing | Immediate |
  | Restriction | Freeze processing temporarily | 30 days |
  | No automated | No algorithmic-only decisions | N/A |

Technical implementation:
  | Requirement | Implementation |
  |-------------|----------------|
  | Consent | Cookie banner + explicit opt-in |
  | Encryption | AES-256 at rest, TLS 1.3 in transit |
  | Minimization | Only collect necessary data |
  | Retention | Auto-delete after 2 years inactive |
  | DPA (Data Processing Agreement) | Contract with each sub-processor |
  | DPO (Data Protection Officer) | Designate if > 250 employees |
  | DPIA (Data Protection Impact Assessment) | For high-risk processing |
  | Breach notification | Notify authority within 72h |
  | Activity register | Log of data processing activities |

```sql
-- Consent table
CREATE TABLE user_consents (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL REFERENCES users(id),
  purpose VARCHAR(100) NOT NULL,  -- marketing, analytics, etc
  consent_given BOOLEAN NOT NULL,
  consent_date TIMESTAMP NOT NULL DEFAULT NOW(),
  withdrawal_date TIMESTAMP,
  ip_address VARCHAR(45),
  UNIQUE(user_id, purpose)
);

-- DSAR (Data Subject Access Requests) table
CREATE TABLE dsar_requests (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL REFERENCES users(id),
  request_type VARCHAR(50) NOT NULL,  -- access, deletion, portability
  status VARCHAR(20) DEFAULT "pending",  -- pending, processing, completed
  requested_at TIMESTAMP DEFAULT NOW(),
  completed_at TIMESTAMP,
  notes TEXT
);

-- Auto-deletion: inactive users after 2 years
-- Cron job: DELETE FROM users WHERE last_login < NOW() - INTERVAL "2 years";

Lessons:

  • GDPR applies if you have users in EU, regardless of server location
  • Consent must be explicit, not pre-checked
  • Right to erasure: delete all data, including backups
  • Breach notification within 72h is mandatory
  • DPA with each sub-processor (AWS, Stripe, etc)
  • Audit log of DSAR: who requested what and when it was completed

### How do I handle the right to erasure with backups?

Delete the data from the active database immediately. For backups, mark the user for deletion in the next backup cycle. When the backup expires (per retention policy, typically 30-90 days), the data is permanently deleted. Document the process: regulators accept that backups take longer, as long as there is a defined process.