Features

React-admin is a rich framework that covers most of the needs of typical admin & B2B applications. It’s a great time saver that unlocks your creativity and helps you build great apps. Its unique and opinionated features make it the best choice for Single-Page Apps developers.

Awesome Developer Experience

With react-admin, developers assemble application components without having to worry about low-level details. They need less code for the same result, and they can focus on the business logic of their app.

List view without and with react-admin

We’ve crafted the API of react-admin’s components and hooks to be as intuitive as possible. The react-admin core team uses react-admin every day, and we’re always looking for ways to improve the developer experience.

React-admin provides the best-in-class documentation, demo apps, and support. Error messages are clear and actionable. Thanks to extensive TypeScript types and JSDoc, it’s easy to use react-admin in any IDE. The API is stable and breaking changes are very rare. You can debug your app with the query and form developer tools, and inspect the react-admin code right in your browser.

That probably explains why more than 3,000 new apps are published every month using react-admin.

So react-admin is not just the assembly of React Query, react-hook-form, react-router, Material UI and Emotion. It’s a framework made to speed up and facilitate the development of single-page apps in React.

Basic CRUD

Most admin and B2B apps start with a few basic screens to manipulate records:

  • A list page, including the ability to filter, paginate and sort the records
  • A read-only page, displaying the record details
  • An edition page, allowing to update the record via a form
  • A creation page

We call this type of interface a “CRUD” interface because it allows us to Create, Read, Update and Delete records.

React-admin started as an engine to generate such CRUD interfaces, and it still does it very well. Building CRUD interfaces with react-admin requires little to no effort, and it’s very easy to customize them.

The basic building blocks of a CRUD interface in react-admin are:

  • <Resource>, which defines CRUD routes for given API resource
  • <List>, which displays a list of records
  • <Edit>, which displays a form to edit a record
  • <Create>, which displays a form to create a record
  • <Show>, which displays a record in read-only mode

But of course, react-admin is not limited to CRUD interfaces. It also provides components to build tailored interfaces for your needs.

Backend Agnostic

React-admin apps run in the browser - they are “Single-Page Apps”. They rely on APIs to fetch and update data.

Which kind of API? All kinds. React-admin is backend agnostic. It doesn’t care if your API is a REST API, a GraphQL API, a SOAP API, a JSON-RPC API, or even a local API. It doesn’t care if your API is written in PHP, Python, Ruby, Java, or even JavaScript. It doesn’t care if your API is a third-party API or a home-grown API.

Backend agnostic

React-admin ships with more than 50 adapters for popular API flavors, and gives you all the tools to build your own adapter. This works thanks to a powerful abstraction layer called the Data Provider.

In a react-admin app, you don’t write API Calls. Instead, you communicate with your API using a set of high-level functions, called “Data Provider methods”. For instance, to fetch a list of posts, you call the getList() method, passing the resource name and the query parameters.

import { useState, useEffect } from 'react';
import { useDataProvider } from 'react-admin';

const PostList = () => {
    const [posts, setPosts] = useState([]);
    const [error, setError] = useState();
    const [isPending, setIsPending] = useState(true);
    const dataProvider = useDataProvider();
    useEffect(() => {
        dataProvider.getList('posts', {
            pagination: { page: 1, perPage: 10 },
            sort: { field: 'published_at', order: 'DESC' },
            filter: { status: 'published' }
        })
            .then(({ data }) => setPosts(data))
            .catch(error => setError(error))
            .finally(() => setIsPending(false));
    }, []);
    if (isPending) { return <p>Loading</p>; }
    if (error) { return <p>ERROR</p>; }
    return (
        <ul>
            {posts.map(post => (
                <li key={post.id}>{post.title}</li>
            ))}
        </ul>
    );
};

The data provider object is responsible for translating the data provider method calls into HTTP requests, and for translating the HTTP responses into data provider method results.

And by the way, using useEffect for data fetching is cumbersome. Instead, you can rely on the specialized data provider hooks, such as useGetList:

import { useGetList } from 'react-admin';

const PostList = () => {
    const { data, isPending, error } = useGetList('posts', {
        pagination: { page: 1, perPage: 10 },
        sort: { field: 'published_at', order: 'DESC' },
        filter: { status: 'published' }
    });
    if (isPending) { return <Loading />; }
    if (error) { return <p>ERROR</p>; }
    return (
        <ul>
            {data.map(post => (
                <li key={post.id}>{post.title}</li>
            ))}
        </ul>
    );
};

React-admin is also backend agnostic for authentication and authorization. Whether your API uses JWT, OAuth, a third-party provider like Auth0 or Cognito, or even Azure Active Directory, you can communicate with the authentication backend through an adapter object called the Auth Provider.

You can then use specialized hooks on your components to restrict access. For instance, to forbid anonymous access, use useAuthenticated:

import { useAuthenticated } from 'react-admin';

const MyPage = () => {
    useAuthenticated(); // redirects to login if not authenticated
    return (
        <div>
            ...
        </div>
    )
};

export default MyPage;

Relationships

APIs often expose a relational model, i.e. endpoints returning foreign keys to other endpoints. React-admin leverages relational APIs to provide smart components that display related records and components that allow editing of related records.

┌──────────────┐       ┌────────────────┐
│ books        │       │ authors        │
│--------------│       │----------------│
│ id           │   ┌───│ id             │
│ author_id    │╾──┘   │ first_name     │
│ title        │       │ last_name      │
│ published_at │       │ date_of_birth  │
└──────────────┘       └────────────────┘

For instance, <ReferenceField> displays the name of a related record, like the name of an author for a book.

const BookList = () => (
    <List>
        <Datagrid>
            <TextField source="id" />
            <TextField source="title" />
            <ReferenceField source="authorId" reference="authors" />
            <TextField source="year" />
        </Datagrid>
    </List>
);

ReferenceField

You don’t need anything fancy on the API side to support that. Simple CRUD routes for both the books and authors resources are enough. <ReferenceField> will fetch the book authors via one single API call:

GET https://my.api.url/authors?filter={ids:[1,2,3,4,5,6,7]}

<ReferenceField> is smart enough to aggregate the calls for related authors and avoid the N+1 query problem, without the need for embeddings or sub-queries on the server-side.

Similarly, reference Input components let users edit related records together with the main record. For instance, to edit the variants of a product:

const ProductEdit = () => (
    <Edit mutationMode="optimistic">
        <SimpleForm>
            <TextInput source="name" />
            <NumberInput source="price" />
            <ReferenceInput source="category_id" reference="categories" />
            <ReferenceManyInput reference="variants" target="product_id">
                <SimpleFormIterator inline>
                    <TextInput source="sku" />
                    <SelectInput source="size" choices={sizes} />
                    <SelectInput source="color" choices={colors} />
                    <NumberInput source="stock" defaultValue={0} />
                </SimpleFormIterator>
            </ReferenceManyInput>
        </SimpleForm>
    </Edit>
);

Reference Input components are also very useful to filter a view by a related record. For instance, to display the list of books of a given author:

const BookList = () => (
    <List filters={[
        <ReferenceInput source="authorId" reference="authors" alwaysOn />,
    ]}>
        <Datagrid>
            <TextField source="id" />
            <TextField source="title" />
            <ReferenceField source="authorId" reference="authors" />
            <TextField source="year" />
        </Datagrid>
    </List>
);

React-admin supports one-to-many, many-to-one, one-to-one, and many-to-many relationships. The Fields For Relationships page lists all reference fields together with their common usage. Check the following components to learn more about relationships:

Reference components are a tremendous development accelerator for complex frontend features. They also liberate the backend developers from the burden of implementing complex joins.

Not Ugly

Let’s be realistic: Many developers focus on features first and don’t have much time to spend polishing the User Interface (UI). We tend to be like that, too! The result is that quite often, admin apps are ugly. Spacing isn’t consistent, buttons aren’t in the best place, and color schemes hurt the eyes.

React-admin provides components that look pretty good out of the box, so even if you don’t spend time on the UI, it won’t look bad (unless you try hard). React-admin uses Material UI, which is a React implementation of the Material Design guidelines, the most battle-tested design system.

And if the default design isn’t good enough for you, you can easily customize it: all react-admin components are fully themeable, and you can even replace them with your own components (see Theming).

Dense UI

Modern web apps are often very visually pleasing, but they can be difficult to use due to low information density. End users need a lot of scrolling and clicking to complete moderately complex tasks.

On the other hand, the default React-admin skin is designed to be dense, giving more space to the content and less to the chrome, which allows for faster user interaction.

Dense layout

We have made many improvements to this default layout based on user feedback. In our experience, for admin apps, dashboards, and B2B apps, efficiency is more important than large margins. If this is not your use case, you can easily customize the margin and density of the UI using the theme.

And for mobile users, react-admin renders a different layout with larger margins and less information density (see Responsive).

Headless Core

React-admin components use Material UI components by default, which lets you scaffold a page in no time. As material UI supports theming, you can easily customize the look and feel of your app. But in some cases, this is not enough, and you need to use another UI library.

You can change the UI library you use with react-admin to use Ant Design, Daisy UI, Chakra UI, or even you own custom UI library. The headless logic behind react-admin components is agnostic of the UI library, and is exposed via ...Base components and controller hooks.

For instance, here a List view built with Ant Design:

List view built with Ant Design

It leverages the useListController hook:

import { useListController } from 'react-admin'; 
import { Card, Table, Button } from 'antd';
import {
  CheckCircleOutlined,
  PlusOutlined,
  EditOutlined,
} from '@ant-design/icons';
import { Link } from 'react-router-dom';

const PostList = () => {
  const { data, page, total, setPage, isPending } = useListController({
    sort: { field: 'published_at', order: 'DESC' },
    perPage: 10,
  });
  const handleTableChange = (pagination) => {
    setPage(pagination.current);
  };
  return (
    <>
      <div style={{ margin: 10, textAlign: 'right' }}>
        <Link to="/posts/create">
          <Button icon={<PlusOutlined />}>Create</Button>
        </Link>
      </div>
      <Card bodyStyle={{ padding: '0' }} loading={isPending}>
        <Table
          size="small"
          dataSource={data}
          columns={columns}
          pagination={{ current: page, pageSize: 10, total }}
          onChange={handleTableChange}
        />
      </Card>
    </>
  );
};

const columns = [
  { title: 'Id', dataIndex: 'id', key: 'id' },
  { title: 'Title', dataIndex: 'title', key: 'title' },
  {
    title: 'Publication date',
    dataIndex: 'published_at',
    key: 'pub_at',
    render: (value) => new Date(value).toLocaleDateString(),
  },
  {
    title: 'Commentable',
    dataIndex: 'commentable',
    key: 'commentable',
    render: (value) => (value ? <CheckCircleOutlined /> : null),
  },
  {
    title: 'Actions',
    render: (_, record) => (
      <Link to={`/posts/${record.id}`}>
        <Button icon={<EditOutlined />}>Edit</Button>
      </Link>
    ),
  },
];

export default PostList;

Check the following hooks to learn more about headless controllers:

And check these examples for admin panels built with react-admin but without Material UI:

Guessers & Scaffolding

When mapping a new API route to a CRUD view, adding fields one by one can be tedious. React-admin provides a set of guessers that can automatically generate a complete CRUD UI based on an API response.

For instance, the following code will generate a complete CRUD UI for the posts resource:

import { Admin, Resource, ListGuesser, EditGuesser, ShowGuesser } from 'react-admin';

const App = () => (
    <Admin dataProvider={dataProvider}>
        <Resource name="posts" list={ListGuesser} edit={EditGuesser} show={ShowGuesser} />
    </Admin>
);

Guesser components start by fetching data from the API, analyzing the shape of the response, then picking up Field and Input components that match the data type. They also dump the generated code in the console, to let you start customizing the UI.

ListGuesser

Check the following components to learn more about guessers:

Powerful Datagrid Components

Most admins need to display a list of records, letting users sort, filter, and paginate them. React-admin provides a set of components to build such lists, called “Datagrid components”.

The basic <Datagrid> component displays a list of records in a table, with a row for each record and a column for each field. It alsosupports an expand panel, a row selection checkbox, and a bulk action toolbar.

The <EditableDatagrid> component lets users edit records in place, without having to navigate to an edit form. It’s a great way to speed up data entry.

Finally, the <DatagridAG> component integrates the powerful AG Grid library to provide a rich set of features, such as cell editing, aggregation, row grouping, master detail, clipboard, pivoting, column filtering, export to excel, context menu, tree data, charting, and more.

Search & Filtering

In most admin and B2B apps, the most common task is to look for a record. React-admin includes many features to help you build a user experience that streamlines the search workflow.

Filter Button/Form Combo <FilterList> Sidebar
<StackedFilters> Dialog Global <Search>

These features rely on powerful components with an intuitive API. For instance, you can set the Filter Button/Form Combo with the <List filters> prop, using the same input components as in edition forms:

import { List, TextInput } from 'react-admin';

const postFilters = [
    <TextInput label="Search" source="q" alwaysOn />,
    <TextInput label="Title" source="title" defaultValue="Hello, World!" />,
];

export const PostList = () => (
    <List filters={postFilters}>
        {/* ... */}
    </List>
);

Check the following chapters to learn more about each search and filtering component:

Users often apply the same filters over and over again. Saved Queries let users save a combination of filters and sort parameters into a new, personal filter, that persists between sessions.

Here is an example <FilterList> sidebar with saved queries:

import { FilterList, FilterListItem, List, Datagrid } from 'react-admin';
import { Card, CardContent } from '@mui/material';

import { SavedQueriesList } from 'react-admin';

const SongFilterSidebar = () => (
    <Card>
        <CardContent>
            <SavedQueriesList />
            <FilterList label="Record Company" icon={<BusinessIcon />}>
                ...
            </FilterList>
            <FilterList label="Released" icon={<DateRangeeIcon />}>
               ...
            </FilterList>
        </CardContent>
    </Card>
);

const SongList = () => (
    <List aside={<SongFilterSidebar />}>
        <Datagrid>
            ...
        </Datagrid>
    </List>
);

Check the Saved Queries Tutorial to learn more.

Finally, react-admin offers low-level components and hooks to build your own search UI:

Check the Building A Custom Filter Tutorial to learn more.

Forms & Validation

Many admin apps let users perform complex tasks implying the update of many fields and records. To allow such complex workflows, developers must be able to build sophisticated forms, with elaborate validation rules.

React-admin offers a rich set of input components and form layouts to build forms, powered by Material UI and react-hook-form. React-admin’s form components also take care of binding the form values to the record being edited and validating the form inputs.

For instance, here is how to build a tabbed form for editing a blog post:

import {
    TabbedForm,
    Edit,
    Datagrid,
    TextField,
    DateField,
    TextInput,
    ReferenceManyField,
    NumberInput,
    DateInput,
    BooleanInput,
    EditButton
} from 'react-admin';

export const PostEdit = () => (
    <Edit>
        <TabbedForm>
            <TabbedForm.Tab label="summary">
                <TextInput label="Id" source="id" InputProps={{ disabled: true }} />
                <TextInput source="title" validate={required()} />
                <TextInput multiline source="teaser" validate={required()} />
            </TabbedForm.Tab>
            <TabbedForm.Tab label="body">
                <RichTextInput source="body" validate={required()} label={false} />
            </TabbedForm.Tab>
            <TabbedForm.Tab label="Miscellaneous">
                <TextInput label="Password (if protected post)" source="password" type="password" />
                <DateInput label="Publication date" source="published_at" />
                <NumberInput source="average_note" validate={[ number(), minValue(0) ]} />
                <BooleanInput label="Allow comments?" source="commentable" defaultValue />
                <TextInput label="Nb views" source="views" InputProps={{ disabled: true }} />
            </TabbedForm.Tab>
            <TabbedForm.Tab label="comments">
                <ReferenceManyField reference="comments" target="post_id" label={false}>
                    <Datagrid>
                        <TextField source="body" />
                        <DateField source="created_at" />
                        <EditButton />
                    </Datagrid>
                </ReferenceManyField>
            </TabbedForm.Tab>
        </TabbedForm>
    </Edit>
);

Form Layouts

React-admin offers, out of the box, several form layouts:

Input Components

Inside forms, you can use specialize input components, designed for many types of data:

Data Type Example value Input Components
String 'Lorem Ipsum' <TextInput>
Rich text <p>Lorem Ipsum</p> <RichTextInput>
Markdown # Lorem Ipsum <MarkdownInput>
Password '********' <PasswordInput>
Image URL 'https://example.com/image.png' <ImageInput>
File URL 'https://example.com/file.pdf' <FileInput>
Number 42, 1.345 <NumberInput>
Boolean true <BooleanInput>, <NullableBooleanInput>
Date '2022-10-23' <DateInput>
Time '14:30:00' <TimeInput>
Date & time '2022-10-24T19:40:28.003Z' <DateTimeInput>
Object { foo: 'bar' } All inputs (see source)
Enum 'foo' <SelectInput>, <AutocompleteInput>, <RadioButtonGroupInput>
Foreign key 42 <ReferenceInput>
Array of objects [{ item: 'jeans', qty: 3 }, { item: 'shirt', qty: 1 }] <ArrayInput>
Array of Enums ['foo', 'bar'] <SelectArrayInput>, <AutocompleteArrayInput>, <CheckboxGroupInput>, <DualListInput>
Array of foreign keys [42, 43] <ReferenceArrayInput>
Translations { en: 'Hello', fr: 'Bonjour' } <TranslatableInputs>
Related records [{ id: 42, title: 'Hello' }, { id: 43, title: 'World' }] <ReferenceManyInput>, <ReferenceManyToManyInput>, <ReferenceOneInput>

Dependent Inputs

You can build dependent inputs, using the react-hook-form’s useWatch hook. For instance, here is a CityInput that displays the cities of the selected country:

import * as React from 'react';
import { Edit, SimpleForm, SelectInput } from 'react-admin';
import { useWatch } from 'react-hook-form';

const countries = ['USA', 'UK', 'France'];
const cities = {
    USA: ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'],
    UK: ['London', 'Birmingham', 'Glasgow', 'Liverpool', 'Bristol'],
    France: ['Paris', 'Marseille', 'Lyon', 'Toulouse', 'Nice'],
};
const toChoices = items => items.map(item => ({ id: item, name: item }));
// toChoices(coutries) should be [{ id: 'USA', name: 'USA' }, ...]


const CityInput = () => {
    const country = useWatch({ name: 'country' });
    return (
        <SelectInput
            choices={country ? toChoices(cities[country]) : []}
            source="cities"
        />
    );
};

const OrderEdit = () => (
    <Edit>
        <SimpleForm>
            <SelectInput source="country" choices={toChoices(countries)} />
            <CityInput />
        </SimpleForm>
    </Edit>
);

export default OrderEdit;

Validation

React-admin ships with a powerful and versatile validation engine.

Validation example

React-admin forms support the most common validation strategies:

Here is an example of per-field validation:

import {
    required,
    minLength,
    maxLength,
    minValue,
    maxValue,
    number,
    regex,
    email,
    choices
} from 'react-admin';

const validateFirstName = [required(), minLength(2), maxLength(15)];
const validateEmail = email();
const validateAge = [number(), minValue(18)];
const validateZipCode = regex(/^\d{5}$/, 'Must be a valid Zip Code');
const validateGender = choices(['m', 'f', 'nc'], 'Please choose one of the values');

export const UserCreate = () => (
    <Create>
        <SimpleForm>
            <TextInput label="First Name" source="firstName" validate={validateFirstName} />
            <TextInput label="Email" source="email" validate={validateEmail} />
            <TextInput label="Age" source="age" validate={validateAge}/>
            <TextInput label="Zip Code" source="zip" validate={validateZipCode}/>
            <SelectInput label="Gender" source="gender" choices={[
                { id: 'm', name: 'Male' },
                { id: 'f', name: 'Female' },
                { id: 'nc', name: 'Prefer not say' },
            ]} validate={validateGender}/>
        </SimpleForm>
    </Create>
);

AutoSave

React-admin lets you build forms saving changes automatically with <AutoSave>, so that users never lose their changes.

JSON Schema Forms

Finally, you can generate entire forms based on a JSON schema, using the <JsonSchemaForm> component.

import { Edit } from "react-admin";
import { JsonSchemaForm } from "@react-admin/ra-json-schema-form";

const CustomerEdit = () => (
  <Edit>
    <JsonSchemaForm
      schema={{
        type: "object",
        properties: {
          id: { type: "number" },
          first_name: { type: "string", title: "First name" },
          last_name: { type: "string", minLength: 3 },
          dob: { type: "string", format: "date" },
          sex: { type: "string", enum: ["male", "female"] },
          employer_id: { type: "number" },
          occupations: {
            type: "array",
            items: {
              type: "object",
              properties: {
                name: { type: "string" },
                from: { type: "string", format: "date" },
                to: { type: "string", format: "date" },
              },
            },
          },
        },
        required: ["id", "last_name", "employer_id"],
      }}
      uiSchema={{
        id: { "ui:disabled": true },
        employer_id: {
          "ui:widget": "reference",
          "ui:options": {
            reference: "employers",
            optionText: "name",
          },
        },
      }}
      onChange={(change) =>
        process.env.NODE_ENV !== "test" && console.log("changed", change)
      }
      onError={(error) =>
        process.env.NODE_ENV !== "test" && console.log("error", error)
      }
    />
  </Edit>
);

JsonSchemaForm

And if you want something super custom that react-admin doesn’t support out of the box, you can always use react-hook-form directly.

AI-Powered Components

React-admin leverages recent breakthroughs in Artificial Intelligence (AI) to boost user productivity.

One example is <PredictiveTextInput>, which suggests completion for the input value, using your favorite AI backend. Users can accept the completion by pressing the Tab key. It’s like Intellisense or Copilot for your forms.

Use <PredictiveTextInput> in any react-admin form:

import { Edit, SimpleForm, TextInput } from 'react-admin';
import { PredictiveTextInput } from '@react-admin/ra-ai';

const PersonEdit = () => (
    <Edit>
        <SimpleForm>
            <TextInput source="firstName" />
            <TextInput source="lastName" />
            <TextInput source="company" />
            <PredictiveTextInput source="email" />
            <PredictiveTextInput source="website" />
            <PredictiveTextInput source="bio" multiline />
        </SimpleForm>
    </Edit>
);

You can also use the <SmartRichTextInput> component, which lets users edit HTML documents in WYSIWYG with superpowers:

Fast

React-admin takes advantage of the Single-Page-Application architecture, implementing various performance optimizations that make react-admin apps incredibly fast by default.

  • Non-Blocking Data Fetching: Instead of waiting for API data before starting to render the UI, React-admin initiates the rendering process immediately. This strategy ensures a snappy application where user interactions receive instant feedback, outperforming Server-side Rendered apps by eliminating waiting times for server responses.
  • Stale While Revalidate: This technique allows pages to display data from previous requests while newer data is being fetched. In most instances, the fresh data remains the same (e.g., when revisiting a list page), ensuring users won’t notice any delays due to network requests.
  • Local Database Mirror: React-admin populates its internal cache with individual records fetched using dataProvider.getList(). When a user views a specific record, React-admin leverages its internal database to pre-fill the dataProvider.getOne() query response. As a result, record details are displayed instantaneously, without any wait time for server responses.
  • Optimistic Updates: When a user edits a record and hits the “Save” button, React-admin immediately updates its local database and displays the revised data, prior to sending the update query to the server. The resulting UI changes are instant - no server response wait time required. The same logic applies to record deletions.
  • Query Deduplication: React-admin identifies instances where multiple components on a page call the same data provider query for identical data. In such cases, it ensures only a single call to the data provider is made.
  • Query Aggregation: React-admin intercepts all calls to dataProvider.getOne() for related data when a <ReferenceField> is used in a list. It aggregates and deduplicates the requested ids, and issues a single dataProvider.getMany() request. This technique effectively addresses the n+1 query problem, reduces server queries, and accelerates list view rendering.
  • Opt-In Query Cache: React-admin provides an option to prevent refetching an API endpoint for a specified duration, which can be used when you’re confident that the API response will remain consistent over time.

Undo

When users submit a form, or delete a record, the UI reflects their change immediately. They also see a confirmation message for the change, containing an “Undo” button. If they click on it before the confirmation slides out (the default delay is 5s), react-admin reverts to the previous state and cancels the call to the data provider.

This undo feature is enabled by default, and requires no particular setup on the server side. In fact, react-admin delays the call to the data provider for mutations, to give users a “grace” period. That’s why the actual call to dataProvider.update() occurs 5 seconds after the user submits an update form - even though the UI reflects the changes immediately.

You can disable this feature page by page, by choosing a different mutationMode.

Roles & Permissions

It’s the server’s responsibility to check that an action is allowed for a given user, and to filter the content based on user permissions. But roles and permissions are also a client-side concern, because you want to hide or disable actions based on the user’s role. For example, you may not want to show a “Delete” button for users who don’t have the admin role.

React-admin lets you customize the user interface based on a simple set of rules, and to define the permissions for each role in a centralized place. Whether you need to have custom pages for specific roles, or to change the props of a component based on the user’s role, react-admin lets you do it. This feature uses the same adapter approach as for the dataProvider, which means you can use any authentication backend you want.

You can define permissions for pages, fields, buttons, etc. Roles and permissions are managed by the authProvider, which means you can use any data source you want (including an ActiveDirectory server).

The above demo uses the following set of permissions:

const roles = {
    accountant: [
        { action: ['list', 'show'], resource: 'products' },
        { action: 'read', resource: 'products.*' },
        { type: 'deny', action: 'read', resource: 'products.description' },
        { action: 'list', resource: 'categories' },
        { action: 'read', resource: 'categories.*' },
        { action: ['list', 'show'], resource: 'customers' },
        { action: 'read', resource: 'customers.*' },
        { action: '*', resource: 'invoices' },
    ],
    contentEditor: [
        {
            action: ['list', 'create', 'edit', 'delete', 'export'],
            resource: 'products',
        },
        { action: 'read', resource: 'products.*' },
        { type: 'deny', action: 'read', resource: 'products.stock' },
        { type: 'deny', action: 'read', resource: 'products.sales' },
        { action: 'write', resource: 'products.*' },
        { type: 'deny', action: 'write', resource: 'products.stock' },
        { type: 'deny', action: 'write', resource: 'products.sales' },
        { action: 'list', resource: 'categories' },
        { action: ['list', 'edit'], resource: 'customers' },
        { action: ['list', 'edit'], resource: 'reviews' },
    ],
    stockManager: [
        { action: ['list', 'edit', 'export'], resource: 'products' },
        { action: 'read', resource: 'products.*' },
        {
            type: 'deny',
            action: 'read',
            resource: 'products.description',
        },
        { action: 'write', resource: 'products.stock' },
        { action: 'write', resource: 'products.sales' },
        { action: 'list', resource: 'categories' },
    ],
    administrator: [{ action: '*', resource: '*' }],
};

To learn more about authentication, roles, and permissions, check out the following pages:

Revisions & Versioning

React-admin lets users track the changes made to any record. They can see the history of revisions, compare differences between any two versions, and revert to a previous state if needed.

In detail, revision tracking lets you:

  • Prevent data loss with robust version control
  • Enhance transparency with detailed change logs
  • Uncover insights with the ‘diff’ feature, a powerful tool for comparing versions
  • Boost confidence in making changes with easy rollback options

These features are available through the following components:

Audit Log

Most admin and B2B apps require that user actions are recorded for audit purposes. React-admin provides templates for displaying such audit logs, and helpers to automatically record user actions.

import { useGetList } from "react-admin";
import { Timeline } from "@react-admin/ra-audit-log";

const Dashboard = () => {
  const { data, isPending } = useGetList(
    "events",
    { page: 1, perPage: 25 },
    { field: "date", order: "desc" }
  );

  return <Timeline isLoading={isPending} records={data} />;
};

The Audit Log features let you:

  • Comply with data and action traceability regulations
  • Troubleshoot and resolve problems with a clear action trail
  • Boost security by detecting unusual activity
  • Improve accountability with detailed action records
  • Monitor user activity with an aggregated timeline

These features are available through the following components:

  • <Timeline> shows a list of all recent changes in the admin. It’s a great component for dashboards.
  • <RecordTimeline> shows a list of all recent changes for a given record, usually embedded in a <Show> or <Edit> view.
  • <EventList> is a ready-to-use List component for navigating in your admin history, complete with filters and pagination.

And you can use the addEventsForMutations helper to record user actions:

import { addEventsForMutations } from "@react-admin/ra-audit-log";
import simpleRestProvider from "ra-data-simple-rest";
import authProvider from "./authProvider";

const dataProvider = addEventsForMutations(
  simpleRestProvider("http://path.to.my.api/"),
  authProvider
);

Calendar

If your app needs to display events, appointments, time intervals, or any other kind of time-based data, you can use the <Calendar> component.

import { Calendar, getFilterValuesFromInterval } from '@react-admin/ra-calendar';
import { List } from 'react-admin';

const EventList = () => (
    <List
        filterDefaultValues={getFilterValuesFromInterval()}
        perPage={1000}
        pagination={false}
    >
        <Calendar />
    </List>
);

The user interface offers everything you expect:

  • month, week, and day views
  • list view
  • drag and resize events
  • whole-day events
  • creating an event by clicking on the calendar
  • edition of the event title, and metadata
  • events spanning multiple days
  • recurring events
  • background events
  • theming
  • locales and timezones
  • resource time grid (e.g. rooms) (requires an additional license from Full Calendar)

Check the following components for more details:

Tree View

To manage directories, categories, and any other hierarchical data, admins often rely on tree structures. Navigating and editing tree structures can be tricky, but React-admin provides a set of components to make it easy.

import { Create, Edit, SimpleForm, TextInput } from 'react-admin';
import { CreateNode, EditNode, EditNodeToolbar, TreeWithDetails } from '@react-admin/ra-tree';

// a Create view for a tree uses <CreateNode> instead of the standard <Create>
const CategoriesCreate = () => (
    <CreateNode>
        <SimpleForm>
            <TextInput source="name" />
        </SimpleForm>
    </CreateNode>
);

// an Edit view for a tree uses <EditNode> instead of the standard <Edit>
const CategoriesEdit = () => (
    <EditNode>
        <SimpleForm toolbar={<EditNodeToolbar />}>
            <TextInput source="title" />
        </SimpleForm>
    </EditNode>
);

// a List view for a tree uses <TreeWithDetails>
export const CategoriesList = () => (
    <TreeWithDetails create={CategoriesCreate} edit={CategoriesEdit} />
);

Check out the following components for displaying hierarchical data:

  • <TreeWithDetails>: A list view for tree structures, with a details panel.
  • <TreeInput>: An input component for tree structures.
  • <Tree>: A list view for tree structures, with a Material UI skin.

Application Building Blocks

A UI kit like Material UI provides basic building blocks like a button, a form, a table, etc. React-admin goes one level higher and provides a set of application components specifically designed for building admin and B2B applications.

These building blocks include:

And if you want to create your building blocks, you can use any of the 75+ hooks that carry headless, reusable logic. To name a few of them:

Pub/Sub and Live Updates

React-admin provides hooks and UI components for collaborative applications where several people work in parallel. It allows publishing and subscribing to real-time events, updating views when another user pushes a change, notifying end users of events, and preventing data loss when two editors work on the same resource concurrently.

At its core, the real-time system provides a pub/sub mechanism to send and receive real-time events. Events are sent to a topic, and all subscribers to this topic receive the event.

// on the publisher side
const [publish] = usePublish();
publish(topic, event);

// on the subscriber side
useSubscribe(topic, callback);

Check the following low-level hooks for more details:

React-admin provides live updates via specialized hooks and components. This means that when a user edits a resource, the other users working on the same resource see the changes in real time whether they are in a list, a show view, or an edit view.

For instance, replace <List> with <ListLive> to have a list refreshing automatically when an element is added, updated, or deleted:

import {
-   List,
    Datagrid,
    TextField,
    NumberField,
    Datefield,
} from 'react-admin';
+import { ListLive } from '@react-admin/ra-realtime';

const PostList = () => (
-   <List>
+   <ListLive>
        <Datagrid>
            <TextField source="title" />
            <NumberField source="views" />
            <DateField source="published_at" />
        </Datagrid>
-   </List>
+   </ListLive>
);

This feature leverages the following hooks:

And the following components:

React-admin also provides badge notifications in the Menu, so that users can see that something new happened to a resource list while working on another one.

MenuLive

Use <MenuLive> instead of react-admin’s <Menu> to get this feature:

import React from 'react';
import { Admin, Layout, Resource } from 'react-admin';
import { MenuLive } from '@react-admin/ra-realtime';

import { PostList, PostShow, PostEdit, realTimeDataProvider } from '.';

const CustomLayout = ({ children }) => (
    <Layout menu={MenuLive}>
        {children}
    </Layout>
);

const MyReactAdmin = () => (
    <Admin dataProvider={realTimeDataProvider} layout={CustomLayout}>
        <Resource name="posts" list={PostList} show={PostShow} edit={PostEdit} />
    </Admin>
);

This feature leverages the following components:

And last but not least, react-admin provides a lock mechanism to prevent two users from editing the same resource at the same time.

A user can lock a resource, either by voluntarily asking for a lock or by editing a resource. When a resource is locked, other users can’t edit it. When the lock is released, other users can edit the resource again.

export const NewMessageForm = () => {
    const [create, { isPending }] = useCreate();
    const record = useRecordContext();

    const { data: lock } = useGetLockLive('tickets', { id: record.id });
    const { identity } = useGetIdentity();
    const isFormDisabled = lock && lock.identity !== identity?.id;

    const [doLock] = useLockOnCall({ resource: 'tickets' });
    const handleSubmit = (values: any) => {
        /* ... */
    };

    return (
        <Form onSubmit={handleSubmit}>
            <TextInput
                source="message"
                multiline
                onFocus={() => {
                    doLock();
                }}
                disabled={isFormDisabled}
            />
            <SelectInput
                source="status"
                choices={statusChoices}
                disabled={isFormDisabled}
            />
            <Button type="submit" disabled={isPending || isFormDisabled}>
                Submit
            </Button>
        </Form>
    );
};

This feature leverages the following hooks:

Preferences

End-users tweak the UI to their liking, and they expect these preferences to be saved so that they don’t need to do it again the next time they visit the app. React-admin provides a persistent Store for user preferences and uses it in many components.

For instance, the Saved Queries feature lets users save a combination of filters and sort parameters into a new, personal filter.

Saved queries persist between sessions, so users can find their custom queries even after closing and reopening the admin. Saved queries are available both for the Filter Button/Form combo and for the <FilterList> Sidebar. It’s enabled by default for the Filter Button/Form combo, but you have to add it yourself in the <FilterList> Sidebar.

import { FilterList, FilterListItem, List, Datagrid } from 'react-admin';
import { Card, CardContent } from '@mui/material';

+import { SavedQueriesList } from 'react-admin';

const SongFilterSidebar = () => (
    <Card>
        <CardContent>
+           <SavedQueriesList />
            <FilterList label="Record Company" icon={<BusinessIcon />}>
                ...
            </FilterList>
            <FilterList label="Released" icon={<DateRangeeIcon />}>
               ...
            </FilterList>
        </CardContent>
    </Card>
);

const SongList = () => (
    <List aside={<SongFilterSidebar />}>
        <Datagrid>
            ...
        </Datagrid>
    </List>
);

React-admin also persists the light/dark mode and the language choice of end-users.

To learn more about the Store and how to use it, check the following sections:

Configurable UI

An extension of preferences is Configurable components. Because no matter how polished your UI is, it will never fit all use cases. That’s why react-admin provides a way to let end users customize the features of many components visually, via the inspector.

To enable this feature, replace a component (in that example, <Datagrid>) with its configurable counterpart:

import {
    List,
-   Datagrid,
+   DatagridConfigurable,
    TextField,
} from 'react-admin';

const PostList = () => (
    <List>
-       <Datagrid>
+       <DatagridConfigurable>
            <TextField source="id" />
            <TextField source="title" />
            <TextField source="author" />
            <TextField source="year" />
-       </Datagrid>
+       </DatagridConfigurable>
    </List>
);

React-admin even provides primitives and components to make your own components configurable.

Check the following components for details:

Theming

The default Material Design look and feel is nice, but a bit… Google-y. If this bothers you, or if you need to brand your app, rest assured: react-admin is fully themeable.

React-admin comes with 4 built-in themes: Default, Nano, Radiant, and House. The e-commerce demo contains a theme switcher, so you can test them in a real application.

To use a custom theme, pass a theme object to the <Admin> theme and darkTheme props:

import { Admin, nanoLightTheme, nanoDarkTheme } from 'react-admin';
import { dataProvider } from './dataProvider';

export const App = () => (
    <Admin
        dataProvider={dataProvider}
        theme={nanoLightTheme}
        darkTheme={nanoDarkTheme}
    >
        // ...
    </Admin>
);

Theming is so powerful that you can even use react-admin to build a Music Player:

Music Player

Use the sx prop on almost every react-admin component to override its default style - and the style of its descendants. For instance, here is how to change the width of Datagrid columns:

import {
    BooleanField,
    Datagrid,
    DateField,
    EditButton,
    List,
    NumberField,
    TextField,
    ShowButton,
} from 'react-admin';
import Icon from '@mui/icons-material/Person';

export const VisitorIcon = Icon;

export const PostList = () => (
    <List>
        <Datagrid
            sx={{
                backgroundColor: "Lavender",
                "& .RaDatagrid-headerCell": {
                    backgroundColor: "MistyRose",
                },
            }}
        >
            <TextField source="id" />
            <TextField source="title" />
            <DateField source="published_at" sortByOrder="DESC" />
            <BooleanField source="commentable" sortable={false} />
            <NumberField source="views" sortByOrder="DESC" />
            <EditButton />
            <ShowButton />
        </Datagrid>
    </List>
);

React-admin relies on Emotion, a popular CSS-in-JS library, to let you customize not only individual components but also the theme constants (colors, fonts, spacing, etc.).

import { defaultTheme } from 'react-admin';
import indigo from '@mui/material/colors/indigo';
import pink from '@mui/material/colors/pink';
import red from '@mui/material/colors/red';

const myTheme = {
    ...defaultTheme,
    palette: {
        primary: indigo,
        secondary: pink,
        error: red,
        contrastThreshold: 3,
        tonalOffset: 0.2,
    },
    typography: {
        // Use the system font instead of the default Roboto font.
        fontFamily: ['-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Arial', 'sans-serif'].join(','),
    },
};

And if you find yourself needing to override the style of a component multiple times, you can do it in the theme, too:

import { defaultTheme } from 'react-admin';

const theme = {
    ...defaultTheme,
    components: {
        ...defaultTheme.components,
        RaDatagrid: {
            styleOverrides: {
              root: {
                  backgroundColor: "Lavender",
                  "& .RaDatagrid-headerCell": {
                      backgroundColor: "MistyRose",
                  },
              }
           }
        }
    }
};

const App = () => (
    <Admin theme={theme}>
        // ...
    </Admin>
);

To learn more about theming in react-admin, check the following sections:

I18n

React-admin is fully internationalized.

The default interface messages (for buttons, tooltips, input labels, etc.) are in English. You can translate them to any of the 30+ languages supported by react-admin by importing the appropriate translation package. For instance, to translate to French:

import { Admin } from 'react-admin';
import polyglotI18nProvider from 'ra-i18n-polyglot';
import fr from 'ra-language-french';

export const i18nProvider = polyglotI18nProvider(() => fr, 'fr');

export const App = () => (
    <Admin i18nProvider={i18nProvider}>
        // ...
    </Admin>
);

If you need to translate to a language not yet supported by react-admin, you can write a custom translation package. Check the Writing a Custom Translation Package page for details.

If your app needs to support more than one language, you can use the <LocalesMenuButton> component to let users choose their language:

import { LocalesMenuButton, TitlePortal } from 'react-admin';
import { AppBar, Toolbar } from '@mui/material';

export const MyAppBar = () => (
    <AppBar>
        <Toolbar>
            <TitlePortal />
            <LocalesMenuButton />
        </Toolbar>
    </AppBar>
);

And in your components, to translate a message, use the useTranslate hook:

import { useTranslate } from 'react-admin';

const MyHelloButton = () => {
    const translate = useTranslate();
    return (
        <button>{translate('myroot.hello.world')}</button>
    );
};

export default MyHelloButton;

The underlying translation library, polyglot.js, supports pluralization and interpolation. It is popular, fast, and lightweight. But if you prefer to store your translations in GETTEXT or YAML files rather than JSON, the adapter approach lets you use any translation library you want.

React-admin is used by thousands of companies across the world, so the internationalization support is mature and well-tested. Check the following sections to learn more about react-admin’s i18n support:

Accessibility

The react-admin core team has a strong commitment to accessibility. React-admin uses the Material UI components, which are accessible by default. For its own components, react-admin uses the WAI-ARIA standard to make them accessible. This includes aria- attributes, keyboard navigation, and focus management.

We routinely test react-admin with the WAVE and Axe accessibility tools.

Accessibility

Responsive

The react-admin layouts and components are primarily designed for desktop screens. But they also work well on mobile devices. On mobile, buttons with a label become icon buttons, the sidebar menu becomes a drawer, the size of clickable elements is increased, the form toolbar becomes fixed, and many more adjustments.

React-admin provides specific components for mobile screens and gives you the tools to adapt the content to the screen size.

For a given component, the sx prop lets you customize its style based on the screen size. For instance, to reduce the width of an element on mobile screens:

<Box
    sx={{
        width: {
            xs: 100, // theme.breakpoints.up('xs')
            sm: 200, // theme.breakpoints.up('sm')
            md: 300, // theme.breakpoints.up('md')
            lg: 400, // theme.breakpoints.up('lg')
            xl: 500, // theme.breakpoints.up('xl')
        },
    }}
>
    This box has a responsive width.
</Box>

To make a component responsive, you can also render it conditionally based on the screen size. For instance, to render a <SimpleList> on desktop and a <Datagrid> on mobile:

import * as React from 'react';
import { useMediaQuery } from '@mui/material';
import { List, SimpleList, Datagrid, TextField, ReferenceField, EditButton } from 'react-admin';

export const PostList = () => {
    const isSmall = useMediaQuery(theme => theme.breakpoints.down('sm'));
    return (
        <List>
            {isSmall ? (
                <SimpleList
                    primaryText={record => record.title}
                    secondaryText={record => `${record.views} views`}
                    tertiaryText={record => new Date(record.published_at).toLocaleDateString()}
                />
            ) : (
                <Datagrid>
                    <TextField source="id" />
                    <ReferenceField label="User" source="userId" reference="users">
                        <TextField source="name" />
                    </ReferenceField>
                    <TextField source="title" />
                    <TextField source="body" />
                    <EditButton />
                </Datagrid>
            )}
        </List>
    );
};

Check the following sections for help on making your app responsive:

Type-Safe

React-admin is written in TypeScript. That doesn’t mean you have to use TypeScript to use react-admin - you can write react-admin apps in JavaScript. But if you do, you get compile-time type checking for your components, hooks, data providers, auth providers, translation messages, and more.

And if your IDE supports TypeScript, you get autocompletion and inline documentation for all react-admin components and hooks.

Building react-admin apps with TypeScript brings more safety and productivity to your development workflow.

Sustainable

Last but not least, react-admin is here to stay. That’s because the development of the open-source project is funded by the customers of the Enterprise Edition.

Maintaining a large open-source project in the long term is a challenge. But the react-admin core team, hosted by Marmelab, doesn’t have to worry about the next funding round, or about paying back venture capital by raising prices. React-admin has zero debt, has already passed the break-even point, and the team will only grow as the number of customers grows.

The core team is fortunate to be able to work full-time on react-admin, and this allows us to:

  • release bug fixes every week
  • release new features every month
  • provide support to our customers
  • maintain the documentation
  • refactor the codebase
  • create demos and tutorials
  • stay up-to-date with the latest React and libraries versions
  • contribute to the open-source community

At Marmelab, “sustainable” also means low carbon footprint. React-admin is regularly audited with GreenFrame, a tool that measures the carbon footprint of software projects. Technical choices are also made with the environment in mind. For instance, the use of React Query for caching data in react-admin reduces the number of HTTP requests, and thus reduces the carbon footprint of the application.