Implement GDPR compliance as an engineering practice, not a legal checkbox. Covers data minimization, consent management, right to erasure, data portability, privacy by design patterns, and the technical architecture that makes compliance maintainable.
GDPR for Engineers: Building Privacy-Compliant Systems
This reference is for engineers building systems that must meet the General Data Protection Regulation (GDPR) requirements. Use it when designing, implementing, or auditing data flows, storage, and processing pipelines. It applies when systems must support data subject rights, record processing activities, enforce consent, and ensure accountability across the data lifecycle.
Implementing the right to access, rectify, erase, and port personal data requires clear data modeling and integration patterns.
When a data subject requests their personal data, the system must return all relevant information in a structured, commonly used format. The request is typically initiated via an API endpoint, such as GET /api/v1/users/{id}/personal-data.
A minimal implementation must return:
userId, email)consent object listing all consents granted, including source, timestamp, and withdrawal statusprocessing-activities list with source system, purpose, retention period, and legal basis{
"userId": "u-7b9c2e",
"email": "[email protected]",
"name": "Alice Johnson",
"consents": [
{
"id": "c-3a1f7d",
"purpose": "marketing",
"grantedAt": "2023-04-15T10:30:00Z",
"expiresAt": "2024-04-15T10:30:00Z",
"withdrawnAt": null,
"source": "website",
"legalBasis": "consent"
}
],
"processing-activities": [
{
"system": "customer-portal",
"purpose": "account management",
"retentionPeriod": "5 years",
"legalBasis": "contract"
},
{
"system": "analytics",
"purpose": "user behavior tracking",
"retentionPeriod": "18 months",
"legalBasis": "legitimate interest"
}
]
}
The API must be idempotent and include a X-Request-ID header for tracing. Errors are returned as HTTP 400–500 with a standard error body:
{
"error": "data_not_found",
"message": "No personal data found for user with ID 'u-7b9c2e'",
"details": {
"userId": "u-7b9c2e",
"requestedAt": "2023-11-20T14:22:11Z"
}
}
Rectification requests update existing data. The system must support partial updates using PATCH /api/v1/users/{id}/personal-data.
PATCH /api/v1/users/{id}/personal-data
Content-Type: application/json
{
"name": "Alice Smith",
"preferences": {
"newsletter": true,
"smsNotifications": false
}
}
The server must:
data_change_log table with userId, field, oldValue, newValue, changedAt, and actorconsent-update event to downstream systems (e.g., via Kafka or SQS)Erasure (right to be forgotten) requires cascading deletion and logging:
-- Trigger erasure via stored procedure
CALL erase_user_data(
user_id := 'u-7b9c2e',
reason := 'user_request',
retention_period := '7 years'
);
This procedure:
users, profiles, orders, sessions, and logsarchive/users/u-7b9c2e/ with a json file and parquet fileuser_erased event to a message queuedata_subject_rights table with userId, right, executedAt, durationMs, statusA common failure: forgetting to remove user data from embedded systems (e.g., email templates, CRM, or third-party analytics). The system must maintain a data_subject_rights table that tracks every right execution and its status.
Consent must be granular, recorded, and auditable.
The system must support POST /api/v1/consents to record new consent:
POST /api/v1/consents
Content-Type: application/json
{
"userId": "u-7b9c2e",
"purpose": "marketing",
"source": "mobile-app",
"legalBasis": "consent",
"grantedAt": "2023-11-20T15:10:00Z",
"expiresAt": "2024-11-20T15:10:00Z",
"consentText": "I agree to receive marketing emails and push notifications."
}
The API returns 201 Created and includes a Location header. The consents table must enforce uniqueness per userId + purpose.
A critical failure: expiresAt is NULL when purpose is marketing but source is email. The system must validate that expiresAt is set when legalBasis is consent.
Withdrawal of consent must be tracked and propagated:
PATCH /api/v1/consents/consent-id
{
"withdrawnAt": "2023-11-21T09:30:00Z",
"reason": "user requested in settings"
}
The system must:
consent_history table with all versions of a consentA common pattern: a consent_version table with id, userId, purpose, version, effectiveFrom, validUntil, withdrawnAt, updatedBy.
Each data processing activity must reference a legal basis: consent, contract, legal obligation, public task, or legitimate interest.
The system must support a processing-activity table with a legalBasis field and a legalBasisDetails JSON column.
{
"purpose": "user profile enrichment",
"system": "data-warehouse",
"legalBasis": "legitimate interest",
"details": {
"justification": "improves user experience and personalization",
"balancingTest": {
"userInterest": "high",
"businessInterest": "medium",
"impactOnUser": "moderate",
"riskOfInfringement": "low"
}
}
}
Failure mode: legalBasis is stored as a string ("consent") but no validation is done. A missing details field causes downstream systems to fail when generating privacy notices.
The GDPR requires a record of all processing activities. This record must be available upon request and updated regularly.
The system must maintain a processing_activities table with these fields:
activityId (UUID)dataSubjectType (e.g., "customer", "employee")purpose (text)category (e.g., "personal data", "financial data")dataController (e.g., "[email protected]")dataProcessor (e.g., "aws-s3", "google-analytics")retentionPeriod (integer in months)legalBasis (string)consentRequired (boolean)dataTransferCountries (array of country codes)dataRetentionPolicy (text)securityMeasures (JSON object)createdAt, updatedAt, lastAuditedUse a cron job to generate the record in JSON and HTML formats:
# /etc/systemd/system/gdpr-process-activities.service
[Unit]
Description=Generate GDPR Processing Activities Record
After=network.target
[Service]
Type=oneshot
ExecStart=/opt/gdpr/bin/generate-processing-activities-record.sh
User=app
Group=app
Environment=NODE_ENV=production
[Install]
WantedBy=timers.target
The script:
#!/bin/bash
set -euo pipefail
OUTPUT_DIR="/var/gdpr/processing-activities"
DATE=$(date +%Y-%m-%d)
mkdir -p "$OUTPUT_DIR/$DATE"
# Export as JSON
psql -c "SELECT json_agg(row_to_json(t)) FROM (
SELECT * FROM processing_activities
) t" > "$OUTPUT_DIR/$DATE/activities.json"
# Export as HTML
curl -X POST \
-H "Content-Type: application/json" \
-d @- \
http://localhost:3000/api/generate-html \
< "$OUTPUT_DIR/$DATE/activities.json" \
> "$OUTPUT_DIR/$DATE/activities.html"
# Archive and publish
tar -czf "$OUTPUT_DIR/$DATE/activities.tar.gz" -C "$OUTPUT_DIR/$DATE" .
aws s3 cp "$OUTPUT_DIR/$DATE/activities.tar.gz" s3://company-gdpr/records/
The output is accessible at https://gdpr.company.com/records/{date}/.
Each change to a processing activity must be logged in processing_activity_audit_log:
CREATE TABLE processing_activity_audit_log (
id BIGSERIAL PRIMARY KEY,
activityId UUID NOT NULL,
changedBy UUID,
action VARCHAR(20) NOT NULL, -- 'create', 'update', 'delete', 'audit'
oldData JSONB,
newData JSONB,
timestamp TIMESTAMPTZ DEFAULT NOW(),
metadata JSONB
);
A POST /api/v1/audits/processing-activities endpoint triggers an audit:
POST /api/v1/audits/processing-activities
{
"activityId": "a-1f9b2c",
"auditorId": "u-3a4b5c",
"notes": "Reviewed for compliance with GDPR Article 30",
"status": "verified"
}
This creates a record in processing_activity_audit_log and updates processing_activities.status to audited.
Accountability requires traceability across systems and events.
The system must support triggering a DPIA for high-risk processing.
POST /api/v1/dpias
{
"title": "New user onboarding workflow",
"description": "End-to-end onboarding with identity verification",
"riskLevel": "high",
"processingActivities": [
"a-1f9b2c",
"a-4d5e6f"
],
"dataController": "[email protected]",
"dueDate": "2024-03-31"
}
The system stores the DPIA in dpias table and generates a dpias_progress table tracking steps: initiated, dataMapping, riskAssessment, consultation, approval, review.
For each data processing activity, the system must generate and store a data flow diagram (DFD) in a standard format.
{
"id": "dfd-7a8b9c",
"title": "User profile sync across systems",
"description": "Sync user data from customer portal to analytics and CRM",
"source": "customer-portal",
"destination": ["analytics", "crm"],
"dataElements": [
{
"name": "userId",
"type": "UUID",
"source": "customer-portal",
"target": ["analytics", "crm"],
"sensitivity": "high",
"retention": "5 years",
"processingPurpose": "user analytics"
}
],
"metadata": {
"author": "[email protected]",
"createdAt": "2023-11-20T12:00:00Z",
"lastUpdated": "2023-11-20T12:15:00Z"
}
}
The system must expose a GET /api/v1/diagrams/{id} endpoint, returning the DFD in JSON or SVG.
Compliance must be tested and monitored continuously.
A test-compliance.sh script runs before deployment:
#!/bin/bash
set -euo pipefail
# Test 1: All consent records have valid legal basis
psql -c "
SELECT count(*) FROM consents
WHERE legal_basis NOT IN ('consent', 'contract', 'legal obligation', 'public task', 'legitimate interest')
" | grep -q '0'
# Test 2: All processing activities have at least one data transfer country
psql -c "
SELECT count(*) FROM processing_activities
WHERE data_transfer_countries IS NULL OR array_length(data_transfer_countries, 1) = 0
" | grep -q '0'
# Test 3: Data subject rights are logged
psql -c "
SELECT count(*) FROM data_subject_rights
WHERE right = 'erase' AND status = 'pending'
" | grep -q '0'
# Fail if any test fails
if ! (test 1 && test 2 && test 3); then
echo "Compliance tests failed"
exit 1
fi
The test suite is run as part of CI, with failures reported in a compliance-report.json file.
A compliance-monitoring service runs every 15 minutes, checking:
data_subject_rights records with status pendingIt logs results to a compliance_check_log table and sends alerts via POST /api/v1/alerts.
Failure mode: the monitoring service runs but does not detect a missing_audit — because the lastAudited field is null but not NULL in the database. Use IS NULL in the query, not = NULL.
GDPR compliance for engineers is not a checklist but a system of traceable, testable, and monitored data flows. From consent to erasure, from processing records to accountability, every action must be logged, versioned, and verifiable. The system must not just store data but prove that it processes data according to law.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy