# Add login, teams and paid plans to a Lovable app, and keep Lovable Cloud or Supabase for your data

> New Lovable apps are TanStack Start apps, which means they can hold the three server routes BuildBase sign-in needs. Built and typechecked on 27 October 2026, with the Supabase wrinkle stated plainly.

- Source: https://www.buildbase.app/guides/add-login-teams-and-paid-plans-to-a-lovable-app
- Section: Guides
- Author: Dharmendra Jagodana
- Published: 2026-10-27

## In short

To add login, teams and paid plans to a Lovable app, ask Lovable for the @buildbase/sdk package, add three small server routes that swap the sign-in code for a session cookie, keep the client secret in Lovable Secrets, and leave your data in Lovable Cloud or Supabase. Teams, roles and plans are then SDK calls.

You are adding real sign-in, a workspace per customer, paid plans and the
email that goes out when a card fails to an app Lovable built for you,
without moving your data out of Lovable Cloud or your Supabase project. When
it works, a stranger opens your published address in a private window, signs
up with Google or a magic link, lands in their own workspace, invites a
colleague who gets an email, picks a plan on a test card, and the feature
they paid for appears without a reload. The client secret never leaves
Lovable's server side.

The thing that makes this possible is a change Lovable made in May 2026.
New Lovable apps are TanStack Start apps, which render on the server and can
hold server routes, instead of React + Vite apps that render only in the
browser. A server route is exactly what the sign-in code exchange needs,
because it uses a secret the browser must never see. Older Lovable apps are
still Vite, and this guide says what to do about that too.

What we ran on 27 October 2026: a current TanStack Start app with the
official Cloudflare Workers adapter, the shape Lovable's new apps use,
scaffolded with `@tanstack/cli` 0.72.0 (TanStack Start 1.169.4, Vite 8.3.5,
React 19.3.1), with `@buildbase/sdk` 0.0.90 installed from npm. The three
handlers from our Vite guide went in unchanged, as server routes. Typecheck
passed, the production build passed, a `wrangler deploy --dry-run` passed,
and the three routes answered correctly inside the Workers runtime, in
development and in the built Worker, against a stub of our platform. What
we have not run: the real hosted sign-in round trip (that needs an
organization and a registered redirect address), a browser, a real deploy,
or Lovable's own build pipeline. Where that last one matters, this guide
says so.

> **Before you start:** A Lovable project on a paid plan or the free plan, either stack. A BuildBase
> organization, and an auth client created in the console under **User
> Management → Authentication**, with its client ID and secret. Your published
> Lovable address (and your custom domain, if you have one) registered on that
> client as redirect URLs, each ending in `/sign-in`. A Stripe account, only
> when you reach plans.

> **Tip:** The handlers in this guide are the ones from [BuildBase on
> Vite](/guides/buildbase-on-vite), byte for byte. If you are comfortable in a
> terminal, that guide and the [admin-dashboard
> example](https://github.com/buildbase-app/examples/tree/main/admin-dashboard)
> are the tested originals; this page is the Lovable wrapping around them.

## 1. Find out which Lovable app you have

Open the **Code** view and look at `package.json`. If it lists
`@tanstack/react-start`, you have a TanStack Start app and every step below
applies as written. If it does not, you have an older React + Vite app, and
you need a server first.

Lovable's own docs give that test, and the date behind it: projects created
from 13 May 2026 (22 June for Enterprise workspaces) use TanStack Start;
earlier ones run on React with Vite and render in the browser. There is no
framework choice. Every project uses the same stack, and if you want a
different one, Lovable's answer is to continue outside Lovable through Git
sync.

If yours is Vite, you have two honest routes:

- **Ask Lovable to upgrade the project to TanStack Start.** Its upgrade
  page says anyone who can edit the project can start it on any plan, most
  upgrades cost 10 to 35 credits, the database is untouched, your published
  site does not change until you publish the upgraded version, and you can
  revert from version history. Do that first, test every page (the page
  warns that some browser-only libraries can break server rendering), then
  come back to step 2.
- **Export to GitHub and host somewhere with serverless functions.** Git
  sync is two-way and on every plan for github.com; it always creates a new
  private repository. Then follow the Vite guide on Vercel, Netlify or
  Cloudflare, where the three handlers are one-line function files. You
  keep building in Lovable; the sign-in server lives on the host.

Why not run the handlers as Lovable Cloud edge functions and stay on Vite?
Because the session has to be an httpOnly cookie on your app's own address,
and a function on another origin cannot set one there. A server route in
the same app can. That is the whole reason TanStack Start matters here.

What you should see: either `@tanstack/react-start` in `package.json`, or
a decision about which of the two routes above you are taking.

## 2. Register where people come back to

The hosted sign-in page sends people back to one exact address per
environment, so register them before you prompt: your published Lovable
address with `/sign-in` on the end, your custom domain with `/sign-in` if
you have one, and `http://localhost:3000/sign-in` if you ever run the
exported code locally. An address it was not told about is refused.

This is also where the most common Lovable login complaint lives. Lovable's
own sign-in docs say that when sign-in works in preview but fails on a
published or custom domain, the redirect addresses are the thing to check,
and that is true of BuildBase's hosted page as much as of the built-in one.
A wildcard can stand in for one leftmost subdomain on a domain you own, but
not on a public suffix, so `*.lovable.app` is refused; register each address
you actually use. Preview addresses change, so most people register the
published one and the custom domain and test sign-in there.

What you should see: the auth client in the console lists each address,
and the client secret has been shown once. Do not paste it into the Lovable
chat.

## 3. Ask Lovable for the package and the three routes

You do not install anything. Lovable's docs say to name the package in the
project chat, and it adds it to the dependencies; there is no terminal
step. Paste this prompt. It names the outcome, the rules, the files, and
the one thing that must never happen to the secret.

```text
Use the @buildbase/sdk npm package to add BuildBase sign-in, a workspace per customer, paid plans and lifecycle email to this app.
Keep Lovable Cloud (or my Supabase project) as the database; BuildBase is not the primary database. Do not add Clerk, Auth0, Resend or n8n. Stripe is bring-your-own; do not store card data.
Read https://www.buildbase.app/llms.txt first and do not invent endpoints. Take the three handlers from https://www.buildbase.app/guides/buildbase-on-vite unchanged and run them as TanStack Start server routes:
- src/server/auth.ts with the guide's verify, session and signout handlers, byte for byte
- src/routes/api/auth/verify.ts (POST), src/routes/api/auth/session.ts (GET) and src/routes/api/auth/signout.ts (POST): each a createFileRoute with server.handlers that calls the matching handler
- src/context/buildbase-provider.tsx from the guide, with import '@buildbase/sdk/css', wrapping <Outlet /> in the root route's component in src/routes/__root.tsx
Put VITE_BUILDBASE_ORG_ID, VITE_BUILDBASE_CLIENT_ID and VITE_BUILDBASE_REDIRECT_URL in .env. I will add BUILDBASE_CLIENT_SECRET under Secrets myself; never read it in the browser and never give it a VITE_ prefix.
If sign-in already exists in this app, stop and ask me before changing it.
```

What you should see: `@buildbase/sdk` in `package.json`, and five new or
changed files. The route that matters is the one that swaps the one-time
code from the hosted page for a session, and in TanStack Start it is three
lines around the Vite guide's handler:

```ts
// src/routes/api/auth/verify.ts
import { createFileRoute } from '@tanstack/react-router';

import { verify } from '../../../server/auth';

export const Route = createFileRoute('/api/auth/verify')({
  server: { handlers: { POST: ({ request }) => verify(request) } },
});
```

`session.ts` is the same shape with `GET: ({ request }) => session(request)`,
and `signout.ts` with `POST: () => signout()`. A Start handler receives
`{ request, params, context }` rather than a bare `Request`, which is the
only reason the wrapper exists; `src/server/auth.ts` is the guide's file.
The provider wraps the root route:

```tsx
// src/routes/__root.tsx (the component; the html shell stays as Lovable wrote it)
import { createRootRoute, Outlet } from '@tanstack/react-router';

import { BuildBaseProvider } from '../context/buildbase-provider';

export const Route = createRootRoute({
  // head and shellComponent as Lovable generated them
  component: () => (
    <BuildBaseProvider>
      <Outlet />
    </BuildBaseProvider>
  ),
});
```

Two things we checked that you do not have to: the provider renders on the
server in its loading state without touching `window`, so server rendering
does not break; and the stylesheet import is emitted as a route asset and
linked in the server-rendered HTML, so the SDK's screens are styled before
the page becomes interactive.

**Correcting potential issues.** Read the change before you accept it. The
ways Lovable tends to drift, and the line to paste back:

- It wrote `createServerFn` functions instead of server routes. Those are
  for calling from your own pages, not for the three `/api/auth/*`
  addresses the provider fetches. Paste: "Replace the server functions
  with file routes under src/routes/api/auth, each exporting a
  createFileRoute whose server.handlers entry calls the matching handler,
  as the guide shows."
- It put the secret in `.env` as `VITE_BUILDBASE_CLIENT_SECRET`, or read it
  in the provider. Paste: "BUILDBASE*CLIENT_SECRET is read only inside
  src/server/auth.ts on the server. Remove every other reference and the
  VITE* prefix."
- It kept, or added, a sign-in of its own next to BuildBase's. Paste:
  "BuildBase is the only sign-in in this app. Remove the other sign-in
  pages and the users table for them, and keep every other table."

## 4. Add the secret, and the three public values

The secret goes under **More → Settings → Secrets** as
`BUILDBASE_CLIENT_SECRET`. Lovable's docs say a secret's value can never be
viewed again in Lovable, only replaced or deleted, that it is not repeated
in chat, logs or code, and that secrets never reach the browser. That is
the right place. The three public values go in `.env`, which Lovable says
is where `VITE_`-prefixed, browser-exposed variables belong, and which it
asks you not to add to `.gitignore`.

```bash
# .env
VITE_BUILDBASE_ORG_ID=<your 24-character org id>
VITE_BUILDBASE_CLIENT_ID=<the client id>
VITE_BUILDBASE_REDIRECT_URL=https://your-app.lovable.app/sign-in
# VITE_BUILDBASE_SERVER_URL defaults to https://api.console.buildbase.app
```

Here is the part we could not confirm from Lovable's docs, so you have to:
the secrets page says secrets are injected into edge functions and
server-side integrations at runtime, and the external-hosting page says
server-function secrets are set on the host. Neither names a TanStack Start
server route. In our test the Workers runtime exposed the secret to the
route as `process.env.BUILDBASE_CLIENT_SECRET` because the Worker had
`nodejs_compat` on. Step 5 tells you whether Lovable's Worker does the same:
a sign-in that fails with "Sign-in failed" on a correct code means the
secret did not reach the route.

One more detail from the test. The provider reads the three public values
with `import.meta.env`, which Vite inlines at build time, and the handlers
read the same three names with `process.env` at request time. In a Worker
that means the values have to exist as runtime variables too, not only in
the build. If step 5 fails with a missing org id, the fix-up is below.

What you should see: after publishing, open
`https://your-app.lovable.app/api/auth/session` and get `{"sessionId":null}`.
The server half is alive before any button exists. If you get the app's
HTML back instead, the routes are not there, and either the prompt did not
land or this is still a Vite project.

**Correcting potential issues.** If the server route cannot see
`process.env` at all, or reads the public values as empty, paste: "In
src/server/auth.ts, read VITE_BUILDBASE_ORG_ID, VITE_BUILDBASE_CLIENT_ID and
VITE_BUILDBASE_SERVER_URL with import.meta.env, and read
BUILDBASE_CLIENT_SECRET from the Worker's env binding; keep every read
inside the handler, never at module scope."

## 5. Publish, then sign in from a private window

Publish. Open the published address in a private window, click the button
that calls `signIn()` from `useSaaSAuth()`, and you are sent to the hosted
sign-in page, which offers whichever methods you switched on in the
console. Sign up with a second email. You come back to `/sign-in` on your
app, signed in.

Then the two checks a demo skips. Close the browser, reopen the site: still
signed in, because the cookie is good for 30 days, which is the
[default session length](/guides/token-expired-mid-request). Sign out, then
reload: asked to sign in again, because sign-out ended the session on the
server rather than clearing a value in the browser. In our test the cookie
came back as `bb-session=...; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000`,
and sign-out answered with `Max-Age=0`.

The methods are switches, not code: email and password, a magic link, an
email code, a phone sign-in code sent through your own Twilio account,
passkeys, Google, LinkedIn, GitHub, Microsoft, Apple, your own OIDC
provider, and Ethereum or Solana wallets. Turning on Google means creating
the client in Google's console and pasting two values; nothing in the
Lovable app changes.

**Correcting potential issues.** "The redirect address is not allowed" on
the hosted page: the address in `VITE_BUILDBASE_REDIRECT_URL` and the one
registered on the client differ, usually by a trailing slash, `www`, or the
`/sign-in` path. "Sign-in failed" after a correct code: the secret did not
reach the server route; check the Secrets panel, and if it is there, use
the env-binding fix-up in step 4. Lovable's own 50-address limit applies to
its built-in sign-in, not to this one.

## 6. A workspace per customer, and the colleague they invite

Every new user lands in their own workspace on first sign-in, with no setup
step. That is on by default for new organizations, and in your app it is
one component for the switcher and the hooks the SDK already has. Prompt
Lovable for the screens; the data model is already there.

```text
Add a workspace switcher to the app header using useSaaSWorkspaces() from @buildbase/sdk/react, and a /settings page that opens the SDK's built-in workspace, members and billing screens.
Members with the admin role may invite teammates by email; everyone else sees the member list read-only.
Do not create workspace, membership or role tables in Lovable Cloud or Supabase; BuildBase holds them.
```

What you should see: a switcher listing the workspaces the signed-in person
belongs to, with **Add team** creating another if you allow it. On the
members screen, inviting an address that has no account yet sends an email
with a link; the person signs up and lands in the workspace with the role
you chose. That flow has been in the SDK since version 0.0.71, and the
reasons an invite can be refused (a seat limit, an outsider policy) come
back as named errors rather than silence.

Why not let Lovable write the tables? Because every audit of a vibe-coded
app finds the same bug: a role column checked on one page and nowhere else.
Lovable's own end-user sign-in docs describe no roles, teams or workspaces
for the people using your app, so whatever a prompt builds here is built
from scratch. The SDK's roles are enforced by the API, so a member who types
the address of an admin page gets refused by the server, not just a missing
menu item.

**Correcting potential issues.** If a member can see another workspace's
rows by changing an id in the address bar, Lovable wrote a page that reads
your database without scoping it. That is your data and your query, and the
next section is about it.

## 7. Your data stays where it is: the one honest wrinkle

Your tables stay in Lovable Cloud or in your Supabase project. The rule we
give every agent is "do not use BuildBase as the primary database", and it
applies here. The wrinkle is row-level security: policies keyed to the old
sign-in (`auth.uid()` and friends) do not know who a BuildBase user is,
because a BuildBase session is an opaque id, not a token Supabase can read.

There are two ways through, and most apps want the first.

**Read and write through server routes.** A server route or server function
checks the BuildBase session and queries your database with a server-side
credential, scoped by the BuildBase user or workspace id. This is the
pattern generated apps should use anyway, because it puts the permission
check where it cannot be forged. The server SDK makes the check one call:

```ts
// src/server/buildbase.ts
import BuildBase from '@buildbase/sdk';
import { getCookie } from '@tanstack/react-start/server';

export async function getSessionId() {
  return getCookie('bb-session') ?? null;
}

let client: ReturnType<typeof BuildBase> | undefined;

export function bb() {
  client ??= BuildBase({
    serverUrl:
      process.env.VITE_BUILDBASE_SERVER_URL ||
      'https://api.console.buildbase.app',
    orgId: process.env.VITE_BUILDBASE_ORG_ID!,
    getSessionId,
  });
  return client;
}
```

In our test a `/profile` route's server function called
`bb().users.getProfile()` with that cookie, the call reached the platform,
and a missing session redirected home. One note from the typecheck: TanStack
Start refuses to send a value it cannot serialise out of a server function,
and the SDK's user type carries free-form claims, so return the fields the
page needs (`{ email }`) rather than the whole user.

**Mint a Supabase-shaped token for browser queries.** If pages query
Supabase directly from the browser and you want the existing policies to
keep working, add one more server route: it validates the session with the
server SDK, then signs a short-lived HS256 token with your Supabase project's
JWT secret (kept in Secrets), with `sub` set to the BuildBase user id and
`role` and `aud` set to `authenticated`, and the browser passes it to
Supabase as the bearer token. `auth.uid()` then matches `sub`, so store the
BuildBase user id in your owner columns. The step-by-step is in
[the skill repository](https://github.com/buildbase-app/claude-skill/blob/main/distribution/supabase-jwt.md).
You own that route and its refresh schedule. The platform change that would
make it unnecessary, BuildBase publishing signing keys so Supabase can
verify a user token directly, is recorded as not built.

Lovable's Quick scan still runs on every publish and still checks that
tables have row rules; nothing here switches it off, and the Deep scan is
worth running once after a change this size.

## 8. Plans people can pay for, and the email when a card fails

Plans, prices and trials live in the BuildBase console and sync to your own
Stripe account at Stripe's rates, with a 0% platform fee, and no card data
touches your app. The part that generated apps get wrong is what happens
after the payment, so this step is mostly about the webhook route and the
feature flag.

In the console, create two plans, Launch and Pro, say, each with a 7-day
trial and a monthly and a yearly price. Then the webhook. It is a fourth
server route, and this is the prompt for it:

```text
Add src/routes/api/webhooks/buildbase.ts as a POST server route. Verify the signature with parseWebhookEvent from @buildbase/sdk on the raw request body, using BUILDBASE_WEBHOOK_SECRET from Secrets. Handle payment.failed with our own side effects only (flag the workspace, post to Slack); the platform sends the customer's email itself. Ignore other events.
Gate the export feature behind a BuildBase feature flag tied to the Pro plan, checked in a server route. Do not store the plan in Lovable Cloud or Supabase.
```

Point an endpoint in the console at
`https://your-app.lovable.app/api/webhooks/buildbase`, add its signing
secret under Secrets as `BUILDBASE_WEBHOOK_SECRET`, and publish.

What you should see: subscribe on a test card and the Pro feature appears
without a reload, because the flag follows the workspace's plan. Let the
test card fail on renewal and the email goes out from your sending domain,
because `payment.failed` is one of the 129 events in the catalog and the
console's notification or a workflow sends it; the route in your app is
for whatever you want to happen besides. The console's delivery log shows
each attempt, and a delivery your server was down for is retried up to
five times over about two and a half minutes.

Compare that with the built-in payments, read on 27 October 2026 from
Lovable's docs: Paddle at 5.0% plus 50 cents per transaction (a flat 10%
under $10) or your own Stripe at Stripe's rates, on Pro and up; one active
subscription per user by default, one provider per project, and once
payments are on, the project cannot be forked. Trials, discount codes and
monthly or yearly prices are there. Usage-based billing, seats and credits
are not mentioned, and neither is a plan per team. If your app sells one
subscription to one person, the built-in is simpler. If a plan belongs to a
workspace and has to change what the app does,
[entitlements rather than flags](/blog/entitlements-are-not-feature-flags)
is the ten-minute read before you prompt.

## Where Lovable's built-in is the better pick

Keep Lovable's built-in sign-in when your app has one user per account and
you need nothing after login. It is zero-config inside Lovable, which an
external SDK will never be, it ships SAML SSO and OIDC, which BuildBase does
not, so a buyer who signs in with Okta is better served by it, and its
redirect addresses are managed for you on the built-in backend. Keep the
built-in payments when you sell one subscription per person and want the
tax side handled, which is Paddle's own pitch for it.

Choose BuildBase when the question is "what may this team do now that they
paid": a workspace per customer, invites and roles enforced by the API,
plans with versions, feature flags tied to the plan, credits with a hard
stop, and the lifecycle email, from one SDK, with your data staying in
Lovable Cloud. That is the layer Lovable's docs do not reach today, and
[the launch checklist](/guides/vibe-coded-app-launch-checklist) is the
twenty-item version of why it matters before strangers arrive.

> **Key takeaway:** On Lovable the hard part was never the SDK, it was having a server. New
> Lovable apps have one. Three server routes, a secret in Secrets, and the rest
> is hooks, with your database exactly where it was.

## What this costs in credits

Lovable prices builds by the request. Its own pricing page gives examples:
0.50 credits to make a button grey, 1.20 to add authentication, 1.70 or so
for a landing page with images, and 1 credit per message in plan mode. Pro
is $25 a month for 100 credits ($21 on annual billing) and Business $50 for
100, with daily build credits on top; an upgrade from Vite to TanStack Start
is 10 to 35. All read on 27 October 2026, and Lovable changes these.

The prompt in step 3 is one request, with perhaps two fix-ups. Steps 6 and
8 are one each. The expensive thing on Lovable is not a feature, it is a
loop: about 63% of the negative reviews we read across the builders were
about credits spent on fixes that did not fix. Naming the exact files, the
exact env names and the exact guide is how you avoid the loop, and it is
why this page is long.

BuildBase's side: a Free plan at $0, then Launch at $19 a month, with a 7-day
trial and no card on the paid plans.

## What breaks, and how you can tell

**Sign-in works in preview and loops after publishing.** The published
address, or the custom domain, is not registered with `/sign-in` on the
end. Register each exact address; `*.lovable.app` is refused.

**"Sign-in failed" on a correct code.** The secret did not reach the server
route. Check Secrets, then use the env-binding fix-up in step 4.

**`/api/auth/session` returns the app's HTML.** There are no server routes,
because the project is still React + Vite, or the prompt wrote server
functions instead. Step 1, then step 3's first fix-up.

**The org id is empty on the server.** The handlers read `process.env` at
request time and the Worker only has the values from the build. Read the
three public values with `import.meta.env` in `src/server/auth.ts`.

**The secret is in the browser.** Search the published JavaScript for its
first characters. If it is there, it was prefixed `VITE_`. Rotate it in the
console, move it to Secrets, publish again.

**Rows vanish, or "permission denied", after switching sign-in.** Row rules
keyed to the old sign-in no longer match. Section 7: route the reads through
the server, or mint the token.

**The package install breaks the build.** Lovable's docs say it cannot
guarantee third-party packages. The SDK's dependencies are plain JavaScript
with no native code, which is the usual cause of a broken install, so read
the build error before assuming the package; and keep `bun.lock` in place,
because Lovable installs with Bun.

## Install

In Lovable, the install is a sentence in the chat: "Use the @buildbase/sdk
npm package". On an exported copy, Lovable's docs say to install with Bun so
the lockfile stays in sync (`bun add @buildbase/sdk`), and on any other host
it is the usual line:

```bash
npm i @buildbase/sdk
```
