Skip to main content
Version: 4.xx.xx

Multitenancy

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.

What is Multitenancy?

Multitenancy refers to a kind of architecture where a single instance of software runs on a server and serves multiple customers. In a multi-tenant environment, separate customers tap into the same hardware and data storage, creating a dedicated instance for each customer. Each tenant’s data is isolated and remains invisible to others, but is running on the same server.

Implementing Multitenancy in Refine

While there are many ways to implement multi tenant features, we'll implement a route based approach in the following sections. While your m implementation may differ, the concepts will be similar and the approach will be tweakable to your needs.

Configuring Multi-tenant Routes

We'll be using routes to determine which tenant is being accessed. To do this, we'll need to configure our routes to include the tenant information. For example, a products resource will have the route definition for list as /:tenantId/products.

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 above.

import { Refine } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider from "@refinedev/react-router-v6";

import { BrowserRouter, Outlet, Routes, Route } from "react-router-dom";

import { ProductsList, ProductsCreate, ProductsShow, ProductsEdit } from "./products";

export const App: React.FC = () => {
  return (
    <BrowserRouter>
      <Refine
        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={<Outlet />}>
            <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>
      </Refine>
    </BrowserRouter>
  );
};

Handling Multi-tenant Requests in Data Providers

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, pagination, filters, sorters, meta }) => {
const { tenantId } = meta;

// We're prefixing the tenantId to the resource name
// Your API may have a different way of handling this
const response = await fetch(
`${API_URL}/${tenantId}/${resource}?${stringify({
/* ... */
})}`,
);

const data = await response.json();

const total = parseInt(response.headers.get("x-total-count") || "0");

return { data, total };
},
};
Implementation Tips:

Check out the Examples below to see a full implementation of a data provider for a multi tenant app.

Adding a Tenant Selector to the UI

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.

Implementation Tips:
  • The implementation of the component may differ depending on your choice of UI library. Regardless of the UI library you choose, the implementation will be similar to the example below.

  • It's best to place the tenant selector in a layout component that wraps the routes. This way, the tenant selector will be available in all pages. If you're using Refine's layout components, it's recommended to place the tenant selector in the header or sider components.

  • Check out the Examples below to see an example implementation of a tenant selector.

import React from "react";
import { useSelect, useParsed, useGo, useGetToPath } from "@refinedev/core";

export const TenantSelector = () => {
  const {
    options,
    queryResult: { isLoading },
  } = useSelect({
    // We're using the `tenants` resource to get the list of tenants
    // Your API may have a different way to access the list of tenants
    // or you may have a specific set of tenants that you want to show
    resource: "tenants",
    optionLabel: "name",
    optionValue: "id",
  });

  // We'll use the useGo and useGetToPath hooks to navigate to the selected tenant
  const go = useGo();
  const getToPath = useGetToPath();
  // We're using the useParsed hook to get the current route information and params (tenantId)
  const {
    resource,
    action,
    id,
    params: { tenantId },
  } = useParsed();

  const onChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
    const selectedTenantId = event.target.value;

    go({
      to: getToPath({
        resource,
        action: action ?? "list",
        id,
        meta: {
          // We're passing the selected tenantId to the meta object
          // Refine will use `meta` to decorate the additional parameters when constructing the route to navigate to
          tenantId: selectedTenantId,
        },
      }),
      type: "replace",
    });
  };

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return (
    <select onChange={onChange}>
      {options.map(({ label, value }) => (
        <option key={value} value={value} selected={value === tenantId}>
          {label}
        </option>
      ))}
    </select>
  );
};

Examples

Here are two examples of multi tenant apps built with Refine. You can view the source code and run the apps in your local to understand how multi tenant features are implemented.

Strapi

Run on your local
npm create refine-app@latest -- --example multi-tenancy-strapi

Appwrite

Run on your local
npm create refine-app@latest -- --example multi-tenancy-appwrite