Quick path
Create a Supabase project → copy your data shape from the ShipNative export into SQL tables → enable RLS on every table → add per-user policies → set two .env values → restart the Expo dev build → verify. About 60–90 minutes.
Step 1: understand what ShipNative gave you
Open the exported project. You’ll typically see:
lib/supabase.ts— the Supabase client, reading env vars..env.example— the two values you need:EXPO_PUBLIC_SUPABASE_URLandEXPO_PUBLIC_SUPABASE_ANON_KEY.- Screen files that import from
@/lib/supabaseand query tables. - A README or comments listing the tables the app expects.
The AI has already written the client code. Your job is the server side.
Step 2: create the Supabase project
- Go to supabase.com → New project. Pick the region closest to your users.
- Wait 2–3 minutes for provisioning.
- Project Settings → API → copy the Project URL and the anon public key.
- Create a
.envin the Expo project root:
EXPO_PUBLIC_SUPABASE_URL=https://xxx.supabase.co EXPO_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci...
Never commit .env. Add to .gitignore if not already there.
Step 3: create tables
Open Supabase → SQL Editor. Run the DDL for each table referenced in your ShipNative export. Example for a habit tracker:
create table habits (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users not null,
name text not null,
emoji text,
reminder_time time,
created_at timestamptz default now()
);
create table check_ins (
id uuid primary key default gen_random_uuid(),
habit_id uuid references habits(id)
on delete cascade not null,
user_id uuid references auth.users not null,
date date not null,
created_at timestamptz default now(),
unique (habit_id, date)
);Step 4: enable row-level security
RLS is what keeps users from seeing each other’s data. The anon key in your Expo app lets anyone query — RLS is what stops them from seeing things they shouldn’t.
alter table habits enable row level security; alter table check_ins enable row level security; create policy "own habits select" on habits for select using (auth.uid() = user_id); create policy "own habits insert" on habits for insert with check (auth.uid() = user_id); create policy "own habits update" on habits for update using (auth.uid() = user_id); create policy "own habits delete" on habits for delete using (auth.uid() = user_id); -- Repeat for check_ins: create policy "own checkins select" on check_ins for select using (auth.uid() = user_id); create policy "own checkins insert" on check_ins for insert with check (auth.uid() = user_id); -- update + delete policies similarly
Step 5: auth linkage
If your ShipNative export uses Supabase Auth, you’re done —auth.uid() just works.
If it uses Clerk (or another external auth), you need to pair them via a JWT template:
- In the Clerk dashboard, create a JWT template for Supabase.
- In Supabase, add Clerk’s JWK URL as an allowed signing source.
- Pass the Clerk token to Supabase on every request via
supabase.auth.setSession()or an auth handler.
Full walkthrough in React Native Authentication 2026.
Step 6: restart and verify
- Stop the Expo dev server. Env changes require a restart.
npx expo start --clearto clear the cache.- Sign up as a new user in the app.
- Create a habit. Verify it appears in Supabase under habits with your user_id.
- Sign up as a second user. Verify they don’t see the first user’s habit.
- If both steps work, RLS is correctly configured.
Common pitfalls
- Forgetting
enable row level security. Default behavior denies all queries without policies — looks like a bug but is actually RLS working. Or (worse) if you disabled RLS in dev and forgot to re-enable, data is open. - Env vars not prefixed with
EXPO_PUBLIC_. They won’t be available in the client. - Not restarting the dev server after editing
.env. - Copy-pasting the service_role key instead of the anon key. Never put the service_role key in your app.
- Skipping the two-user test. The only way to confirm RLS is working is to log in as a different user.
You’re done
Your ShipNative app now has real data persistence, real auth, and real security. From here, everything the AI generated now operates against your live backend. Next logical step: add offline support so the app stays useful when users are on spotty connections.