gdpr-dev/SKILL.md
GDPR compliance for European developers. Use when building features that handle personal data: user registration, profiles, authentication, email sending, analytics, error tracking, payments, search indexes, background jobs, or any feature that stores, processes, or transmits information about people. Provides stack-aware gotchas, anti-patterns to avoid, and schema design principles. Does not ask questions — applies principles proactively based on what is being built.
npx skillsauth add mikkelkrogsholm/dev-skills gdpr-devInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A reference for developers building GDPR-compliant web applications in Europe. These principles apply proactively — if personal data is involved, GDPR applies.
For the full list of anti-patterns, see references/gotchas.md. For schema design, see references/schema-patterns.md.
console.log(user) or logging request bodies sends personal data to error trackers and log aggregators. Sentry captures console.log calls as breadcrumbs — treat it as a PII risk. Log IDs, not objects.
// Bad
console.log("Processing user:", user);
logger.info({ user });
// Good
logger.info({ userId: user.id });
If email or name is in the JWT payload, that data cannot be erased when a user requests deletion — tokens already issued remain valid and cannot be recalled. Use opaque session tokens, or put only a user ID in JWTs with a very short expiry.
// Bad — email is now unrevocable in every issued token
jwt.sign({ userId, email, name }, secret);
// Good
jwt.sign({ userId }, secret, { expiresIn: "15m" });
deleted_at = now() keeps all PII intact. The right to erasure means nulling or overwriting PII fields, not just marking a row deleted. Keep the row for referential integrity; erase the content.
-- Bad: PII still present
UPDATE users SET deleted_at = now() WHERE id = ?;
-- Good: PII removed, row preserved
UPDATE users SET email = null, name = null, erased_at = now() WHERE id = ?;
Erasing from the live database has no effect on backups. Either align backup retention with your erasure SLA, or prefer anonymization over deletion so backups contain no identifiable data.
Access logs, rate-limiting records, and analytics all capture IP addresses. Under GDPR these are personal data. Apply the same retention and erasure policies as any other PII.
Stack traces often include the data that triggered the error (Error: User [email protected] not found). These reach Sentry and log aggregators with PII attached. Sanitize error messages before logging.
// Bad
throw new Error(`User ${email} not found`);
// Good
const err = new Error("User not found");
err.userId = userId; // internal reference only, not surfaced to Sentry
throw err;
development
Zod — TypeScript-first schema validation with static type inference. Use when building with Zod or asking about schema definitions, type inference, parsing, transformations, refinements, coercion, error handling, or integration with forms, APIs, or tRPC. Fetch live documentation for up-to-date details.
tools
Vite — next-generation frontend build tool with instant dev server and optimized production builds. Use when building with Vite or asking about its APIs, configuration, plugins, SSR, environment variables, or integration with frameworks. Fetch live documentation for up-to-date details.
tools
Upstash — serverless Redis, QStash, and Vector database with per-request pricing optimized for edge and serverless environments. Use when building with Upstash or asking about its Redis client, QStash message queuing, rate limiting, workflows, or vector search. Fetch live documentation for up-to-date details.
tools
Turso — edge-hosted SQLite database built on libSQL with embedded replicas, multi-tenancy, and low-latency global distribution. Use when building with Turso or asking about its libSQL client, embedded replicas, database-per-tenant patterns, auth tokens, sync, or integration with Drizzle or other ORMs. Fetch live documentation for up-to-date details.