Implementing GDPR right of access (Article 15) and right to erasure (Article 17) means building secure, token-based data request flows, composing export and deletion logic across multiple domains—auth, payment, notifications, cookies, contact—and delivering type-safe export payloads. From data request submission and email verification to access export and full erasure, creating a production-ready system requires weeks of careful development. Fastack provides a package-based GDPR access and erasure architecture that each domain implements once; the app composes handlers and exposes token-protected API routes—saving you weeks of development time.
Why right of access and right to erasure matter for SaaS
GDPR grants data subjects the right to obtain a copy of their data and the right to have their data deleted. SaaS applications must support both in a verifiable, auditable way:
- Right of access – Provide a complete copy of personal data in a portable format
- Right to erasure – Delete or anonymize data in a defined order to respect foreign keys and business rules
- Verification – Confirm the requester controls the email (e.g. token in link) before exposing or deleting data
- Composition – Export and erasure must span all domains that hold personal data (accounts, payments, notifications, cookies, contact, etc.)
What you get with Fastack
Fastack implements access and erasure at the package level; the app composes them into a single export and a single erasure flow.
Package-level access and erasure handlers
Each domain package exposes createGdprAccess and createGdprErasure. Handlers receive a context with userId and/or email so they work for both logged-in export and token-based data-subject requests. The core GDPR package defines the contract:
export type GdprContext = {
userId?: string;
email?: string;
};
export type GdprAccessHandler = (
context: GdprContext
) => Promise<Record<string, unknown>>;
export type GdprErasureHandler = (context: GdprContext) => Promise<void>;For example, the notifications package returns notifications and push subscriptions for the user (resolved by userId or email); its erasure handler deletes those records. Auth, payment, cookies, and contact follow the same pattern—each package owns its schema and returns a typed chunk (e.g. ExportNotificationsChunk) from dedicated types in the package.
Composed access export in the app
The app creates access handlers for each package (passing Prisma), calls them in parallel with a single context, and assembles a single payload. Logged-in users get an export by userId; data-subject requests use email (and optional userId if found). Type-safe chunk types are defined in the packages (e.g. @saas/notifications/gdpr) and imported in the app:
import { createGdprAccess as createNotificationsAccess } from '@saas/notifications/gdpr';
import type { ExportNotificationsChunk } from '@saas/notifications/gdpr';
// ... auth, payment, cookies, contact
function createAllAccessHandlers(prisma: PrismaClient) {
return {
auth: createAuthAccess({ prisma }),
payment: createPaymentAccess({ prisma }),
notifications: createNotificationsAccess({ prisma }),
cookies: createCookiesAccess({ prisma }),
contact: createContactAccess({ prisma }),
};
}
export async function buildExportForUser(prisma: PrismaClient, userId: string) {
const handlers = createAllAccessHandlers(prisma);
const context = { userId };
const [authChunk, paymentChunk, notificationsChunk, ...] = await Promise.all([
handlers.auth(context),
handlers.payment(context),
handlers.notifications(context),
// ...
]);
return {
exportedAt: new Date().toISOString(),
user: auth.user,
accounts: auth.accounts,
subscriptions: payment?.subscriptions ?? [],
notifications: notificationsChunk.notifications ?? null,
cookies: cookiesChunk.cookies ?? null,
contact: contactChunk.contact ?? null,
};
}Ordered erasure in the app
Erasure must run in a defined order to respect foreign keys and business rules (e.g. delete subscriptions before the user account). The app creates erasure handlers and runs them sequentially: payment first, then auth, then notifications, cookies, and contact. After that, the one-time token is deleted so the link cannot be reused.
const context = { userId: user?.id, email };
await paymentErasure(context);
await authErasure(context);
await notificationsErasure(context);
await cookiesErasure(context);
await contactErasure(context);
await prisma.dataSubjectRequestToken.deleteMany({
where: { token: row.token },
});
return NextResponse.json({ message: 'Your data has been deleted.' });Token-based data request flow
Data subjects request access or erasure by submitting their email (and request type) on a form, often with CAPTCHA. The app creates a short-lived token, stores it with the email and type, and sends a verification link by email. Only when the user clicks the link (access or erasure) does the app use the token to run export or erasure—then the token is consumed and removed.
- Submit – POST email + type (access/erasure) + CAPTCHA; create token, send verification email
- Verify – User opens link with token; confirm and choose "Download my data" or "Delete my data"
- Access – POST token; return JSON export, then delete token
- Erasure – POST token; run all erasure handlers, then delete token
Email verification and no enumeration
To avoid revealing whether an email exists in the system, the submit endpoint always returns the same message after validation and CAPTCHA: "If we have data associated with this email, we have sent a link...". The app only sends the verification email if there is account or contact data for that email (e.g. via a lightweight lookup); otherwise it still returns success without sending. That way, data subjects cannot enumerate accounts by email.
Time saved: 25+ hours
Building GDPR access and erasure from scratch typically requires:
- Designing access/erasure contracts and context (2–3 hours)
- Implementing per-domain access and erasure (auth, payment, etc.) (8–12 hours)
- Composing export payload and erasure order in the app (2–3 hours)
- Token generation, storage, and expiry (2–3 hours)
- Submit, verify, access, and erasure API routes (3–4 hours)
- Verification email template and no-enumeration behavior (1–2 hours)
- Data request UI and flows (2–3 hours)
- Testing and documentation (2–3 hours)
Total: 22–33 hours of development time that you save with Fastack.
Best practices included
Fastack follows GDPR and security best practices for data requests:
- Package-owned chunks – Export types (e.g. ExportNotificationsChunk) live in the owning package for type safety and single source of truth
- Deterministic erasure order – Payment then auth then other domains to respect dependencies
- One-time tokens – Verification links consume the token so they cannot be replayed
- No email enumeration – Same response and optional email send so attackers cannot probe for accounts
- Context by userId or email – Handlers work for both logged-in export and unauthenticated data-subject requests
Conclusion
GDPR right of access and right to erasure are essential for compliant SaaS applications. Fastack provides a package-based architecture where each domain implements createGdprAccess and createGdprErasure; the app composes them into a single export and a single erasure flow, protected by token-based verification and no-enumeration behavior. You get type-safe export chunks, ordered erasure, and a full data request flow—saving you weeks of development time while staying compliant.
With Fastack, you get:
- Package-level access and erasure handlers (auth, payment, notifications, cookies, contact)
- Type-safe export chunk types in each package
- Composed buildExportForUser / buildExportForEmail in the app
- Ordered erasure (payment → auth → notifications, cookies, contact)
- Token-based submit → verify → access/erasure flow
- Verification email and no-enumeration response
- GDPR data request flow built-in from day one
Ready to build your SaaS faster?
Get our scalable, production-ready boilerplate to save endless hours of development and setup
