Best SaaS Boilerplates for GDPR Compliance in 2026
TL;DR
GDPR compliance isn't a plugin — it's an architectural choice. The decisions baked into your boilerplate (how auth is stored, where data lives, whether you can delete a user completely) determine how hard GDPR compliance will be. The best boilerplates for GDPR in 2026 are: Supastarter (Supabase RLS ensures data isolation; EU data center option), T3 Stack (you control every data store), and self-hosted Supabase + any boilerplate (data never leaves your servers). Here's what GDPR actually requires technically and how each boilerplate stacks up.
Key Takeaways
- GDPR core requirements: right to deletion, right to access, data portability, consent management, breach notification
- Boilerplate impact: data model (org isolation, cascade deletes) and hosting choice (EU servers)
- Supastarter: best pre-built RLS isolation + EU Supabase region support
- T3 Stack + self-hosted: maximum control, minimum third-party data sharing
- The hard GDPR parts: deleting user data from all third-party integrations (analytics, CRM, email)
- Cookie consent: all boilerplates need a consent manager added — none ship with one
What GDPR Actually Requires (Technically)
GDPR is a legal framework, but its requirements translate into concrete technical features:
| GDPR Requirement | Technical Implementation |
|---|---|
| Right to erasure (Art. 17) | CASCADE DELETE from all tables; purge from analytics, email, CRM |
| Right to access (Art. 15) | Export all user data as JSON/CSV |
| Data portability (Art. 20) | Machine-readable data export |
| Consent (Art. 7) | Cookie consent manager; opt-in not pre-ticked |
| Data minimization (Art. 5) | Don't store data you don't need |
| Breach notification (Art. 33) | Incident response process, 72-hour notification |
| Data Processing Agreements | DPAs with all vendors (Stripe, SendGrid, etc.) |
| Privacy by design (Art. 25) | Default settings protect privacy |
| EU data residency | Optional but often contractually required |
The Four GDPR-Critical Technical Features
1. Complete User Data Deletion
The most technically challenging GDPR requirement is "right to erasure" — you must be able to delete a user and all their data from every system.
// Complete user deletion (what you need to implement in any boilerplate):
export async function deleteUserAndAllData(userId: string) {
// Step 1: Cancel Stripe subscription
const user = await db.user.findUnique({
where: { id: userId },
include: { subscription: true },
});
if (user?.subscription?.stripeSubscriptionId) {
await stripe.subscriptions.cancel(user.subscription.stripeSubscriptionId);
await stripe.customers.del(user.subscription.stripeCustomerId);
}
// Step 2: Delete from email service (Resend/SendGrid)
if (user?.resendContactId) {
await resend.contacts.remove({ id: user.resendContactId, audienceId: AUDIENCE_ID });
}
// Step 3: Delete from analytics (PostHog)
if (process.env.POSTHOG_API_KEY) {
await fetch(`https://your-posthog.com/api/person/`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.POSTHOG_API_KEY}` },
body: JSON.stringify({ distinct_ids: [userId] }),
});
}
// Step 4: Delete files from storage (Supabase/S3)
await supabase.storage.from('user-files').list(userId).then(async ({ data }) => {
const paths = data?.map((f) => `${userId}/${f.name}`) ?? [];
if (paths.length) await supabase.storage.from('user-files').remove(paths);
});
// Step 5: Delete from database (CASCADE deletes handle related records):
await db.user.delete({ where: { id: userId } });
// Step 6: Anonymize audit logs (delete identity, keep event)
await db.auditLog.updateMany({
where: { userId },
data: { userId: null, email: '[deleted]', ipAddress: null },
});
}
Why Prisma/Drizzle schemas matter for GDPR:
// ✅ GDPR-friendly schema — cascade deletes propagate:
model User {
id String @id @default(cuid())
email String @unique
subscriptions Subscription[]
sessions Session[]
posts Post[]
files File[]
// When user is deleted: all related records auto-delete
}
model Subscription {
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
// ❌ GDPR-unfriendly — orphaned records after deletion:
model Subscription {
userId String
user User @relation(fields: [userId], references: [id])
// onDelete defaults to Restrict — deletion FAILS if subscription exists
}
2. Data Export (Right to Access)
// app/api/user/data-export/route.ts
export async function GET(req: Request) {
const session = await auth();
if (!session) return new Response('Unauthorized', { status: 401 });
const userId = session.user.id;
// Collect all user data:
const [user, subscriptions, posts, files] = await Promise.all([
db.user.findUnique({ where: { id: userId } }),
db.subscription.findMany({ where: { userId } }),
db.post.findMany({ where: { userId } }),
db.file.findMany({ where: { userId } }),
]);
const exportData = {
exportDate: new Date().toISOString(),
profile: {
id: user?.id,
email: user?.email,
name: user?.name,
createdAt: user?.createdAt,
},
subscriptions: subscriptions.map((s) => ({
plan: s.plan,
status: s.status,
startDate: s.createdAt,
})),
content: posts,
files: files.map((f) => ({ name: f.name, size: f.size, createdAt: f.createdAt })),
};
return new Response(JSON.stringify(exportData, null, 2), {
headers: {
'Content-Type': 'application/json',
'Content-Disposition': `attachment; filename="my-data-${Date.now()}.json"`,
},
});
}
3. Consent Management
All boilerplates need a cookie consent manager added — none ship with one:
// Minimal consent manager (add to any boilerplate):
// components/ConsentBanner.tsx
'use client';
import { useState, useEffect } from 'react';
type ConsentPreferences = {
necessary: true; // Always true, can't be turned off
analytics: boolean;
marketing: boolean;
};
export function ConsentBanner() {
const [showBanner, setShowBanner] = useState(false);
const [prefs, setPrefs] = useState<ConsentPreferences>({
necessary: true,
analytics: false,
marketing: false,
});
useEffect(() => {
const stored = localStorage.getItem('consent');
if (!stored) setShowBanner(true);
}, []);
const acceptAll = () => {
const consent = { necessary: true, analytics: true, marketing: true };
localStorage.setItem('consent', JSON.stringify(consent));
setShowBanner(false);
initAnalytics(); // Only load analytics after consent
};
const acceptNecessary = () => {
const consent = { necessary: true, analytics: false, marketing: false };
localStorage.setItem('consent', JSON.stringify(consent));
setShowBanner(false);
};
if (!showBanner) return null;
return (
<div className="fixed bottom-0 left-0 right-0 bg-white border-t p-4 shadow-lg z-50">
<p className="text-sm text-gray-600 mb-3">
We use cookies to improve your experience. Analytics cookies help us understand
how you use our product.{' '}
<a href="/privacy" className="underline">Privacy policy</a>
</p>
<div className="flex gap-2">
<button
onClick={acceptAll}
className="px-4 py-2 bg-black text-white rounded text-sm"
>
Accept all
</button>
<button
onClick={acceptNecessary}
className="px-4 py-2 border rounded text-sm"
>
Necessary only
</button>
</div>
</div>
);
}
// Only load analytics after consent:
export function initAnalytics() {
if (typeof window === 'undefined') return;
const consent = JSON.parse(localStorage.getItem('consent') ?? '{}');
if (consent.analytics) {
// Load PostHog, GA, etc.
import('./analytics').then(({ loadPostHog }) => loadPostHog());
}
}
Or use a third-party consent manager: CookieYes ($10/month), Cookiebot ($12/month), or Termly (free tier). These handle auto-blocking scripts based on consent — more reliable than DIY.
4. EU Data Residency
# Supabase — choose EU region at project creation:
# Europe (Frankfurt): eu-central-1
# Europe (London): eu-west-2
# Configure in .env:
NEXT_PUBLIC_SUPABASE_URL="https://[ref].supabase.co"
# Choose EU region during project creation in Supabase dashboard
# Vercel — set region:
# vercel.json:
{
"regions": ["fra1"] # Frankfurt
}
# Or in Vercel dashboard: Settings → Functions → Region → Frankfurt (fra1)
Boilerplate Rankings for GDPR
Supastarter — Best GDPR Foundation
Why it's best for GDPR:
- Row Level Security pre-written — data isolation between organizations is enforced at the database level (users can't access other orgs' data, even with SQL injection)
- Supabase EU region support — point to Frankfurt during setup
- Complete Prisma-style cascade deletes in migrations
- SSR auth means auth tokens never stored in localStorage (XSS safe)
What you still need to add:
- Cookie consent manager (not included)
- Data export endpoint
- Complete deletion flow including Stripe/email service cleanup
T3 Stack — Most Controllable
Why T3 is good for GDPR:
- Prisma schema fully in your control — design cascade deletes from day one
- No mandatory third-party services baked in
- Self-host on Railway/Render in EU region easily
- NextAuth self-hosted = no user data at auth0.com/clerk.com
GDPR additions needed:
// Add to prisma/schema.prisma — cascade deletes:
model User {
// Add to every relation:
posts Post[] @relation(onDelete: "Cascade")
files File[] @relation(onDelete: "Cascade")
sessions Session[] @relation(onDelete: "Cascade")
}
ShipFast — Average GDPR Support
ShipFast doesn't have pre-written RLS (no multi-tenancy), but for B2C SaaS (one user = their own data), GDPR is actually simpler — you only need deletion, export, and consent.
ShipFast GDPR gaps:
- MongoDB option: cascade deletes require middleware or manual deletion
- No built-in data export
- Analytics (Plausible) is privacy-friendly — points in ShipFast's favor
Self-Hosted Supabase + Any Boilerplate — Maximum Privacy
If GDPR compliance is a hard requirement and you have data sovereignty concerns:
# Self-host Supabase with Docker:
git clone https://github.com/supabase/supabase
cd supabase
cp .env.example .env
# Configure SMTP, JWT secret, etc.
docker compose up -d
# Your data never leaves your server
# You control every aspect of data processing
# GDPR Article 28 DPA needed with hosting provider, not Supabase
Third-Party Services Checklist
When building a GDPR-compliant SaaS, sign DPAs with every vendor:
| Service | DPA Available | Data Location | GDPR-Safe |
|---|---|---|---|
| Supabase | ✅ | EU regions available | ✅ |
| Stripe | ✅ | EU regions | ✅ |
| Resend | ✅ | US (EU option) | ✅ with DPA |
| PostHog (self-hosted) | N/A | Your server | ✅ |
| PostHog (cloud EU) | ✅ | EU | ✅ |
| Vercel | ✅ | EU regions | ✅ |
| Clerk | ✅ | US + EU | ✅ with DPA |
| Plausible (self-hosted) | N/A | Your server | ✅ |
The GDPR Setup Checklist for Any Boilerplate
Database:
[ ] Cascade deletes on all user relations (Prisma onDelete: Cascade)
[ ] No user PII in log files
[ ] Audit logs anonymized on deletion (keep event, remove identity)
API / Backend:
[ ] DELETE /api/user/account endpoint implemented
[ ] GET /api/user/data-export endpoint implemented
[ ] Auth tokens in httpOnly cookies (not localStorage)
[ ] No unnecessary data collected at signup
Frontend:
[ ] Cookie consent banner (not pre-checked boxes)
[ ] Analytics only loaded after consent
[ ] Privacy policy linked from signup and footer
[ ] Cookie policy listing all cookies used
Infrastructure:
[ ] EU region selected (Vercel fra1, Supabase eu-central-1)
[ ] DPAs signed with all vendors
[ ] Data retention policy defined (delete inactive accounts after X days)
Legal:
[ ] Privacy policy drafted (use Iubenda, Termly, or a lawyer)
[ ] Terms of service updated
[ ] Stripe DPA signed in Stripe dashboard
[ ] Supabase DPA signed in Supabase dashboard
Find boilerplates by compliance features at StarterPick.