documentation/docs/enterprise-edition/multitenancy/index.md
Refine's Enterprise Edition provides built-in support for Multitenancy. This feature allows you to build applications that can serve multiple tenants with a single codebase with help of pre-built components and hooks with minimal configuration.
This package is included in Refine's Enterprise Edition. To learn more about Refine's Enterprise Edition, please contact us.
<InstallPackagesCommand args="@refinedev/enterprise @refinedev/multitenancy"># A registry with the auth token should be added for the @refinedev scope
@refinedev:registry=https://registry.refine.dev/
//registry.refine.dev/:_authToken=$NPM_TOKEN
To use the multitenancy feature, we need to wrap our application with the <RefineEnterprise /> component and provide the multitenancyProvider prop.
import { RefineEnterprise } from "@refinedev/enterprise";
import { useRouterAdapter, WithTenant } from "@refinedev/multitenancy";
type Tenant = {
id: string;
name: string;
};
// ... other imports
const App = () => {
return (
<RefineEnterprise
// ... other props
multitenancyProvider={{
adapter: useRouterAdapter(),
fetchTenants: async () => {
const response = await dataProvider("<API_URL>").getList<Tenant>({
resource: "tenants",
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>
);
};
The multitenancyProvider is a prop that accepts an object with two properties: adapter and fetchTenants.
The fetchTenants function is a crucial part of the multitenancyProvider, responsible for fetching tenant data from an API or data source and determining the default tenant for the app. The function should return an object with two properties: tenants and defaultTenant.
Adapters define where tenant information is stored. Refine offers useRouterAdapter for storing tenants in the URL and useLocalStorageAdapter for local storage.
Extracts the tenantId from the URL and updates the route when the tenant changes.
import { useRouterAdapter } from "@refinedev/multitenancy";
const multitenancyProvider = {
adapter: useRouterAdapter({
// The parameter name to use in the URL. (eg: localhost:3000/:tenantId/products)
parameterName: "tenantId",
// The tenant field to use in the route params. (eg: localhost:3000/:tenantId/products)
parameterKey: "id",
// Determines if the query string should be used to get the tenant instead of the route params. (eg: localhost:3000/products?tenantId=1)
useQueryString: false,
}),
fetchTenants: async () => {
// Fetch tenants from the API
},
};
Retrieves tenantId from local storage and updates it when the tenant changes.
import { useLocalStorageAdapter } from "@refinedev/multitenancy";
const multitenancyProvider = {
adapter: useLocalStorageAdapter({
// The key to use in the local storage. (eg: localStorage.getItem(key))
storageKey: "tenantId",
}),
fetchTenants: async () => {
// Fetch tenants from the API
},
};
The <WithTenant /> component is required to wrap your app code. It fetches tenants, handling the loading state and error state.
import { RefineEnterprise } from "@refinedev/enterprise";
import { WithTenant } from "@refinedev/multitenancy";
<WithTenant
// render a component when the tenant is not available.
fallback={<div>Tenant not found</div>}
// render a component while the tenant is loading.
loadingComponent={<div>Loading...</div>}
>
</WithTenant>;
These components allow users to select a tenant from a list of available tenants. They are automatically change the current tenant when a new tenant is selected from the list.
<Tabs wrapContent={false}> <TabItem value="Ant Design">import { TenantSelect } from "@refinedev/multitenancy/antd";
<TenantSelect
// Specifies the tenant object field to display in the select component.
optionLabel="title"
// Specifies the tenant object field to use as the value in the select component.
optionValue="id"
// Event handler for when a tenant is selected, receiving the selected tenant as an argument.
onChange={(tenant) => console.log(tenant)}
// Function to sort tenants.
sortTenants={(a, b) => a.name.localeCompare(b.name)}
/>;
import { TenantSelect } from "@refinedev/multitenancy/mui";
<TenantSelect
// Specifies the tenant object field to display in the select component.
optionLabel="title"
// Specifies the tenant object field to use as the value in the select component.
optionValue="id"
// Event handler for when a tenant is selected, receiving the selected tenant as an argument.
onChange={(tenant) => console.log(tenant)}
// Function to sort tenants.
sortTenants={(a, b) => a.name.localeCompare(b.name)}
/>;
Refine provides hooks to interact with the multitenancy feature.
The useMultitenancy hook is used to interact with the multitenancy context.
import { useMultitenancy } from "@refinedev/multitenancy";
const {
// The current tenant object.
tenant,
// The list of available tenants.
tenants,
// The loading state of the tenants.
isLoading,
// It triggers `authProvider.fetchTenants` to fetch tenants.
fetchTenants,
// It sets the current tenant. It accepts a tenant object as an argument.
setTenant,
// It deletes the current tenant.
deleteTenant,
} = useMultitenancy();
Refine automatically sends the tenantId to the data provider in the meta object. You can access the tenantId in the data provider and use it to fetch tenant-specific data.
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,
});
},
};
Here are some examples of multi-tenant apps built: