FrameworksNextjs
Supabase Forms in Next.js
Connecting React forms to Supabase Postgres with @supabase/ssr, Row Level Security, and real-time synchronization.
Preview
Supabase Postgres + RLS
Supabase User Profile Form
Connect form mutations directly to Postgres with Row Level Security (RLS) enforcement and real-time client upserts.
Interactive responsive preview
Drag handles on right and bottom to resize
Overview
Supabase pairs PostgreSQL with built-in Authentication, Row Level Security (RLS), and instant auto-generated APIs. Using forms with Supabase allows developers to enforce data validation at both the application tier (Zod) and the database tier (Postgres constraints and RLS policies).
Architecture with @supabase/ssr
When building forms in Next.js App Router with Supabase, use @supabase/ssr to ensure cookies and JWT tokens are securely forwarded during mutations:
// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
export async function createClient() {
const cookieStore = await cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
);
},
},
}
);
}Upsert Mutation in a Server Action
'use server';
import { createClient } from '@/lib/supabase/server';
import { revalidatePath } from 'next/cache';
import { z } from 'zod';
const profileSchema = z.object({
username: z.string().min(3),
fullName: z.string().min(2),
role: z.enum(['owner', 'engineer', 'designer', 'analyst']),
bio: z.string().max(160).optional(),
});
export async function updateProfile(formData: FormData) {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
throw new Error('Unauthorized');
}
const payload = profileSchema.parse(Object.fromEntries(formData));
const { data, error } = await supabase
.from('profiles')
.upsert({
id: user.id,
...payload,
updated_at: new Date().toISOString(),
})
.select()
.single();
if (error) {
return { success: false, error: error.message };
}
revalidatePath('/profile');
return { success: true, data };
}Best Practices for Supabase Forms
- Always Enable Row Level Security: Run
alter table profiles enable row level security;on all user-facing tables. - Postgres Check Constraints: Complement Zod schemas with SQL constraints (e.g.
check (char_length(username) >= 3)). - Optimistic Updates: Use React 19 optimistic hooks to update the UI instantly while Supabase resolves the mutation.