Refine's architecture allows you to customize your app's data providers, access control and routing to support multi tenant features easily. This guide will provide you with a high level overview of the concepts and how to implement them. To see multi tenant app examples, check out the Examples section.
Multitenancy, especially in cloud-based systems or software solutions, refers to the ability of a software application or system to serve multiple customers (tenants) simultaneously. While these customers share the same infrastructure and codebase, their data remains separate, and each customer has exclusive access to their own data.
Benefits of Multitenancy:
Resource Sharing: Efficient use of shared infrastructure reduces costs.
Cost Savings: Lower maintenance costs passed on to customers.
Customization: Each tenant can adjust settings to their needs.
Easy Updates: System-wide updates benefit all tenants at once.
In the next sections, we'll show you how to set up multitenancy in Refine using a route-based approach. We'll use the multitenancyProvider from the "@refinedev-ee/enterprise" package. This Multi-Tenancy Provider is part of the Refine Enterprise Edition. It makes managing multi-tenant applications easier by providing tools like context, hooks, and components that are designed to handle tenants.
Then we need to change <Refine /> component to <RefineEnterprise /> in your App.tsx file. You can use same props of <Refine /> component in <RefineEnterprise /> component.
🚨 All the props of the <Refine /> component are also available in the <RefineEnterprise /> component with additional features.
After that, we need to provide the multitenancyProvider to the <RefineEnterprise /> component. The multitenancyProvider prop accepts an object with two properties: adapter and fetchTenants.
adapter: The adapter is a function that extracts the tenantId from the current route. You can use the provided useRouterAdapter or create your own custom adapter.
fetchTenants: This function is used to fetch the list of tenants. You can fetch the list of tenants from your API and return them in the format { tenants: Tenant[], defaultTenant: Tenant }.
<WithTenant />: This component is required to wrap your app code. It fetches tenants, handling the loading state and error state.
fallback: You can provide a custom fallback component to be displayed while the tenant is not available.
loadingComponent: You can provide a custom loading component to be displayed while the tenant is loading.
When you mount <RefineEnterprise /> and <WithTenant /> components and provide the multitenancyProvider prop, Refine will automatically extract the tenantId from the route and pass it to the data provider in the meta object.
We'll be using routes to determine which tenant is being selected. Once we've setup our routes, useRouterAdapter will automatically extract the tenantId from the route.
Note: In the examples below, we are only showing the route definitions. You may need additional code to implement styling and layout depending on your choice of UI library. Regardless of the UI library you choose, the routing implementation will be similar to the examples below.
React Router Dom
Next.js
Remix
import{RefineEnterprise}from"@refinedev-ee/enterprise";import{WithTenant}from"@refinedev-ee/multitenancy";importdataProviderfrom"@refinedev-ee/simple-rest";importrouterProviderfrom"@refinedev-ee/react-router-v6";import{BrowserRouter,Outlet,Routes,Route}from"react-router";import{multitenancyProvider}from"./multitenancy";import{ProductsList,ProductsCreate,ProductsShow,ProductsEdit}from"./products";exportconstApp: React.FC = ()=>{return(<BrowserRouter><RefineEnterprisemultitenancyProvider={multitenancyProvider}dataProvider={dataProvider("<API_URL>")}routerProvider={routerProvider}resources={[{name:"products",// We're prefixing the routes with `/:tenantId` to make them tenant-aware.list:"/:tenantId/products",show:"/:tenantId/products/:id",edit:"/:tenantId/products/:id/edit",create:"/:tenantId/products/create",},]}><Routes>{/* We're defining the `tenantId` as a route parameter. */}
<Routepath="/:tenantId"element={<WithTenantfallback={<div>Tenant not found</div>}loadingComponent={<div>Loading...</div>}><Outlet/></WithTenant>}><Routepath="products"element={<ProductsList/>}/><Routepath="products/create"element={<ProductsCreate/>}/><Routepath="products/:id"element={<ProductsShow/>}/><Routepath="products/:id/edit"element={<ProductsEdit/>}/></Route></Routes></RefineEnterprise></BrowserRouter>);};
Dependencies: @refinedev/core@latest
Code Files
File: App.tsx
Content: import { RefineEnterprise } from "@refinedev-ee/enterprise";
import { WithTenant } from "@refinedev-ee/multitenancy";
import dataProvider from "@refinedev-ee/simple-rest";
import routerProvider from "@refinedev-ee/react-router-v6";
import { BrowserRouter, Outlet, Routes, Route } from "react-router";
import { multitenancyProvider } from "./multitenancy";
import { ProductsList, ProductsCreate, ProductsShow, ProductsEdit } from "./products";
export const App: React.FC = () => {
return (
<BrowserRouter>
<RefineEnterprise
multitenancyProvider={multitenancyProvider}
dataProvider={dataProvider("<API_URL>")}
routerProvider={routerProvider}
resources={[
{
name: "products",
// We're prefixing the routes with `/:tenantId` to make them tenant-aware.
list: "/:tenantId/products",
show: "/:tenantId/products/:id",
edit: "/:tenantId/products/:id/edit",
create: "/:tenantId/products/create",
},
]}
>
<Routes>
{/* We're defining the `tenantId` as a route parameter. */}
<Route path="/:tenantId" element={
<WithTenant
fallback={<div>Tenant not found</div>}
loadingComponent={<div>Loading...</div>}
>
<Outlet />
</WithTenant>
}>
<Route path="products" element={<ProductsList />} />
<Route path="products/create" element={<ProductsCreate />} />
<Route path="products/:id" element={<ProductsShow />} />
<Route path="products/:id/edit" element={<ProductsEdit />} />
</Route>
</Routes>
</RefineEnterprise>
</BrowserRouter>
);
};
importReactfrom"react";import{RefineEnterprise}from"@refinedev-ee/enterprise";import{WithTenant}from"@refinedev-ee/multitenancy";importrouterProviderfrom"@refinedev-ee/nextjs-router/pages";importdataProviderfrom"@refinedev-ee/simple-rest";importtype{AppProps}from"next/app";import{multitenancyProvider}from"./providers/multitenancy";functionApp({Component,pageProps}: AppProps){return(<RefineEnterprisemultitenancyProvider={multitenancyProvider}dataProvider={dataProvider("<API_URL>")}routerProvider={routerProvider}resources={[{name:"products",// We're prefixing the routes with `/:tenantId` to make them tenant-aware.list:"/:tenantId/products",show:"/:tenantId/products/:id",edit:"/:tenantId/products/:id/edit",create:"/:tenantId/products/create",},]}><WithTenantfallback={<div>Tenant not found</div>}loadingComponent={<div>Loading...</div>}><Component{...pageProps}/></WithTenant></RefineEnterprise>);}exportdefaultApp;
Dependencies: @refinedev/core@latest
Code Files
File: /pages/_app.tsx
Content: import React from "react";
import { RefineEnterprise } from "@refinedev-ee/enterprise";
import { WithTenant } from "@refinedev-ee/multitenancy";
import routerProvider from "@refinedev-ee/nextjs-router/pages";
import dataProvider from "@refinedev-ee/simple-rest";
import type { AppProps } from "next/app";
import { multitenancyProvider } from "./providers/multitenancy";
function App({ Component, pageProps }: AppProps) {
return (
<RefineEnterprise
multitenancyProvider={multitenancyProvider}
dataProvider={dataProvider("<API_URL>")}
routerProvider={routerProvider}
resources={[
{
name: "products",
// We're prefixing the routes with `/:tenantId` to make them tenant-aware.
list: "/:tenantId/products",
show: "/:tenantId/products/:id",
edit: "/:tenantId/products/:id/edit",
create: "/:tenantId/products/create",
},
]}
>
<WithTenant
fallback={<div>Tenant not found</div>}
loadingComponent={<div>Loading...</div>}
>
<Component {...pageProps} />
</WithTenant>
</RefineEnterprise>
);
}
export default App;
importReactfrom"react";import{Links,LiveReload,Meta,Outlet,Scripts,ScrollRestoration,}from"@remix-run/react";import{RefineEnterprise}from"@refinedev-ee/enterprise";import{WithTenant}from"@refinedev-ee/multitenancy";importrouterProviderfrom"@refinedev-ee/remix-router";importdataProviderfrom"@refinedev-ee/simple-rest";import{multitenancyProvider}from"./providers/multitenancy";exportdefaultfunctionApp(){return(<htmllang="en"><head><Meta/><Links/></head><body><RefineEnterprisemultitenancyProvider={multitenancyProvider}dataProvider={dataProvider("<API_URL>")}routerProvider={routerProvider}resources={[{name:"products",// We're prefixing the routes with `/:tenantId` to make them tenant-aware.list:"/:tenantId/products",show:"/:tenantId/products/:id",edit:"/:tenantId/products/:id/edit",create:"/:tenantId/products/create",},]}><WithTenantfallback={<div>Tenant not found</div>}loadingComponent={<div>Loading...</div>}><Outlet/></WithTenant></RefineEnterprise><ScrollRestoration/><Scripts/><LiveReload/></body></html>);}
Dependencies: @refinedev/core@latest
Code Files
File: /app/root.tsx
Content: import React from "react";
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import { RefineEnterprise } from "@refinedev-ee/enterprise";
import { WithTenant } from "@refinedev-ee/multitenancy";
import routerProvider from "@refinedev-ee/remix-router";
import dataProvider from "@refinedev-ee/simple-rest";
import { multitenancyProvider } from "./providers/multitenancy";
export default function App() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<RefineEnterprise
multitenancyProvider={multitenancyProvider}
dataProvider={dataProvider("<API_URL>")}
routerProvider={routerProvider}
resources={[
{
name: "products",
// We're prefixing the routes with `/:tenantId` to make them tenant-aware.
list: "/:tenantId/products",
show: "/:tenantId/products/:id",
edit: "/:tenantId/products/:id/edit",
create: "/:tenantId/products/create",
},
]}
>
<WithTenant
fallback={<div>Tenant not found</div>}
loadingComponent={<div>Loading...</div>}
>
<Outlet />
</WithTenant>
</RefineEnterprise>
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
We'll be using the tenantId from the route to determine which tenant is being accessed. Refine will infer the tenantId from the current route and pass it to the data provider in meta. You can access the tenantId from the meta object in your data provider and use it in your API calls.
To customize the data providers, you can override each method in the data provider instance or use the swizzle command to be fully able to customize the data provider for your needs.
An example implementation of a custom getList method is shown below.
import dataProvider from"@refinedev-ee/simple-rest"; constAPI_URL="<API_URL>"; const baseDataProvider =dataProvider(API_URL); const customDataProvider ={ ...baseDataProvider, getList:async({ resource, filters =[], meta,...props })=>{ const{ tenantId }= meta; // We're adding the tenantId to the filters // Your API may have a different way of handling this if(meta?.tenantId){ filters.push({ field:"organization", operator:"eq", value: meta.tenantId, }); } // Call the base data provider's getList method with the updated filters return baseDataProvider.getList({ resource, filters, meta, ...props, }); }, };
Now we've defined our routes and data providers to use tenantId to determine which tenant is being accessed. We'll need to add a tenant selector to the UI to allow users to switch between tenants.
You can use the Tenant selector components from the @refinedev-ee/multitenancy package to easily add a tenant selector to your app.