Skip to main content
Back to Blog
6 min read

GDPR for small SaaS: the 5-hour setup that's enough

You don't need a €10K legal audit to be GDPR compliant. Here's the minimum viable compliance setup I implement for every EU-facing SaaS: what to do, what to skip, and what actually matters.

security

Most small SaaS founders in the EU know they need GDPR compliance. Most also think it requires a €10K legal audit, a consent management platform, and a full-time DPO. It doesn't — not at the small scale.

I've set up GDPR compliance for 6 client projects. Here's the minimum viable setup that takes about 5 hours and covers what actually matters.

What GDPR actually requires (the short version)

  1. Lawful basis for processing: You need a legal reason to collect data. For SaaS: consent (for marketing) or contract performance (for the service itself).
  2. Transparency: Tell users what you collect, why, and how long you keep it.
  3. Data minimization: Don't collect more than you need.
  4. Right to access/delete: Users can request their data or its deletion.
  5. Security: Protect the data you hold.
  6. Breach notification: Report breaches to the authority within 72 hours.

That's it. Everything else (DPO, DPIA, records of processing) has thresholds that small SaaS rarely meets.

The 5-hour setup

Hour 1: Privacy policy (the real one)

Not a legal template copied from a Fortune 500 company. A plain-language document that covers:

Write it in plain language. Not legalese. A user should understand what happens to their data in 2 minutes of reading.

If you use only essential cookies (session, authentication), you don't need a cookie banner at all under GDPR. Essential cookies have an exemption.

If you use analytics (PostHog, Google Analytics, Plausible):

My recommendation: use Plausible or PostHog in cookie-free mode and skip the cookie banner entirely. The banner costs you 5–15% of analytics accuracy (users who decline) and adds UX friction.

If you must have a banner, keep it simple: two buttons ("Accept" / "Reject"), no dark patterns, no pre-checked boxes.

Hour 3: Data deletion flow

When a user deletes their account, you need to actually delete their data. Not soft-delete — hard delete from the database.

async function deleteUserData(userId: string) {
  // 1. Cancel any active subscriptions
  await stripe.subscriptions.cancel(user.stripeSubscriptionId)

  // 2. Delete from your database
  await db.transaction(async (tx) => {
    await tx.delete(sessions).where(eq(sessions.userId, userId))
    await tx.delete(preferences).where(eq(preferences.userId, userId))
    await tx.delete(activities).where(eq(activities.userId, userId))
    await tx.delete(users).where(eq(users.id, userId))
  })

  // 3. Remove from third-party services
  await posthog.deleteUser(userId)
  await sentry.deleteUser(userId)

  // 4. Log the deletion (without PII)
  logger.info('User data deleted', { userId, timestamp: new Date() })
}

The deletion log (step 4) is your proof that you complied. Keep it for 3 years.

Hour 4: Data export endpoint

GDPR gives users the right to receive their data in a machine-readable format. Build one endpoint:

// app/api/user/export/route.ts
export async function GET(request: Request) {
  const user = await getAuthenticatedUser(request)

  const data = {
    profile: await db.query.users.findFirst({ where: eq(users.id, user.id) }),
    preferences: await db.query.preferences.findMany({
      where: eq(preferences.userId, user.id),
    }),
    activities: await db.query.activities.findMany({
      where: eq(activities.userId, user.id),
    }),
  }

  // Remove internal fields
  delete data.profile.passwordHash
  delete data.profile.stripeCustomerId

  return new Response(JSON.stringify(data, null, 2), {
    headers: {
      'Content-Type': 'application/json',
      'Content-Disposition': `attachment; filename="user-data-${user.id}.json"`,
    },
  })
}

JSON is a valid machine-readable format. You don't need CSV or XML.

Hour 5: Third-party audit

List every service that touches user data. For each one:

ServiceData it receivesDPA signed?EU hosting?
StripeEmail, name, paymentYes (standard)EU data residency available
VercelIP addresses (logs)Yes (DPA in ToS)EU region selectable
SentryIP, error contextYes (DPA available)EU data region
PlausibleNone (cookie-free)N/AEU-hosted
ResendEmail addressesYes (DPA available)Check region

DPA = Data Processing Agreement. Most SaaS providers have one. You need to sign it — usually just checking a box in their dashboard.

If a service doesn't offer a DPA or EU hosting, consider switching. For small SaaS, this is usually Resend vs SendGrid territory — both offer DPAs.

What you can skip (at small scale)

The ongoing maintenance

GDPR isn't a one-time setup. Monthly:

Quarterly:

Annually:

The cost of not doing it

A GDPR fine for a small company starts at €10K–€50K. More realistically, your first penalty would be a warning from the supervisory authority. But the real cost is losing a B2B deal because the buyer's procurement team sees no privacy policy and no DPA.

5 hours of setup. Zero ongoing cost if you use cookie-free analytics. And a privacy page that actually makes sense to real humans.


Need GDPR compliance set up for your SaaS? Let's talk — I include this in every EU-facing project I build.