Skip to main content
RapidDev - Software Development Agency
V0 TemplatesAuth & FormsIntermediate to customize

Team Member Invites V0 Template: Multi-User SaaS Invite Flow That Actually Ships

The Team Member Invites v0 template gives you a full invite flow UI: an Invite Form with email input and role Select, a Pending Invites Table with status badges and Resend/Revoke row actions, and a Team Members Table with active users. The prompt pack covers bulk CSV import, invite expiry countdown, Resend email delivery, Supabase token-based acceptance, and RBAC middleware — everything a multi-user SaaS needs before wiring the backend.

Auth & FormsIntermediate~10 minutes

Best for

SaaS founders building a multi-user product who need an invite flow UI before implementing the backend

Stack

Next.jsTypeScriptTailwind CSSshadcn/uireact-hook-formzod

A ready-made Team Member Invites UI you can fork, run, and customize with the prompt pack below.

What's actually inside

The honest engineer's breakdown — what the Team Member Invitestemplate does, how it's wired, and where it's opinionated.

The Team Member Invites template is the most B2B-specific auth UI template in the v0 community. It ships three interconnected sections: an Invite Form (shadcn/ui Form with email Input, role Select with Admin/Member/Viewer options, and a 'Send Invite' Button), a Pending Invites Table (shadcn/ui Table with invited email, role, sent date, and Invite Status Badge), and a Team Members Table (shadcn/ui Table of active members with avatar, name, email, role, and Remove action). The Pending Invites Table includes a Resend / Revoke DropdownMenu per row — a detail that most founders appreciate only after their first user reports a missed invite.

The form validation uses react-hook-form with a Zod schema validating email format and role as a typed enum — the schema is already wired but the error display (FormMessage) requires a prompt step to wire to the Invite Form fields. The Invite Status Badge uses shadcn/ui Badge with three color states: pending (yellow), accepted (green), expired (red). All state is currently mock data in the template — no backend calls are wired out of the box, which is the right call for a UI-first template.

The honest caveats: the template has no invite acceptance route — you need to add app/invite/[token]/page.tsx via prompt. The Remove team member action deletes the UI row but a Supabase session that is already active won't be invalidated until the user's next page load — this is expected behavior but worth knowing before demoing to investors. The NEXT_PUBLIC_SITE_URL gotcha is a production-only trap that breaks invite email links; it's covered in the gotchas section below.

Key UI components

Invite Form

shadcn/ui Form with email Input, role Select (Admin/Member/Viewer), and Send Invite Button

Pending Invites Table

shadcn/ui Table showing invited email, role, sent date, and Invite Status Badge per row

Team Members Table

shadcn/ui Table of active team members with avatar, name, email, role, and Remove action

Role Select

shadcn/ui Select with role options and inline permission description text

Invite Status Badge

shadcn/ui Badge colored by status: pending (yellow), accepted (green), expired (red)

Resend / Revoke Row Actions

shadcn/ui DropdownMenu per row in the Pending Invites Table with 'Resend invite' and 'Revoke' options

Libraries it leans on

react-hook-form

Invite form registration, email validation wiring, and form submission handling

zod

Email format validation and role enum validation for the Invite Form schema

shadcn/ui

Form, Input, Select, Table, Badge, Button, and DropdownMenu components throughout

Fork it and get it running

This template covers the UI layer of a complete invite flow. Follow these steps to fork, brand, and connect a working email + Supabase backend — you will have a demoable invite flow by the end.

1

Fork the template

Open https://v0.dev/chat/community/BtbvdBJqRve and click the "Fork" button in the top-right corner. You land in a new V0 project with the Invite Form, Pending Invites Table, and Team Members Table all visible in the preview. Sign in to v0.dev first if you are not already logged in.

You should see: A new V0 project opens with all three sections of the invite flow visible in the preview panel.

2

Brand the invite UI in Design Mode

Press Option+D to enter Design Mode. Click the team name heading and update it to your product's team name. Rename the role labels in the Role Select if your product uses different terminology — for example rename 'Viewer' to 'Read Only' or 'Admin' to 'Owner'. Update the Invite Status Badge label text and brand colors. All Design Mode changes cost zero credits.

You should see: The preview shows your team name and custom role labels without any credits consumed.

3

Add Zod validation to the Invite Form

In the V0 chat, type: "Validate the invite email as a valid email address and role as one of ['admin', 'member', 'viewer']; show an inline FormMessage error below the email field if the email is invalid; disable the Send Invite Button while the form is submitting". V0 will wire zodResolver to the useForm instance and add FormMessage output below the email Input.

Tip: Add the specific role strings your product uses in the prompt — replace admin/member/viewer with your actual role names if you changed them in Design Mode.

You should see: Submitting the Invite Form with an invalid email shows a FormMessage error; the Send Invite Button shows a loading state during submission.

4

Add Resend API key and Supabase credentials

Click the Vars panel icon in V0. Add RESEND_API_KEY (get it from resend.com → API Keys), NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY (from your Supabase project's Settings → API page), and critically — NEXT_PUBLIC_SITE_URL set to your Vercel production domain (e.g. https://yourapp.com). This last variable is used to construct the invite link in emails; getting it wrong causes broken invite links in production.

Tip: If you do not have a Supabase project yet, create one at supabase.com — the free tier covers 2 projects.

You should see: All four environment variables are saved in the Vars panel.

5

Wire the Server Action for invite email delivery

Prompt V0: "Add a Server Action in app/actions/send-invite.ts that generates a unique invite token using crypto.randomUUID(); inserts a row into a Supabase team_invites table with columns id, team_id, email, role, token, expires_at (NOW() + 7 days), status; sends an email via Resend with a link to /invite/{token}; returns the new invite row to prepend to the Pending Invites Table". Then create the team_invites table in your Supabase SQL editor with those columns.

You should see: Submitting the Invite Form sends an email to the invited address and prepends a new pending row to the Pending Invites Table.

6

Deploy and add the invite acceptance route

Click Share → Publish → "Publish to Production". After deployment, prompt V0: "Add app/invite/[token]/page.tsx that fetches the invite from Supabase using the token param, shows an 'Invite expired' message if status is not 'pending', and shows a signup form for new users that creates their account and moves them to the Team Members Table on success". Redeploy after V0 generates this page.

Tip: Add your Vercel domain to Supabase Auth → URL Configuration → Redirect URLs before testing the signup form in the acceptance page.

You should see: Clicking an invite link in the email opens /invite/[token] with a signup form; completing signup adds the user to the Team Members Table.

The prompt pack

Copy-paste these straight into v0's chat to customize the Team Member Invitestemplate. Each one names this template's own components — no generic filler.

1

Add bulk invite by CSV upload

Lets admins invite an entire team at once by uploading a CSV — a commonly requested feature for B2B SaaS onboarding of large accounts.

Quick win
Paste into v0 chat
Add a secondary 'Import CSV' Button next to the Send Invite Button in the Invite Form area. On click, show a shadcn/ui Dialog with a file input accepting .csv files. Parse the CSV using papaparse — expect one email per row with an optional second column for role. Show a preview table inside the Dialog with columns Email and Role before sending, with a 'Send all' confirmation Button. Submit all valid rows as a batch via the existing send-invite Server Action.
2

Add dynamic role permission descriptions

Helps the inviting admin choose the right role before sending — reduces the need for role changes after the invitee accepts.

Quick win
Paste into v0 chat
Below the Role Select in the Invite Form, add a dynamic text block that updates as the user changes the selected role. Use useWatch({ control, name: 'role' }) to read the current selection. Show: 'Admin — full access, can invite and remove team members' for admin, 'Member — can create and edit content, cannot manage team' for member, 'Viewer — read-only access, cannot make changes' for viewer. Style the description text as text-sm text-muted-foreground.
3

Add invite expiry countdown to the Pending Invites Table

Shows admins at a glance which invites are about to expire so they can resend before the link becomes invalid.

Medium
Paste into v0 chat
Add an 'Expires' column to the Pending Invites Table. For each pending invite row, calculate the remaining time until expiry as a relative string (e.g. 'in 6 days', 'in 2 hours') using date-fns formatDistanceToNow. If the expiry date has already passed, update the Invite Status Badge to 'expired' (red) in the UI without needing a database call. Add a shadcn/ui Tooltip on the Badge that shows the exact expiry date and time on hover.
4

Wire Resend invite email delivery with deduplication

Prevents duplicate invite rows when admins click 'Resend' — instead of inserting a new row, it extends the expiry and increments a resend counter.

Medium
Paste into v0 chat
Create app/actions/send-invite.ts as a Server Action with 'use server'. Before inserting a new invite, check for an existing pending invite with the same email and team_id: supabase.from('team_invites').select('id, resend_count').eq('email', email).eq('team_id', teamId).eq('status', 'pending').single(). If found, UPDATE the existing row — set expires_at to NOW() + 7 days and increment resend_count — and resend the email via Resend. If not found, INSERT a new row with crypto.randomUUID() as the token. Return the upserted invite row. Use RESEND_API_KEY from the Vars panel.
5

Add invite acceptance page with Supabase Auth

Closes the invite loop — the acceptance page verifies the token, creates the user account, and joins them to the team in one flow.

Advanced
Paste into v0 chat
Create app/invite/[token]/page.tsx. In the Server Component, fetch the invite: supabase.from('team_invites').select().eq('token', params.token).eq('status', 'pending').single(). If the invite is not found or expires_at is in the past, show a shadcn/ui Card with an 'Invite expired or invalid' message and a 'Request new invite' link. If valid and the user is not logged in, render a signup form using shadcn/ui Input fields for email (pre-filled from the invite) and password. On successful supabase.auth.signUp, insert a row into team_members (team_id, user_id, role from the invite), update the invite status to 'accepted', and redirect to /dashboard.
6

Add RBAC middleware to protect team admin routes

Protects admin-only routes at the server level using the team_members role column, so non-admin users get redirected before any admin UI loads.

Advanced
Paste into v0 chat
Update middleware.ts to add a role check after verifying the Supabase session. For any request to /admin paths, fetch the user's role from team_members: supabase.from('team_members').select('role').eq('user_id', user.id).eq('team_id', teamId).single() using a server Supabase client. If role !== 'admin', redirect to /dashboard?error=insufficient_permissions. On /dashboard, read the error query param and show a shadcn/ui Alert: 'You do not have permission to access that page'. Add SUPABASE_SERVICE_ROLE_KEY (server-only, no NEXT_PUBLIC_ prefix) to Vars for admin-level operations that bypass RLS.

Gotchas when you extend it

The failures people actually hit when they push this template past its defaults — and the exact fix for each.

Invite email links point to Vercel preview URL instead of production — recipient gets a broken link

Why: If NEXT_PUBLIC_SITE_URL is not set explicitly in the Vars panel, the invite token URL falls back to VERCEL_URL which is the preview deployment URL. The recipient's link points to a temporary domain that may not match the deployed production app.

Fix: Always set NEXT_PUBLIC_SITE_URL explicitly in the Vercel Dashboard → Settings → Environment Variables for the Production scope. Use your production domain (e.g. https://app.yourdomain.com) — never rely on VERCEL_URL for invite links.

Fix prompt — paste into v0
Set NEXT_PUBLIC_SITE_URL to your production domain (e.g. https://app.yourdomain.com) in the Vercel Dashboard Production environment; never rely on VERCEL_URL for invite links as it is the preview URL
`new row violates row-level security policy` on team_invites INSERT

Why: The team_invites table has RLS enabled but no INSERT policy for authenticated users. The Server Action's database call is blocked silently — no UI error appears, the invite just doesn't save.

Fix: Add an INSERT policy that checks the requesting user is an admin of the team they are inviting to: CREATE POLICY "team admins can invite" ON team_invites FOR INSERT WITH CHECK (EXISTS (SELECT 1 FROM team_members WHERE team_id = NEW.team_id AND user_id = auth.uid() AND role = 'admin')).

Fix prompt — paste into v0
Add a Supabase RLS INSERT policy on team_invites that checks the requesting user is an admin of the team they are inviting to: CREATE POLICY team_admins_can_invite ON team_invites FOR INSERT WITH CHECK (EXISTS (SELECT 1 FROM team_members WHERE team_id = NEW.team_id AND user_id = auth.uid() AND role = 'admin'))
Resend invite action creates duplicate invite rows in the Pending Invites Table

Why: The 'Resend invite' DropdownMenu action calls the Server Action without checking whether a pending invite already exists for that email + team combination. A new row is inserted each time, filling the table with duplicates.

Fix: Before inserting, check for an existing pending invite. If found, UPDATE expires_at and increment resend_count instead of INSERT. The medium-difficulty prompt in this playbook implements this deduplication logic.

Fix prompt — paste into v0
Add a conflict check before inserting a new invite: if a pending invite already exists for this email and team_id, UPDATE expires_at and increment resend_count instead of INSERT
Remove team member button updates the UI but the removed user can still access the app

Why: Deleting the team_members row removes UI access and middleware-level checks will block the user on their next navigation. However, any active Supabase session is still valid — the user won't be kicked out of pages they already have loaded.

Fix: This is expected behavior — document it in your product as 'changes take effect on next page load'. For immediate session invalidation, call supabase.auth.admin.signOut(userId) from a Server Action using SUPABASE_SERVICE_ROLE_KEY.

Fix prompt — paste into v0
For immediate session invalidation when removing a team member, call supabase.auth.admin.signOut(userId) from a Server Action using the SUPABASE_SERVICE_ROLE_KEY (server-only, no NEXT_PUBLIC_ prefix)

Template vs. custom — the honest call

A forked template gets you far, fast. Here's where it holds up, and where you'll outgrow it.

The template is enough when

  • SaaS MVP where users invite colleagues and the invite flow needs to be demoable before any backend is built
  • Admin panel for a team-based tool where simple Admin/Member/Viewer roles cover all permissions
  • B2B product demo showing multi-user capability to potential customers or investors
  • Workspace product with straightforward role-based access where one tier of team membership is enough

Go custom when

  • You need SSO-based provisioning where users are auto-added to teams via SCIM from Okta or Azure AD
  • Team hierarchies with nested orgs (org → team → workspace) requiring multi-level role inheritance
  • Seat-based billing that charges per active team member — requires Stripe subscription item updates on invite accept and member removal
  • Audit logs of every role change and team membership event for SOC 2 or enterprise compliance

RapidDev implements the full invite backend — Supabase schema, Resend emails, token verification, RBAC middleware — and delivers it production-ready in 2–3 days.

Frequently asked questions

Is the Team Member Invites template free to use?

Yes. Forking v0 community templates is free. V0 credits are only spent when you send prompts in the chat to customize the template. Design Mode changes to role labels and brand colors cost nothing.

Can I use this template commercially in a paid SaaS product?

Yes. V0 community templates are available for commercial use. The generated code is yours to deploy in any commercial product. Check v0.dev's terms of service for the complete licensing details.

Why does the invite link in the email point to the wrong URL?

The most common cause is NEXT_PUBLIC_SITE_URL not being set in the Vars panel. Without it, the invite link falls back to VERCEL_URL which is the preview deployment URL — a temporary domain that doesn't match production. Always set NEXT_PUBLIC_SITE_URL explicitly to your production domain (e.g. https://app.yourdomain.com) in the Vercel Dashboard → Settings → Environment Variables for the Production scope.

Why does my fork break in V0 preview when I add Supabase imports?

V0's esm.sh preview sandbox cannot resolve @supabase/ssr because it uses server-side Node.js APIs. This is a known limitation of the preview environment. Deploy to Vercel and test on the live URL — the app works correctly after deployment.

Does the template include the invite acceptance page?

No. The template ships the invite UI (Invite Form, Pending Invites Table, Team Members Table) but not the acceptance route. Add it using the advanced prompt in this playbook — it generates app/invite/[token]/page.tsx that verifies the token, creates the user account, and joins them to the team.

How do I prevent duplicate rows when admins click 'Resend invite'?

Use the 'Wire Resend invite email delivery with deduplication' medium prompt. It adds a conflict check before inserting: if a pending invite exists for the same email + team_id, it updates expires_at and increments resend_count instead of creating a new row.

Can RapidDev implement the full invite backend for my product?

Yes. RapidDev builds the complete invite system — Supabase schema, Resend email delivery, invite token verification, RBAC middleware — production-ready in 2–3 days.

Outgrowing the template?

RapidDev turns v0 prototypes into production apps — real auth, database, and payments — at $13K–$25K.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.