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

- Tool: App Features
- Last updated: July 2026

## 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.

## 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.

- **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.
- **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.
- **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.
- **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.
- **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.
- **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.

## 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:

```sql
create table public.user_privacy_settings (
  user_id uuid primary key references auth.users(id) on delete cascade,
  profile_visibility text not null
    check (profile_visibility in ('public', 'friends', 'private'))
    default 'public',
  show_email boolean not null default false,
  show_online_status boolean not null default true,
  email_notifications boolean not null default true,
  push_notifications boolean not null default true,
  marketing_emails boolean not null default false,
  analytics_consent boolean not null default true,
  data_deletion_requested_at timestamptz,
  updated_at timestamptz not null default now()
);

alter table public.user_privacy_settings enable row level security;

create policy "Users can read own privacy settings"
  on public.user_privacy_settings for select
  using (auth.uid() = user_id);

create policy "Users can update own privacy settings"
  on public.user_privacy_settings for update
  using (auth.uid() = user_id);

create policy "Users can insert own privacy settings"
  on public.user_privacy_settings for insert
  with check (auth.uid() = user_id);

-- Efficient query for deletion scheduling
create index privacy_deletion_queue_idx
  on public.user_privacy_settings (data_deletion_requested_at)
  where data_deletion_requested_at is not null;

-- Profile visibility enforced at DB level
create policy "Public profiles are visible to authenticated users"
  on public.profiles for select
  using (
    auth.uid() = id
    or (
      select profile_visibility from public.user_privacy_settings
      where user_id = profiles.id
    ) = 'public'
  );

-- Auto-create default settings on new user
create or replace function public.handle_new_user_privacy()
returns trigger language plpgsql security definer set search_path = public
as $$
begin
  insert into public.user_privacy_settings (user_id)
  values (new.id)
  on conflict (user_id) do nothing;
  return new;
end;
$$;

create trigger on_auth_user_created_privacy
  after insert on auth.users
  for each row execute procedure public.handle_new_user_privacy();
```

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 paths

### Lovable — fit 5/10, 1–2 hours

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.

1. Connect 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. Paste the prompt below into Agent Mode; include the full list of tables your app uses so the deletion Edge Function cascades correctly
3. After generation, navigate to the privacy settings page and toggle each setting; verify it saves to Supabase by checking the Table Editor
4. Test 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. Test 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. Test data export: click 'Export my data' and verify the downloaded JSON contains all expected data categories without internal columns

Starter prompt:

```
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.
```

Limitations:

- 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

### V0 — fit 4/10, 1–2 hours

V0 generates clean settings UI with shadcn/ui Switch and Select components. Best for Next.js apps where the settings page is one section of a larger dashboard built in V0.

1. Add environment variables in the V0 Vars panel: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, and RESEND_API_KEY
2. Run the user_privacy_settings SQL schema from this page in the Supabase SQL editor
3. Paste the prompt below into V0 and let it generate the settings page and Route Handler for data export
4. After generation, test that initial toggle states load correctly — check for the SSR localStorage flash issue described in the gotchas
5. Test profile visibility by calling the Supabase REST API as a different user to confirm private profiles are unreachable

Starter prompt:

```
Build a privacy settings page at /settings/privacy in Next.js App Router using Supabase and shadcn/ui. Page is a Server Component that fetches user_privacy_settings for the current user on render. Interactive form wrapped in a Client Component with react-hook-form. Three sections: Profile Visibility (shadcn Select with public/friends/private options), Notifications (shadcn Switch for email_notifications, push_notifications, marketing_emails), Data & Privacy (Switch for analytics_consent and marketing_emails; Export Data button; Delete Account button). Form auto-saves to Supabase with a 500ms debounce using React useTransition to avoid blocking UI. Show a skeleton loader for each toggle while settings load. Export data: POST to a Next.js Route Handler at /api/privacy/export that runs server-side with the service role key, queries the user's rows from profiles, user_privacy_settings, and any other relevant tables, writes a JSON file to Supabase Storage with a 1-hour signed URL, and returns the URL. Account deletion: shadcn AlertDialog with consequences listed, text input requiring 'DELETE', on confirm PATCH user_privacy_settings setting data_deletion_requested_at = now() and POST to /api/privacy/send-deletion-confirmation which sends email via Resend. RLS policy on profiles: SELECT allowed when profile_visibility = 'public' OR auth.uid() = id. Show 'Account scheduled for deletion on [date]' banner and Cancel Deletion button if data_deletion_requested_at is set.
```

Limitations:

- Initial render may flash incorrect toggle states if settings fetch runs client-side; use a Server Component for the initial data fetch to avoid SSR hydration mismatch
- Requires manual Supabase table setup and RLS policy; V0 does not provision these automatically
- Account deletion across multiple tables must be implemented as a server-side Route Handler or Edge Function with the service role key — never use the anon key for cascade deletion

### Flutterflow — fit 4/10, 2–3 hours

FlutterFlow handles toggle-based settings pages naturally with native SwitchListTile widgets and direct Supabase update actions. Data export requires a custom action (Dart code); account deletion cascade requires a backend function.

1. Create a Settings page in FlutterFlow with a Column layout containing a SwitchListTile for each notification preference and a DropdownButton for profile visibility
2. In the Action Editor for each toggle's onChanged event, add a Supabase Update Row action targeting user_privacy_settings filtered by user_id
3. Set page state variables for each toggle, initialized from a Supabase Query Row action on page load
4. For data export, add a Custom Action (Dart) that calls your Supabase Edge Function via HTTP and opens the returned URL in the browser
5. For account deletion, add an Alert Dialog component that requires text confirmation, then a Supabase Update Row action to set data_deletion_requested_at

Limitations:

- Data export function must be a Supabase Edge Function called via HTTP from a FlutterFlow Custom Action — no way to write the multi-table collection logic in FlutterFlow's visual action editor
- Account deletion cascade across all tables requires a backend Edge Function with the service role key; FlutterFlow's Supabase Delete Row action uses the anon key and is blocked by RLS on other users' data
- Push notification preferences in FlutterFlow integrate with Firebase Messaging — connecting this to the user_privacy_settings table requires a custom action to subscribe or unsubscribe FCM tokens

### Custom — fit 2/10, 3–5 days

Standard CRUD form — custom dev is overkill unless you need GDPR Article 17 automated deletion pipelines, multi-jurisdiction consent, or IAB TCF 2.0 compliance for a cookie management platform.

1. Build a GDPR Data Subject Request pipeline: automated deletion queue, DPA audit trail, 30-day response SLA tracking, and notification to DPO
2. Implement multi-jurisdiction consent: different consent requirements for EU users (GDPR) vs US users (CCPA), routing consent requirements by geolocation
3. Integrate an IAB TCF 2.0 compliant CMP (OneTrust or Cookiebot, $23–83/mo approx) for enterprise ad-tech consent management

Limitations:

- Not warranted for standard privacy settings — AI tools produce equivalent results in a fraction of the time for typical app requirements
- GDPR compliance for an enterprise app is a legal and operational problem as much as an engineering one; compliance tooling adds its own cost and complexity

## Gotchas

- **Profile visibility toggle saves but RLS policy is missing** — 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** — 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** — 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** — 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)** — 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

- 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
- List every table containing user data in your deletion Edge Function explicitly — the cascade must be complete for GDPR Article 17 compliance
- Show skeleton loaders for toggle states on initial load; never render default 'off' values while fetching actual preferences
- 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
- 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
- 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
- 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
- Disable analytics scripts (PostHog, Mixpanel) when analytics_consent is false — not just stop calling track() — remove the initialization entirely to prevent passive data collection

## 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.

---

Source: https://www.rapidevelopers.com/app-features/privacy-settings
© RapidDev — https://www.rapidevelopers.com/app-features/privacy-settings
