[PR #2819] [CLOSED] Multi-tenant: add basic support for tenantId #21403

Closed
opened 2026-04-15 20:20:59 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/2819
Author: @astanciu
Created: 5/28/2025
Status: Closed

Base: mainHead: multi-tenancy


📝 Commits (7)

  • d8ba624 add basic support for tenantId
  • e1f8e3a Apply suggestions from code review
  • c5a180f refactor to support calls through API
  • 37406eb update core routes with multi-tenancy and tests
  • 4bf2aef remove injectIntoSession option
  • 6a1d212 pack
  • 8785008 cleanup

📊 Changes

33 files changed (+3855 additions, -17 deletions)

View changed files

.npmrc (+0 -2)
packages/better-auth/ALEX.md (+2 -0)
packages/better-auth/pack/package/LICENSE.md (+17 -0)
packages/better-auth/pack/package/README.md (+34 -0)
packages/better-auth/pack/package/package.json (+709 -0)
📝 packages/better-auth/package.json (+2 -1)
packages/better-auth/package/LICENSE.md (+17 -0)
packages/better-auth/package/README.md (+34 -0)
packages/better-auth/package/package.json (+709 -0)
packages/better-auth/src/api/async-context.ts (+7 -0)
packages/better-auth/src/api/multi-tenancy.test.ts (+63 -0)
📝 packages/better-auth/src/api/routes/account.test.ts (+321 -0)
📝 packages/better-auth/src/api/routes/email-verification.test.ts (+155 -0)
📝 packages/better-auth/src/api/routes/email-verification.ts (+6 -2)
📝 packages/better-auth/src/api/routes/reset-password.test.ts (+256 -0)
📝 packages/better-auth/src/api/routes/session-api.test.ts (+306 -0)
📝 packages/better-auth/src/api/routes/sign-in.test.ts (+239 -0)
📝 packages/better-auth/src/api/routes/sign-in.ts (+9 -2)
📝 packages/better-auth/src/api/routes/sign-out.test.ts (+123 -0)
📝 packages/better-auth/src/api/routes/sign-up.test.ts (+181 -0)

...and 13 more files

📄 Description

This PR adds very basic support for multi-tenancy.

  • adds a tenantId column to the core tables
  • updates the internal-adapter.ts methods to filter by tenantId when necessary
  • updates api/routes to support tenantId where needed
  • updates api/routes/**/*.test.ts with multi-tenancy tests

Usage:

const auth = betterAuth({
  // normal options
  
  multiTenancy: {
    enabled: true,
    
    // optional, defaults to "tenantId"
    tableFieldName: "tenant_id", 
  },
});

To pass the tenanId

From a route handler: Simply add a Request header named x-internal-tenantid before handing off better-auth

// Fastify

 fastify.route({
    method: ["GET", "POST"],
    url: "/api/*",
    async handler(request, reply) {
      // Construct request URL
      const url = new URL(request.url, `http://${request.headers.host}`);

      // Convert Fastify headers to standard Headers object
      const headers = new Headers();
      Object.entries(request.headers).forEach(([key, value]) => {
        if (value) headers.append(key, value.toString());
      });

      // Get tenantId somehow
      const tenantId = "acme"; // ...your implementation..
      headers.append("x-internal-tenantid", tenantId);

      // Create Fetch API-compatible request
      const req = new Request(url.toString(), {
        method: request.method,
        headers,
        body: request.body ? JSON.stringify(request.body) : undefined,
      });

      const response = await auth.handler(req);

      // Forward response to client
      reply.status(response.status);
      response.headers.forEach((value, key) => reply.header(key, value));
      reply.send(response.body ? await response.text() : null);
    },
  });

From .api.xxx(): Same idea, add the header to the call

headers.append("x-internal-tenantid", tenantId);
const session = await req.auth.api.getSession({
   headers,
});

Notes

  • First, this change is mostly to serve my own needs, but I'm sharing in case it helps others. If project owners are okay with this approach, I'm happy to flesh this out fully
  • The cli tool, so migrate and generate commands should be tenant aware, I only tested generate, but you will likely need to make edits to the generated file:
    • users.email - the unique constraint had to be removed. You need to manage your own unique constraint, either in the app, or adding whatever your db supports, for example UNIQUE (tenantId, email) on users
    • the new tenantId fields don't specify a foreign key, add it manually as needed.
    • needless to say, the cli tool needs to be run manually, from code, not from npx better-auth/cli, unless this PR is merged and npm package is updated

Plugins

  • I only tested the magic-link plugin for now, worked without issues, but other plugins will likely have issues. For ex, the organizations plugin will not be tenant aware. I will update this branch over time with whatever plugins I use. Happy to update them all if project owners will accept this PR

Internal

In order to pass the tenantId, which differs per-request, all the way down to internal-adapter.ts and other low level methods, there were two options:

  • pass through context. but this would require making a ton of changes throughout the app, to add ctx as a parameter everywhere
  • use AsyncLocalStorage

I chose AsyncLocalStorage, mainly because it will be easier for me to keep my fork up to date with less code changes. Happy to switch if project owners decide to move forward with this PR

The PR does an okay job with the type system, so if you enable multiTenancy in options, you should see the tenantId property on User for ex, but it's not perfect and can probably be improved.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/better-auth/better-auth/pull/2819 **Author:** [@astanciu](https://github.com/astanciu) **Created:** 5/28/2025 **Status:** ❌ Closed **Base:** `main` ← **Head:** `multi-tenancy` --- ### 📝 Commits (7) - [`d8ba624`](https://github.com/better-auth/better-auth/commit/d8ba624d711218ffce079f2e19b5803ab510c967) add basic support for tenantId - [`e1f8e3a`](https://github.com/better-auth/better-auth/commit/e1f8e3a8ac06aeb1c879ef0d60a92a19ed8197b5) Apply suggestions from code review - [`c5a180f`](https://github.com/better-auth/better-auth/commit/c5a180faa46d86fcae7fee494fba0076a3563f1f) refactor to support calls through API - [`37406eb`](https://github.com/better-auth/better-auth/commit/37406eb190a9ad39441b307c8d31d271a34a6378) update core routes with multi-tenancy and tests - [`4bf2aef`](https://github.com/better-auth/better-auth/commit/4bf2aef09b45a58a316f26158657d565c1310771) remove injectIntoSession option - [`6a1d212`](https://github.com/better-auth/better-auth/commit/6a1d21215a2add1f62e7da74c967b2b62944958a) pack - [`8785008`](https://github.com/better-auth/better-auth/commit/87850084b3612be24ad6a15ba673f5a34431f902) cleanup ### 📊 Changes **33 files changed** (+3855 additions, -17 deletions) <details> <summary>View changed files</summary> ➖ `.npmrc` (+0 -2) ➕ `packages/better-auth/ALEX.md` (+2 -0) ➕ `packages/better-auth/pack/package/LICENSE.md` (+17 -0) ➕ `packages/better-auth/pack/package/README.md` (+34 -0) ➕ `packages/better-auth/pack/package/package.json` (+709 -0) 📝 `packages/better-auth/package.json` (+2 -1) ➕ `packages/better-auth/package/LICENSE.md` (+17 -0) ➕ `packages/better-auth/package/README.md` (+34 -0) ➕ `packages/better-auth/package/package.json` (+709 -0) ➕ `packages/better-auth/src/api/async-context.ts` (+7 -0) ➕ `packages/better-auth/src/api/multi-tenancy.test.ts` (+63 -0) 📝 `packages/better-auth/src/api/routes/account.test.ts` (+321 -0) 📝 `packages/better-auth/src/api/routes/email-verification.test.ts` (+155 -0) 📝 `packages/better-auth/src/api/routes/email-verification.ts` (+6 -2) 📝 `packages/better-auth/src/api/routes/reset-password.test.ts` (+256 -0) 📝 `packages/better-auth/src/api/routes/session-api.test.ts` (+306 -0) 📝 `packages/better-auth/src/api/routes/sign-in.test.ts` (+239 -0) 📝 `packages/better-auth/src/api/routes/sign-in.ts` (+9 -2) 📝 `packages/better-auth/src/api/routes/sign-out.test.ts` (+123 -0) 📝 `packages/better-auth/src/api/routes/sign-up.test.ts` (+181 -0) _...and 13 more files_ </details> ### 📄 Description This PR adds very basic support for multi-tenancy. - adds a `tenantId` column to the core tables - updates the `internal-adapter.ts` methods to filter by `tenantId` when necessary - updates api/routes to support tenantId where needed - updates api/routes/**/*.test.ts with multi-tenancy tests ### Usage: ```ts const auth = betterAuth({ // normal options multiTenancy: { enabled: true, // optional, defaults to "tenantId" tableFieldName: "tenant_id", }, }); ``` ### To pass the tenanId **From a route handler:** Simply add a Request header named `x-internal-tenantid` before handing off better-auth ```ts // Fastify fastify.route({ method: ["GET", "POST"], url: "/api/*", async handler(request, reply) { // Construct request URL const url = new URL(request.url, `http://${request.headers.host}`); // Convert Fastify headers to standard Headers object const headers = new Headers(); Object.entries(request.headers).forEach(([key, value]) => { if (value) headers.append(key, value.toString()); }); // Get tenantId somehow const tenantId = "acme"; // ...your implementation.. headers.append("x-internal-tenantid", tenantId); // Create Fetch API-compatible request const req = new Request(url.toString(), { method: request.method, headers, body: request.body ? JSON.stringify(request.body) : undefined, }); const response = await auth.handler(req); // Forward response to client reply.status(response.status); response.headers.forEach((value, key) => reply.header(key, value)); reply.send(response.body ? await response.text() : null); }, }); ``` **From .api.xxx()**: Same idea, add the header to the call ```ts headers.append("x-internal-tenantid", tenantId); const session = await req.auth.api.getSession({ headers, }); ``` ### Notes - First, this change is mostly to serve my own needs, but I'm sharing in case it helps others. If project owners are okay with this approach, I'm happy to flesh this out fully - The `cli` tool, so `migrate` and `generate` commands should be tenant aware, I only tested `generate`, but you will likely need to make edits to the generated file: - `users.email` - the unique constraint had to be removed. **You need to manage your own unique constraint**, either in the app, or adding whatever your db supports, for example `UNIQUE (tenantId, email)` on users - the new `tenantId` fields don't specify a foreign key, add it manually as needed. - needless to say, the cli tool needs to be run manually, from code, not from `npx better-auth/cli`, unless this PR is merged and npm package is updated ### Plugins - I only tested the `magic-link` plugin for now, worked without issues, but other plugins will likely have issues. For ex, the `organizations` plugin will not be tenant aware. I will update this branch over time with whatever plugins I use. Happy to update them all if project owners will accept this PR ### Internal In order to pass the `tenantId`, which differs per-request, all the way down to `internal-adapter.ts` and other low level methods, there were two options: - pass through context. but this would require making a ton of changes throughout the app, to add ctx as a parameter everywhere - use AsyncLocalStorage I chose AsyncLocalStorage, mainly because it will be easier for me to keep my fork up to date with less code changes. Happy to switch if project owners decide to move forward with this PR The PR does an okay job with the type system, so if you enable multiTenancy in options, you should see the `tenantId` property on User for ex, but it's not perfect and can probably be improved. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-15 20:20:59 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#21403