Skip to main content
RapidDev - Software Development Agency

How to Build Privacy tools with V0

Build a GDPR/CCPA compliance toolkit with V0 using Next.js and Supabase that gives users a self-service privacy center for managing consent, requesting data exports, and submitting deletion requests. Includes a cookie consent banner, audit logging, and admin review workflows — all in about 1-2 hours.

What you'll build

  • Self-service privacy center with consent management toggles using shadcn/ui Switch components
  • Data export API route that aggregates user PII across all tables into a downloadable JSON file
  • Deletion request workflow with cascading soft-delete and audit trail
  • Cookie consent banner using Next.js middleware to block tracking scripts before consent
  • Admin audit log viewer with shadcn/ui Table showing all privacy-related actions
  • AlertDialog confirmations for irreversible deletion operations
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate10 min read1-2 hoursV0 Premium or higherApril 2026RapidDev Engineering Team
TL;DR

Build a GDPR/CCPA compliance toolkit with V0 using Next.js and Supabase that gives users a self-service privacy center for managing consent, requesting data exports, and submitting deletion requests. Includes a cookie consent banner, audit logging, and admin review workflows — all in about 1-2 hours.

What you're building

If your app collects any personal data, GDPR and CCPA require you to give users control over that data. Users must be able to see what you collect, consent to specific uses, export their data, and request deletion. Failing to comply risks fines up to 4% of annual revenue under GDPR.

V0 streamlines building this compliance toolkit by generating the privacy center UI, consent management forms, and export/deletion API routes from natural language prompts. Connect Supabase for storing consent records and audit logs, and use Next.js middleware for the cookie consent gate.

The architecture uses Next.js App Router with Server Components for the privacy dashboard, API routes for data export and deletion endpoints, Next.js middleware for pre-consent script blocking, Server Actions for consent updates, and Supabase for storing consent records, data requests, and a complete audit log.

Final result

A complete privacy compliance toolkit with a user-facing privacy center, consent management with granular toggles, data export and deletion request workflows, cookie consent banner, and a full audit trail for regulatory compliance.

Tech stack

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
SupabaseDatabase
ClerkAuth

Prerequisites

  • A V0 account (Premium recommended for prompt queuing)
  • A Supabase project (free tier works — connect via V0's Connect panel)
  • Clerk account for authentication (free tier works — users need to be logged in to manage privacy)
  • A list of what personal data your app collects (email, name, usage data, etc.)

Build steps

1

Set up the project and privacy schema

Open V0 and create a new project. Use the Connect panel to add Supabase, then prompt V0 to create the schema for consent records, data requests, and audit logging.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a GDPR/CCPA privacy compliance toolkit. Create a Supabase schema with:
3// 1. consent_records: id (uuid PK), user_id (uuid FK), consent_type (text check analytics/marketing/functional/essential), granted (boolean), ip_address (text), granted_at (timestamptz), revoked_at (timestamptz)
4// 2. data_requests: id (uuid PK), user_id (uuid FK), request_type (text check export/deletion/access), status (text check pending/processing/completed/denied), completed_at (timestamptz), download_url (text), created_at (timestamptz)
5// 3. audit_log: id (uuid PK), actor_id (uuid), action (text), entity_type (text), entity_id (uuid), details (jsonb), created_at (timestamptz)
6// Add RLS: users can only read/write their own consent_records and data_requests. Audit log is insert-only for all, select for admins.
7// Generate SQL migration and TypeScript types.

Pro tip: Use V0's Git panel to connect to GitHub — this gives you version-controlled history of every privacy policy and consent flow change, which is valuable for regulatory audits.

Expected result: Supabase is connected with consent_records, data_requests, and audit_log tables created. RLS policies enforce per-user data access.

2

Build the consent management privacy center

Create the main privacy center page where users can see and manage their consent preferences. Each consent category gets a Switch toggle that immediately updates via a Server Action.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a privacy center at app/privacy/page.tsx.
3// Requirements:
4// - Show a heading "Your Privacy Settings" with description about data control
5// - Display consent categories in shadcn/ui Cards: Essential (always on, disabled Switch), Analytics, Marketing, Functional
6// - Each Card has: category name, description of what data is collected, Switch toggle for granted/revoked
7// - Switch toggles call a Server Action updateConsent() that updates consent_records and logs to audit_log
8// - Below consent, show a "Your Data" section with two Cards:
9// - "Export My Data" Card with Button that creates a data_request with type=export
10// - "Delete My Data" Card with Button that opens AlertDialog ("This action cannot be undone") then creates a data_request with type=deletion
11// - Show a Table of existing data_requests with Badge for status (pending=yellow, completed=green)
12// - Add an Accordion at the bottom for privacy policy sections
13// - Use Toast for confirmation feedback after each action
14// - Server Components for data fetching, 'use client' for Switch toggles and AlertDialog

Expected result: A privacy center with Switch toggles for each consent category, export and delete request buttons, and a Table showing request history with status Badges.

3

Create the data export API route

Build an API route that aggregates all user data across tables into a JSON file. This uses SUPABASE_SERVICE_ROLE_KEY to bypass RLS and join across all tables containing user PII, then stores the export in Supabase Storage.

app/api/privacy/export/route.ts
1import { NextRequest, NextResponse } from 'next/server'
2import { createClient } from '@supabase/supabase-js'
3import { auth } from '@clerk/nextjs/server'
4
5const supabase = createClient(
6 process.env.SUPABASE_URL!,
7 process.env.SUPABASE_SERVICE_ROLE_KEY!
8)
9
10export async function POST(req: NextRequest) {
11 const { userId } = await auth()
12 if (!userId) {
13 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
14 }
15
16 const { data: request } = await supabase
17 .from('data_requests')
18 .insert({ user_id: userId, request_type: 'export', status: 'processing' })
19 .select()
20 .single()
21
22 const { data: profile } = await supabase
23 .from('profiles')
24 .select('*')
25 .eq('user_id', userId)
26 .single()
27
28 const { data: consents } = await supabase
29 .from('consent_records')
30 .select('*')
31 .eq('user_id', userId)
32
33 const { data: activities } = await supabase
34 .from('audit_log')
35 .select('*')
36 .eq('actor_id', userId)
37
38 const exportData = {
39 exported_at: new Date().toISOString(),
40 profile,
41 consent_records: consents,
42 activity_log: activities,
43 }
44
45 const blob = new Blob([JSON.stringify(exportData, null, 2)], {
46 type: 'application/json',
47 })
48
49 const filePath = `exports/${userId}/${request!.id}.json`
50 await supabase.storage.from('data-exports').upload(filePath, blob)
51
52 const { data: signedUrl } = await supabase.storage
53 .from('data-exports')
54 .createSignedUrl(filePath, 3600)
55
56 await supabase
57 .from('data_requests')
58 .update({
59 status: 'completed',
60 completed_at: new Date().toISOString(),
61 download_url: signedUrl?.signedUrl,
62 })
63 .eq('id', request!.id)
64
65 await supabase.from('audit_log').insert({
66 actor_id: userId,
67 action: 'data_export_completed',
68 entity_type: 'data_request',
69 entity_id: request!.id,
70 details: { file_path: filePath },
71 })
72
73 return NextResponse.json({ download_url: signedUrl?.signedUrl })
74}

Expected result: POST /api/privacy/export generates a JSON file with all user data, uploads it to Supabase Storage, and returns a signed download URL valid for 1 hour.

4

Add the cookie consent banner with Next.js middleware

Create a middleware that checks for a consent cookie before allowing analytics and marketing scripts to load. This ensures no tracking happens before the user explicitly grants consent, which is a core GDPR requirement.

middleware.ts
1import { NextRequest, NextResponse } from 'next/server'
2
3export function middleware(request: NextRequest) {
4 const response = NextResponse.next()
5 const consent = request.cookies.get('privacy-consent')?.value
6
7 if (!consent) {
8 response.headers.set('x-consent-status', 'none')
9 } else {
10 try {
11 const parsed = JSON.parse(consent)
12 response.headers.set(
13 'x-consent-analytics',
14 parsed.analytics ? 'granted' : 'denied'
15 )
16 response.headers.set(
17 'x-consent-marketing',
18 parsed.marketing ? 'granted' : 'denied'
19 )
20 } catch {
21 response.headers.set('x-consent-status', 'invalid')
22 }
23 }
24
25 return response
26}
27
28export const config = {
29 matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
30}

Pro tip: The cookie consent banner must load before any analytics scripts. Read the x-consent-analytics header in your layout.tsx to conditionally include Google Analytics or Plausible script tags.

Expected result: Every request gets consent status headers. Your layout can conditionally render analytics scripts based on these headers, ensuring no tracking before consent.

5

Build the admin audit log viewer

Create an admin page that displays the complete audit trail of all privacy-related actions. This is essential for demonstrating GDPR compliance during regulatory audits.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build an admin audit log page at app/admin/audit/page.tsx.
3// Requirements:
4// - Only accessible to admin users (check Clerk user role)
5// - Fetch all audit_log entries ordered by created_at descending
6// - Display in a shadcn/ui Table with columns: timestamp, actor_id, action, entity_type, entity_id
7// - Action column uses Badge with color coding: data_export=blue, consent_update=green, deletion_request=red
8// - Add date range filter using Calendar + Popover (DatePicker pattern)
9// - Add Select filter for action type
10// - Add pagination with 50 entries per page
11// - Use Server Components for data fetching
12// - Add a "Download Audit Log" Button that exports filtered results as CSV

Expected result: An admin audit log page showing all privacy actions in a filterable, paginated Table with color-coded action Badges and CSV export.

Complete code

app/api/privacy/export/route.ts
1import { NextRequest, NextResponse } from 'next/server'
2import { createClient } from '@supabase/supabase-js'
3import { auth } from '@clerk/nextjs/server'
4
5const supabase = createClient(
6 process.env.SUPABASE_URL!,
7 process.env.SUPABASE_SERVICE_ROLE_KEY!
8)
9
10export async function POST(req: NextRequest) {
11 const { userId } = await auth()
12 if (!userId) {
13 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
14 }
15
16 const { data: request } = await supabase
17 .from('data_requests')
18 .insert({
19 user_id: userId,
20 request_type: 'export',
21 status: 'processing',
22 })
23 .select()
24 .single()
25
26 const [profileRes, consentsRes, activitiesRes] = await Promise.all([
27 supabase.from('profiles').select('*').eq('user_id', userId).single(),
28 supabase.from('consent_records').select('*').eq('user_id', userId),
29 supabase.from('audit_log').select('*').eq('actor_id', userId),
30 ])
31
32 const exportData = {
33 exported_at: new Date().toISOString(),
34 profile: profileRes.data,
35 consent_records: consentsRes.data,
36 activity_log: activitiesRes.data,
37 }
38
39 const filePath = `exports/${userId}/${request!.id}.json`
40 const blob = new Blob([JSON.stringify(exportData, null, 2)])
41 await supabase.storage.from('data-exports').upload(filePath, blob)
42
43 const { data: signed } = await supabase.storage
44 .from('data-exports')
45 .createSignedUrl(filePath, 3600)
46
47 await supabase
48 .from('data_requests')
49 .update({
50 status: 'completed',
51 completed_at: new Date().toISOString(),
52 download_url: signed?.signedUrl,
53 })
54 .eq('id', request!.id)
55
56 return NextResponse.json({ download_url: signed?.signedUrl })
57}

Customization ideas

Add automated data retention policies

Create a scheduled Supabase function that automatically deletes data older than your retention period, with configurable retention windows per data category.

Add email notifications for request status changes

Integrate Resend via Vercel Marketplace to send email notifications when data export or deletion requests are completed.

Build a privacy policy version history

Store each privacy policy version with an effective date, and track which version each user consented to for precise compliance records.

Add third-party consent propagation

When a user revokes marketing consent, automatically call external APIs (Google Analytics, Mixpanel, Mailchimp) to propagate the opt-out.

Common pitfalls

Pitfall: Loading analytics scripts before checking consent status

How to avoid: Use Next.js middleware to set consent headers, then conditionally render script tags in layout.tsx based on the header values — scripts never load without consent.

Pitfall: Using SUPABASE_SERVICE_ROLE_KEY on the client side for data export

How to avoid: Only use SUPABASE_SERVICE_ROLE_KEY in API routes (app/api/*/route.ts) and Server Actions. Set it in the Vars tab without a NEXT_PUBLIC_ prefix.

Pitfall: Hard-deleting user data without an audit trail

How to avoid: Use soft-delete (mark records as deleted) and log every deletion action to the audit_log table before actually removing data. Keep audit logs for the legally required retention period.

Pitfall: Not including all tables in the data export

How to avoid: Create a Supabase database function that joins across ALL tables containing user_id and returns a complete JSON object. Maintain a checklist of PII-containing tables and update the export function when adding new tables.

Best practices

  • Always load the cookie consent banner before any analytics or marketing scripts via Next.js middleware
  • Log every privacy-related action (consent change, export, deletion) to the audit_log table for regulatory compliance
  • Use Supabase Storage with signed URLs for data exports — links expire automatically after 1 hour
  • Use V0's Git panel to connect to GitHub so all privacy flow changes are version-controlled and reviewable via PRs
  • Set SUPABASE_SERVICE_ROLE_KEY in the Vars tab without NEXT_PUBLIC_ prefix — only use it server-side for data aggregation
  • Implement soft-delete with a deleted_at timestamp before hard-deleting data to maintain audit trail integrity
  • Use V0's Design Mode (Option+D) to visually adjust the consent banner and privacy center layout without spending credits

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a GDPR/CCPA privacy compliance toolkit with Next.js App Router and Supabase. I need a Supabase database function that aggregates all user personal data across multiple tables (profiles, orders, messages, consent_records) by user_id and returns a single JSON object. Also need a cascading soft-delete function that marks all records with a deleted_at timestamp. Write both functions and the audit logging logic.

Build Prompt

Create a cookie consent banner component that appears on first visit. Use shadcn/ui Card with Switch toggles for analytics, marketing, and functional cookies. Essential is always on and disabled. Include Accept All, Reject All, and Save Preferences buttons. Store preferences in a cookie and update consent_records in Supabase via Server Action. The banner should prevent any non-essential scripts from loading until preferences are saved.

Frequently asked questions

Do I need a paid V0 plan to build privacy tools?

V0 Free works for the basic build, but Premium ($20/month) is recommended for prompt queuing to generate the consent banner, privacy center, and export endpoint more efficiently.

Does this comply with GDPR and CCPA?

This build implements the core technical requirements: consent management, data export, deletion requests, and audit logging. However, you should consult a legal professional for your specific jurisdiction and data processing activities.

How do I handle the cookie consent banner correctly?

The build uses Next.js middleware to check for a consent cookie on every request. Analytics and marketing scripts are only injected in layout.tsx when the corresponding consent flag is true. This ensures no tracking before explicit consent.

Can users download their data immediately?

Yes. The export API route aggregates all user data in real time, uploads it to Supabase Storage, and returns a signed download URL. For large datasets, consider queuing the export as a background job and notifying the user when ready.

How do I deploy this to production?

Click Share then Publish to Production in V0. Make sure SUPABASE_SERVICE_ROLE_KEY and CLERK_SECRET_KEY are set in the Vars tab without NEXT_PUBLIC_ prefix. Your cookie consent banner will work automatically on the Vercel domain.

What if I add new tables that contain personal data?

Update the data export API route to include the new table in its aggregation query. Maintain a checklist of PII-containing tables and review it whenever you add new data models.

Can RapidDev help build a custom privacy compliance toolkit?

Yes. RapidDev has built 600+ apps including privacy-compliant platforms with automated data retention, consent propagation to third parties, and multi-jurisdiction compliance. Book a free consultation to discuss your requirements.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.