Skip to main content
RapidDev - Software Development Agency
App Featuresauth-security22 min read

How to Add Privacy Settings to Your App — Copy-Paste Prompts Included

A privacy settings page lets users control profile visibility, notification preferences, data export, and account deletion. With Lovable, V0, or FlutterFlow and a detailed prompt, you can ship a working settings page in 1–3 hours for $0–25/month. The form UI is simple — the GDPR-compliant data export and account deletion flows are where builds typically need extra prompt iterations.

4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members

Feature spec

Beginner

Category

auth-security

Build with AI

1–3 hours with Lovable, V0, or FlutterFlow

Custom build

3–5 days custom dev

Running cost

$0–25/mo up to 10K users

Works on

WebMobile

Everything it takes to ship Privacy Settings — parts, prompts, and real costs.

TL;DR

A privacy settings page lets users control profile visibility, notification preferences, data export, and account deletion. With Lovable, V0, or FlutterFlow and a detailed prompt, you can ship a working settings page in 1–3 hours for $0–25/month. The form UI is simple — the GDPR-compliant data export and account deletion flows are where builds typically need extra prompt iterations.

What a Privacy Settings Page Actually Does

Privacy settings give users control over three things: what other people can see about them, what your app can do on their behalf (send emails, collect analytics), and what happens to their data when they leave. The visibility toggles are UI work — but they only mean something if the corresponding RLS policies on your Supabase tables actually enforce them. The data export and account deletion flows are where most AI-built privacy pages fail: the export Edge Function times out on large datasets, or the deletion only removes the profile row and leaves user data scattered across ten other tables. Building this right is a prompt engineering problem more than a coding problem.

What users consider table stakes in 2026

  • Toggle visibility of profile fields with clear labels — public (anyone), friends-only (connected users), or private (only you)
  • Granular notification preferences per channel with individual toggles: email, push, in-app, and marketing separately
  • Data export that delivers a downloadable JSON file containing all the user's data, initiated with a single button click
  • Account deletion with a multi-step confirmation — list what will be deleted, require typing 'DELETE' to confirm, apply a 14-day grace period before hard deletion
  • Cookie consent preferences accessible from settings (not just the first-visit banner) with categories: analytics, marketing, strictly necessary
  • Activity history visibility toggle so users can control whether their recent actions are visible to others

Anatomy of the Feature

Six components. The settings form UI is the easy part — the profile visibility enforcer and the data export/deletion flows are where implementations break.

Layers:UIDataBackend

Privacy settings table

Data

A user_privacy_settings table stores all preference flags per user: profile_visibility (public/friends/private), show_email boolean, show_online_status boolean, email_notifications boolean, push_notifications boolean, marketing_emails boolean, analytics_consent boolean, data_deletion_requested_at timestamptz. RLS ensures only the owner can read or write their own row.

Note: Include a data_deletion_requested_at column from day one — retrofitting a deletion scheduling mechanism onto an existing table is harder than adding the column upfront.

Settings form UI

UI

Built with react-hook-form on web or Flutter's StatefulWidget on mobile. Groups settings into labeled sections: Profile Visibility, Notifications, and Data & Privacy. Uses shadcn/ui Switch and Select components (web) or Flutter's SwitchListTile and DropdownButton (mobile). Auto-saves on change with a 500ms debounce, or presents an explicit Save button — auto-save feels more polished but requires careful error state handling if the Supabase write fails.

Note: On initial load, show skeleton loaders for all toggles while fetching settings. A flash where all toggles appear 'off' before data loads destroys user trust that their preferences were actually saved.

Data export flow

Backend

User clicks 'Export my data' → a Supabase Edge Function activates → it queries all tables containing auth.uid() data (profiles, posts, comments, activity, settings) → assembles a JSON object → returns a signed URL to a Supabase Storage object → user downloads the file. For large datasets, the function generates the file asynchronously and emails a download link via Resend when ready.

Note: Supabase Edge Functions have a 25-second timeout. For users with thousands of records, generate the export in chunks or offload to a background job triggered by a queue. Never return sensitive internal columns (admin flags, internal IDs, hashed passwords) in the export.

Account deletion flow

UI

Multi-step: clicking 'Delete my account' opens a modal that lists every category of data that will be permanently deleted. The user types 'DELETE' to confirm. On confirmation, the app sets data_deletion_requested_at = now() and sends a confirmation email. A scheduled Edge Function (or cron) checks for rows where data_deletion_requested_at is older than 14 days and executes the hard delete across all user data tables using the service role key.

Note: The 14-day grace period allows users to cancel. Store cancellation capability: a 'Cancel deletion' button that clears data_deletion_requested_at while the grace period is active. Hard delete must cascade across every table containing user data — list them explicitly in the Edge Function.

Consent management

UI

A cookie consent banner on first visit lets users choose analytics and marketing consent separately from strictly necessary cookies. Consent choices are stored in user_privacy_settings.analytics_consent and marketing_emails. On subsequent visits, the consent banner does not re-appear. From privacy settings, users can change their consent at any time. If analytics_consent is false, the PostHog or Mixpanel initialization script must not run.

Note: Consent stored only in the database (not localStorage) means it follows the user across devices. This is better UX and required for GDPR compliance — consent must be per-user, not per-device.

Profile visibility enforcer

Backend

RLS policies on the profiles table and any other user-visible data tables reference the user_privacy_settings.profile_visibility column. Public: any authenticated user can SELECT the profile. Private: only the owner. Friends-only: requires a friendship record joining the two user IDs. Without these RLS policies, the visibility toggle is cosmetic — users can bypass it via direct Supabase REST API calls.

Note: This is the most commonly skipped component. AI tools reliably generate the toggle UI and the settings save logic, but frequently omit the RLS policy that makes visibility actually enforced at the database layer. Always verify with a direct API call as a different user.

The data model

One table covers all privacy preferences per user, with a check constraint on the visibility values and a partial index for efficient deletion queue queries. Run this in the Supabase SQL editor:

schema.sql
1create table public.user_privacy_settings (
2 user_id uuid primary key references auth.users(id) on delete cascade,
3 profile_visibility text not null
4 check (profile_visibility in ('public', 'friends', 'private'))
5 default 'public',
6 show_email boolean not null default false,
7 show_online_status boolean not null default true,
8 email_notifications boolean not null default true,
9 push_notifications boolean not null default true,
10 marketing_emails boolean not null default false,
11 analytics_consent boolean not null default true,
12 data_deletion_requested_at timestamptz,
13 updated_at timestamptz not null default now()
14);
15
16alter table public.user_privacy_settings enable row level security;
17
18create policy "Users can read own privacy settings"
19 on public.user_privacy_settings for select
20 using (auth.uid() = user_id);
21
22create policy "Users can update own privacy settings"
23 on public.user_privacy_settings for update
24 using (auth.uid() = user_id);
25
26create policy "Users can insert own privacy settings"
27 on public.user_privacy_settings for insert
28 with check (auth.uid() = user_id);
29
30-- Efficient query for deletion scheduling
31create index privacy_deletion_queue_idx
32 on public.user_privacy_settings (data_deletion_requested_at)
33 where data_deletion_requested_at is not null;
34
35-- Profile visibility enforced at DB level
36create policy "Public profiles are visible to authenticated users"
37 on public.profiles for select
38 using (
39 auth.uid() = id
40 or (
41 select profile_visibility from public.user_privacy_settings
42 where user_id = profiles.id
43 ) = 'public'
44 );
45
46-- Auto-create default settings on new user
47create or replace function public.handle_new_user_privacy()
48returns trigger language plpgsql security definer set search_path = public
49as $$
50begin
51 insert into public.user_privacy_settings (user_id)
52 values (new.id)
53 on conflict (user_id) do nothing;
54 return new;
55end;
56$$;
57
58create trigger on_auth_user_created_privacy
59 after insert on auth.users
60 for each row execute procedure public.handle_new_user_privacy();

Heads up: The trigger ensures every new user has a default privacy_settings row — this prevents null reference errors when the profile visibility RLS policy tries to look up a user's visibility preference. The partial index on data_deletion_requested_at keeps the daily deletion cron query fast even with millions of users.

Build it — pick your path

Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.

Full-stack, non-technical friendlyFit for this feature:

Lovable handles settings forms, Supabase CRUD, and auto-save patterns exceptionally well. Privacy settings is one of Lovable's strongest categories. The data export Edge Function and account deletion cross-table cascade need explicit detail in the prompt.

Step by step

  1. 1Connect Lovable Cloud to provision Supabase; run the user_privacy_settings SQL schema from this page in the Supabase SQL editor if Lovable does not auto-generate it
  2. 2Paste the prompt below into Agent Mode; include the full list of tables your app uses so the deletion Edge Function cascades correctly
  3. 3After generation, navigate to the privacy settings page and toggle each setting; verify it saves to Supabase by checking the Table Editor
  4. 4Test profile visibility enforcement: open an incognito window logged in as a different user and verify private profiles are not visible via the Supabase REST API
  5. 5Test account deletion: trigger the deletion, verify data_deletion_requested_at is set, then manually call the deletion Edge Function to confirm it removes all user data
  6. 6Test data export: click 'Export my data' and verify the downloaded JSON contains all expected data categories without internal columns
Paste into Lovable
Build a privacy settings page at /settings/privacy with three sections. Section 1 — Profile Visibility: a Select dropdown with options 'Public (anyone can see)', 'Friends only (connected users)', and 'Private (only me)'; a toggle for 'Show my email on profile'; a toggle for 'Show when I am online'. Section 2 — Notifications: individual toggles for email notifications, push notifications, marketing emails, and in-app notifications. Section 3 — Data & Privacy: a 'Cookie preferences' subsection with toggles for analytics consent and marketing consent; an 'Export my data' button that calls a Supabase Edge Function which collects all rows for the current user across the profiles, posts, comments, and user_privacy_settings tables, writes a JSON file to a private Supabase Storage bucket, and returns a signed URL the user can download; an 'Account deletion' button that opens a shadcn/ui AlertDialog listing 'All your posts, comments, profile, and settings will be permanently deleted', includes a text input requiring the user to type DELETE to confirm, and on confirmation sets data_deletion_requested_at to now() in user_privacy_settings and sends a confirmation email via Resend. All settings save to the user_privacy_settings table on change (debounced 500ms). Settings load with skeleton loaders on mount. The profile_visibility setting must be enforced by an RLS policy on the profiles table: public = any authenticated user can SELECT, private = only owner. Include a 'Cancel account deletion' button visible while data_deletion_requested_at is set.

Where this path bites

  • Data export async flow needs a separate Edge Function — if user data is large, Lovable may generate a synchronous response that times out; verify the Edge Function returns a signed URL, not the data inline
  • Account deletion across multiple tables requires explicitly listing every table in the prompt — AI cannot discover your schema autonomously; missing tables = incomplete GDPR deletion
  • Friends-only visibility requires a friendship table that must exist before Lovable can write the RLS policy — build the social graph feature first or stub the policy to fall back to public

Third-party services you'll need

Privacy settings itself is Supabase CRUD — no third-party services required for the core feature. Optional services appear only for email delivery and analytics consent:

ServiceWhat it doesFree tierPaid from
Supabaseuser_privacy_settings table, RLS enforcement, Edge Functions for data export and account deletion, Storage for export file hostingFree tier (2 projects, 500MB DB, 1GB Storage, 500K Edge Function invocations/mo)Pro $25/mo (production uptime, 8GB DB, 100GB Storage)
ResendTransactional emails for data export download link notification and account deletion confirmation3,000 emails/mo free$20/mo thereafter
PostHogAnalytics tracking that must be conditionally disabled when analytics_consent is false1M events/mo free$0.00031/event thereafter (approx)
OneTrust or Cookiebot (optional)Enterprise CMP for IAB TCF 2.0 compliant cookie consent — only needed for ad-tech or enterprise compliance requirementsNo meaningful free tier$23–83/mo (approx)

Swipe the table sideways to see pricing.

What it costs to run

Drag through the tiers to see how your monthly bill scales with users — no surprises later.

Estimated monthly running cost

$0/mo

Supabase free tier covers the table, RLS, and Edge Functions easily; Resend free tier handles all confirmation and export emails; PostHog free tier covers analytics events

Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.

What breaks when AI tools build this

The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.

Profile visibility toggle saves but RLS policy is missing

Symptom: AI generates the dropdown UI and the Supabase update call for profile_visibility — but forgets to add the corresponding RLS policy on the profiles table. A user sets their profile to 'private' and assumes they are invisible. Any authenticated user calling the Supabase REST API directly still gets their profile data. The toggle is cosmetic theater without the RLS layer.

Fix: After generating privacy settings, immediately test enforcement: in a separate browser session as a different user, call the Supabase profiles API directly for the user who set their profile to private. Verify the response is empty. If data is returned, add the RLS policy: SELECT allowed when profile_visibility = 'public' OR auth.uid() = id. Add a similar check for any other tables that expose user content.

Account deletion removes one table, leaves data in nine others

Symptom: AI generates a deletion confirmation flow that calls supabase.from('profiles').delete().eq('user_id', uid). That removes the profile. But posts, comments, files, activity logs, notification preferences, and any other user-authored content remain in their tables. For GDPR Article 17 right-to-erasure compliance, incomplete deletion is a violation — not a partial implementation.

Fix: In the deletion prompt, explicitly list every table name in your database that contains user data. The Edge Function must cascade DELETE across all of them in a single transaction using the service role key. Test by inserting data in every listed table before triggering deletion, then checking each table for orphaned rows afterward.

Data export Edge Function times out for active users

Symptom: Supabase Edge Functions have a 25-second execution limit. A user with thousands of posts and comments causes the export function to time out midway, returning a 500 error instead of a download link. The user has no way to export their data — a GDPR violation and a support ticket waiting to happen.

Fix: Paginate the data collection in the Edge Function or offload to an asynchronous job. A practical pattern: the Edge Function starts the export, writes partial results to Supabase Storage in chunks, then triggers a completion email via Resend when the full file is ready. Cap each table query at 1,000 rows per page and loop with OFFSET. Never return the raw data in the HTTP response — always write to Storage and return a signed URL.

Marketing email preference not checked before sends

Symptom: The marketing_emails toggle is saved to user_privacy_settings and the UI shows it working. But every other Edge Function that sends promotional or newsletter emails was written before the privacy settings feature existed — none of them query user_privacy_settings.marketing_emails before sending. Users who opt out still receive marketing emails. This is a CAN-SPAM violation in the US and a GDPR consent violation in Europe.

Fix: In every Edge Function that sends non-transactional email, add a query: SELECT marketing_emails FROM user_privacy_settings WHERE user_id = target_user_id. If marketing_emails = false, skip the send and log the skip. Apply the same check to any analytics event tracking: if analytics_consent = false, do not call PostHog.capture() or Mixpanel.track().

Settings page shows all toggles as 'off' on initial load (SSR)

Symptom: V0-generated Next.js settings pages often read user preferences with useEffect after the component mounts. During server-side rendering and initial hydration, all toggles render in their default (off) state. Users see a flash where every notification preference appears unchecked, then the correct values snap in. This causes panic — users think their saved settings were reset.

Fix: Fetch user_privacy_settings in a Next.js Server Component and pass the values as props to the interactive form Client Component. The form renders with correct values from the first paint, with no flash. If Server Component fetch is not feasible, use React Suspense with a loading skeleton rather than rendering default values — a skeleton is less alarming than false 'off' states.

Best practices

1

Enforce profile visibility in Supabase RLS policies on the profiles table, not only in the UI — a frontend guard is bypassed by direct API calls

2

List every table containing user data in your deletion Edge Function explicitly — the cascade must be complete for GDPR Article 17 compliance

3

Show skeleton loaders for toggle states on initial load; never render default 'off' values while fetching actual preferences

4

Check marketing_emails preference in every email-sending Edge Function before dispatching non-transactional messages — add this check as part of your email helper function, not individually

5

Store consent preferences in the database per-user, not in localStorage per-device — consent follows the user across devices and is compliant with GDPR's per-person consent model

6

Implement the 14-day deletion grace period with a 'Cancel deletion' option and a visible countdown — many users trigger account deletion accidentally and benefit from the safety valve

7

Paginate the data export Edge Function for users with large datasets; a synchronous export that times out is worse than an async one that emails a download link 30 seconds later

8

Disable analytics scripts (PostHog, Mixpanel) when analytics_consent is false — not just stop calling track() — remove the initialization entirely to prevent passive data collection

When You Need Custom Privacy Settings Development

AI tools handle the standard privacy settings page well — toggles, auto-save, data export, account deletion with a grace period. Custom development is needed when privacy becomes a compliance system, not just a UI:

  • GDPR Article 17 automated deletion pipeline with Data Processing Agreement audit trail, DPO notification workflow, and 30-day response SLA tracking for formal Data Subject Requests
  • Multi-jurisdiction consent management with different legal requirements for EU users (GDPR explicit opt-in), California users (CCPA opt-out), and other regions — routing consent requirements by IP geolocation at the infrastructure level
  • IAB TCF 2.0 compliant Cookie Consent Management Platform integration for apps that use ad-tech, programmatic advertising, or third-party measurement partners
  • Privacy controls at the organization level in a B2B app — an admin setting privacy policies for all users in a workspace, not individual users managing their own preferences

RapidDev builds these features for production

Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.

Get a fixed-price quote

Frequently asked questions

Is a privacy settings page required by GDPR?

GDPR requires that users can exercise their rights: access their data, correct it, delete it, and withdraw consent for processing. A privacy settings page is the practical implementation of those rights. You do not need the exact UI described here, but you do need a way for users to request deletion and withdraw marketing consent. An MVP that lacks account deletion is technically non-compliant for EU users.

What is the difference between 'delete account' and 'deactivate account'?

Deactivation hides the account from other users and disables login, but keeps all data intact. Deletion removes all data permanently. GDPR's right-to-erasure requires deletion, not just deactivation. Offer deactivation as an alternative that users can reverse within the 14-day grace period — many users who intend to leave temporarily choose deactivation when they realize deletion is permanent.

How do I export all a user's data in a GDPR-compliant way?

Collect every row associated with the user's ID across every table in your database, serialize it to JSON, and provide a download. GDPR Article 20 (data portability) requires the format to be machine-readable. JSON qualifies. The export must be comprehensive — profile data, posts, comments, settings, activity history, and any other user-generated content. Exclude internal columns like hashed passwords, admin flags, and foreign key IDs that have no meaning outside your system.

Should I show privacy settings to new users or only when they ask?

Show a cookie consent banner to all new users on first visit — this is required in the EU and many other jurisdictions. The full privacy settings page can be accessible from account settings. Some apps also surface a privacy settings prompt during onboarding after registration. The key rule: consent must be given freely before you activate analytics or marketing tracking, not retroactively.

What should the 14-day deletion grace period actually do?

Set a data_deletion_requested_at timestamp when the user confirms deletion. During the grace period: disable the account so the user cannot log in but do not delete any data yet. Send a reminder email at day 7 reminding them of the upcoming deletion. On day 14, a scheduled Edge Function executes hard deletion across all tables. The 'Cancel deletion' flow clears data_deletion_requested_at and re-enables the account.

How do I respect email opt-outs in my transactional emails?

Transactional emails (password resets, purchase receipts, security alerts) should always send regardless of marketing preference — these are expected by users. Marketing emails (newsletters, promotional offers, product updates) must check the marketing_emails flag in user_privacy_settings before sending. Build a utility function in your email Edge Function that takes a user_id and email type, queries the preference, and skips the send if the user has opted out.

Can I use analytics on users who have not consented?

No. If a user's analytics_consent is false (or they have not been shown a consent banner yet), you cannot fire analytics events for them. This applies to PostHog, Mixpanel, Google Analytics, and any other tracking tool. The safest implementation: do not initialize the analytics SDK at all until consent is confirmed. A conditional script load is cleaner than initializing and then silencing events.

What is the minimum privacy settings UI I need for an MVP?

For a consumer-facing app with EU users: a cookie consent banner on first visit, an account deletion option, and a way to opt out of marketing emails. Those three cover the highest-risk GDPR exposure for an early-stage app. Profile visibility controls and granular notification preferences improve UX but are not legally mandated for an MVP — add them after launch based on user requests.

RapidDev

Need this feature production-ready?

RapidDev builds privacy settings into real apps — auth, database, payments — at $13K–$25K.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

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.