documentation/docs/guides-concepts/multitenancy/index.md
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:
Use Cases:
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/enterprise" package.
First, we need to install the @refinedev/enterprise and @refinedev/multitenancy packages.
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.
- import { Refine } from "@refinedev/core";
+ import { RefineEnterprise } from "@refinedev/enterprise";
export const App = () => {
return (
- <Refine>
+ <RefineEnterprise>
+ </RefineEnterprise>
- </Refine>
);
};
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.
import { RefineEnterprise } from "@refinedev/enterprise";
import { useRouterAdapter, WithTenant } from "@refinedev/multitenancy";
// ... other imports
const App = () => {
return (
<RefineEnterprise
// ... other props
multitenancyProvider={{
adapter: useRouterAdapter(),
fetchTenants: async () => {
const response = await dataProvider(API_URL).getList<ICategory>({
resource: "categories",
pagination: {
mode: "off",
},
});
const tenants = response.data;
const defaultTenant = tenants[0];
return {
tenants,
defaultTenant,
};
},
}}
>
<WithTenant
fallback={<div>Tenant not found</div>}
loadingComponent={<div>Loading...</div>}
>
</WithTenant>
</RefineEnterprise>
);
};
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.
<Tabs wrapContent={false}> <TabItem value="React Router Dom">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.
import ReactRouterRouteDefinitions from "./examples/react-router.tsx";
<ReactRouterRouteDefinitions /> </TabItem> <TabItem value="Next.js">import NextjsRouteDefinitions from "./examples/nextjs.tsx";
<NextjsRouteDefinitions /> </TabItem> <TabItem value="Remix">import RemixRouteDefinitions from "./examples/remix.tsx";
<RemixRouteDefinitions /> </TabItem> </Tabs>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/simple-rest";
const API_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/multitenancy package to easily add a tenant selector to your app.
import { TenantSelect } from "@refinedev/multitenancy/antd";
<TenantSelect />;
import { TenantSelect } from "@refinedev/multitenancy/mui";
<TenantSelect />;
Here are some examples of multi-tenant apps: