--- title: 'Build a User Management App with SolidJS' description: 'Learn how to use Supabase in your SolidJS App.' --- ![Supabase User Management example](/docs/img/user-management-demo.png) If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/solid-user-management). ## Building the app Let's start building the SolidJS app from scratch. ### Initialize a SolidJS app We can use [Degit](https://github.com/Rich-Harris/degit) to initialize an app called `supabase-solid`: ```bash npx degit solidjs/templates/ts supabase-solid cd supabase-solid ``` Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js) ```bash npm install @supabase/supabase-js ``` And finally we want to save the environment variables in a `.env`. All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys). ```bash .env VITE_SUPABASE_URL=YOUR_SUPABASE_URL VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY ``` Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database. ```js src/supabaseClient.jsx import { createClient } from '@supabase/supabase-js' const supabaseUrl = import.meta.env.VITE_SUPABASE_URL const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY export const supabase = createClient(supabaseUrl, supabaseAnonKey) ``` ### App styling (optional) An optional step is to update the CSS file `src/index.css` to make the app look nice. You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/solid-user-management/src/index.css). ### Set up a login component Let's set up a SolidJS component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords. ```jsx src/Auth.tsx import { createSignal } from 'solid-js' import { supabase } from './supabaseClient' export default function Auth() { const [loading, setLoading] = createSignal(false) const [email, setEmail] = createSignal('') const handleLogin = async (e: SubmitEvent) => { e.preventDefault() try { setLoading(true) const { error } = await supabase.auth.signInWithOtp({ email: email() }) if (error) throw error alert('Check your email for the login link!') } catch (error) { if (error instanceof Error) { alert(error.message) } } finally { setLoading(false) } } return (

Supabase + SolidJS

Sign in via magic link with your email below

setEmail(e.currentTarget.value)} />
) } ``` ### Account page After a user is signed in we can allow them to edit their profile details and manage their account. Let's create a new component for that called `Account.tsx`. ```tsx src/Account.tsx import { AuthSession } from '@supabase/supabase-js' import { Component, createEffect, createSignal } from 'solid-js' import { supabase } from './supabaseClient' interface Props { session: AuthSession } const Account: Component = ({ session }) => { const [loading, setLoading] = createSignal(true) const [username, setUsername] = createSignal(null) const [website, setWebsite] = createSignal(null) const [avatarUrl, setAvatarUrl] = createSignal(null) createEffect(() => { getProfile() }) const getProfile = async () => { try { setLoading(true) const { user } = session const { data, error, status } = await supabase .from('profiles') .select(`username, website, avatar_url`) .eq('id', user.id) .single() if (error && status !== 406) { throw error } if (data) { setUsername(data.username) setWebsite(data.website) setAvatarUrl(data.avatar_url) } } catch (error) { if (error instanceof Error) { alert(error.message) } } finally { setLoading(false) } } const updateProfile = async (e: Event) => { e.preventDefault() try { setLoading(true) const { user } = session const updates = { id: user.id, username: username(), website: website(), avatar_url: avatarUrl(), updated_at: new Date().toISOString(), } const { error } = await supabase.from('profiles').upsert(updates) if (error) { throw error } } catch (error) { if (error instanceof Error) { alert(error.message) } } finally { setLoading(false) } } return (
Email: {session.user.email}
setUsername(e.currentTarget.value)} />
setWebsite(e.currentTarget.value)} />
) } export default Account ``` ### Launch! Now that we have all the components in place, let's update `App.tsx`: ```jsx src/App.tsx import { Component, createEffect, createSignal } from 'solid-js' import { supabase } from './supabaseClient' import { AuthSession } from '@supabase/supabase-js' import Account from './Account' import Auth from './Auth' const App: Component = () => { const [session, setSession] = createSignal(null) createEffect(() => { supabase.auth.getSession().then(({ data: { session } }) => { setSession(session) }) supabase.auth.onAuthStateChange((_event, session) => { setSession(session) }) }) return (
{!session() ? : }
) } export default App ``` Once that's done, run this in a terminal window: ```bash npm start ``` And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app. ![Supabase SolidJS](/docs/img/supabase-solidjs-demo.png) ## Bonus: Profile photos Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos. ### Create an upload widget Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component: ```jsx src/Avatar.tsx import { Component, createEffect, createSignal, JSX } from 'solid-js' import { supabase } from './supabaseClient' interface Props { size: number url: string | null onUpload: (event: Event, filePath: string) => void } const Avatar: Component = (props) => { const [avatarUrl, setAvatarUrl] = createSignal(null) const [uploading, setUploading] = createSignal(false) createEffect(() => { if (props.url) downloadImage(props.url) }) const downloadImage = async (path: string) => { try { const { data, error } = await supabase.storage.from('avatars').download(path) if (error) { throw error } const url = URL.createObjectURL(data) setAvatarUrl(url) } catch (error) { if (error instanceof Error) { console.log('Error downloading image: ', error.message) } } } const uploadAvatar: JSX.EventHandler = async (event) => { try { setUploading(true) const target = event.currentTarget if (!target?.files || target.files.length === 0) { throw new Error('You must select an image to upload.') } const file = target.files[0] const fileExt = file.name.split('.').pop() const fileName = `${Math.random()}.${fileExt}` const filePath = `${fileName}` const { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file) if (uploadError) { throw uploadError } props.onUpload(event, filePath) } catch (error) { if (error instanceof Error) { alert(error.message) } } finally { setUploading(false) } } return (
{avatarUrl() ? ( {avatarUrl() ) : (
)}
) } export default Avatar ``` ### Add the new widget And then we can add the widget to the Account page: ```jsx src/Account.tsx // Import the new component import Avatar from './Avatar' // ... return (
{/* Add to the body */} { setAvatarUrl(url) updateProfile(e) }} /> {/* ... */}
) ``` At this stage you have a fully functional application!