examples/user-management/react-user-management/README.md
This example demonstrates how to build a user management app with React and Supabase. Users can sign up with a magic link and then update their account with public profile information, including a profile image.
This app demonstrates how to use:
getUser() method to fetch current user detailsBefore you begin, make sure you have:
Once your database has started:
profiles table - verify it in the Table EditorThe quickstart creates the following schema:
-- Create a table for Public Profiles
create table profiles (
id uuid references auth.users not null,
updated_at timestamp with time zone,
username text unique,
avatar_url text,
website text,
primary key (id),
unique (username),
constraint username_length check (char_length(username) >= 3)
);
alter table profiles enable row level security;
create policy "Public profiles are viewable by everyone."
on profiles for select using (true);
create policy "Users can insert their own profile."
on profiles for insert with check ((select auth.uid()) = id);
create policy "Users can update own profile."
on profiles for update using ((select auth.uid()) = id);
-- Set up Storage for avatars
insert into storage.buckets (id, name) values ('avatars', 'avatars');
create policy "Avatar images are publicly accessible."
on storage.objects for select using (bucket_id = 'avatars');
create policy "Anyone can upload an avatar."
on storage.objects for insert with check (bucket_id = 'avatars');
Create a .env.local file in the project root:
VITE_SUPABASE_URL=YOUR_SUPABASE_URL
VITE_SUPABASE_PUBLISHABLE_KEY=YOUR_SUPABASE_PUBLISHABLE_KEY
Replace the values with your actual Project URL and key.
npm install
npm run dev
Open your browser to http://localhost:5173 š
If you want to build this app from scratch, follow these steps:
Use Vite to create a new React app:
npm create vite@latest supabase-react -- --template react
cd supabase-react
Install the Supabase JavaScript client:
npm install @supabase/supabase-js
Create a .env.local file with your Supabase credentials (see step 4 above).
Create src/supabaseClient.js:
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabasePublishableKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
export const supabase = createClient(supabaseUrl, supabasePublishableKey)
This initializes the Supabase client with your project credentials. These variables are exposed in the browser, which is fine because Row Level Security protects your data.
Update src/index.css to style the app. You can find the full CSS file here.
src/Auth.jsx)Handles user login with Magic Links - users can sign in with their email without passwords:
import { useState } from 'react'
import { supabase } from './supabaseClient'
export default function Auth() {
const [loading, setLoading] = useState(false)
const [email, setEmail] = useState('')
const handleLogin = async (event) => {
event.preventDefault()
setLoading(true)
const { error } = await supabase.auth.signInWithOtp({ email })
if (error) {
alert(error.error_description || error.message)
} else {
alert('Check your email for the login link!')
}
setLoading(false)
}
return (
<div className="row flex flex-center">
<div className="col-6 form-widget">
<h1 className="header">Supabase + React</h1>
<p className="description">Sign in via magic link with your email below</p>
<form className="form-widget" onSubmit={handleLogin}>
<div>
<input
className="inputField"
type="email"
placeholder="Your email"
value={email}
required={true}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div>
<button className={'button block'} disabled={loading}>
{loading ? <span>Loading</span> : <span>Send magic link</span>}
</button>
</div>
</form>
</div>
</div>
)
}
src/Avatar.jsx)Manages profile photo uploads using Supabase Storage:
avatars bucketSee the full component in src/Avatar.jsx.
src/Account.jsx)Allows users to view and edit their profile:
profiles tableKey features:
user prop (not session) passed from the parent componentignore flag to prevent race conditionsupsert to handle both inserts and updatesSee the full component in src/Account.jsx.
src/App.jsx)The root component that manages authentication state:
import { useState, useEffect } from 'react'
import './App.css'
import { supabase } from './supabaseClient'
import Auth from './Auth'
import Account from './Account'
function App() {
const [user, setUser] = useState(null)
useEffect(() => {
// Get initial user on mount
supabase.auth.getUser().then(({ data: { user } }) => {
setUser(user)
})
// Listen for auth changes
supabase.auth.onAuthStateChange(async () => {
const {
data: { user },
} = await supabase.auth.getUser()
setUser(user)
})
}, [])
return (
<div className="container" style={{ padding: '50px 0 100px 0' }}>
{!user ? <Auth /> : <Account key={user.id} user={user} />}
</div>
)
}
export default App
Important: This component uses the getUser() method instead of getSession(). The getUser() method:
This project uses Postgres Row Level Security to provide fine-grained authorization:
authenticated and their UUIDThis approach keeps your data secure while maintaining a simple client-side implementation.
react-user-management/
āāā src/
ā āāā App.jsx # Main app component with auth state
ā āāā Auth.jsx # Login component with Magic Links
ā āāā Account.jsx # Profile management component
ā āāā Avatar.jsx # Avatar upload component
ā āāā supabaseClient.js # Supabase client initialization
ā āāā App.css # App styles
ā āāā index.css # Global styles
āāā .env.local # Environment variables (create this)
āāā package.json
āāā vite.config.js
Magic link not working?
Images not uploading?
avatars bucket exists in StorageProfile not updating?
profiles table existsSupabase is open source. We'd love for you to follow along and get involved at github.com/supabase/supabase