React Admin Panel Development
React Admin is a framework for building admin interfaces on top of REST or GraphQL APIs. It provides ready-made components for CRUD operations, filtering, pagination, authentication, and permission management. The main value is development speed: a typical section with a table and create/edit forms takes hours instead of days.
Installation and Structure
npm install react-admin ra-data-simple-rest
# or for GraphQL:
npm install react-admin ra-data-graphql-simple
src/
admin/
App.tsx — root component
dataProvider.ts — API adapter
authProvider.ts — authentication
resources/
users/
UserList.tsx
UserEdit.tsx
UserCreate.tsx
products/
ProductList.tsx
ProductEdit.tsx
dataProvider
Adapter between React Admin and your API. ra-data-simple-rest works with REST APIs where:
-
GET /users— list withContent-Rangeheader -
GET /users/1— single record -
POST /users— create -
PUT /users/1— update -
DELETE /users/1— delete
// dataProvider.ts
import simpleRestProvider from 'ra-data-simple-rest';
import { fetchUtils } from 'react-admin';
const httpClient = (url: string, options: fetchUtils.Options = {}) => {
const token = localStorage.getItem('auth_token');
const headers = new Headers(options.headers);
if (token) headers.set('Authorization', `Bearer ${token}`);
return fetchUtils.fetchJson(url, { ...options, headers });
};
export const dataProvider = simpleRestProvider(
import.meta.env.VITE_API_URL,
httpClient
);
For non-standard APIs — custom dataProvider:
import { DataProvider } from 'react-admin';
export const dataProvider: DataProvider = {
getList: async (resource, params) => {
const { page, perPage } = params.pagination;
const { field, order } = params.sort;
const res = await api.get(`/${resource}`, {
params: {
_page: page,
_limit: perPage,
_sort: field,
_order: order.toLowerCase(),
...params.filter,
},
});
return {
data: res.data.items,
total: res.data.total,
};
},
getOne: async (resource, { id }) => {
const { data } = await api.get(`/${resource}/${id}`);
return { data };
},
create: async (resource, { data }) => {
const res = await api.post(`/${resource}`, data);
return { data: res.data };
},
update: async (resource, { id, data }) => {
const res = await api.put(`/${resource}/${id}`, data);
return { data: res.data };
},
delete: async (resource, { id }) => {
await api.delete(`/${resource}/${id}`);
return { data: { id } as any };
},
deleteMany: async (resource, { ids }) => {
await Promise.all(ids.map((id) => api.delete(`/${resource}/${id}`)));
return { data: ids };
},
updateMany: async (resource, { ids, data }) => {
await Promise.all(ids.map((id) => api.put(`/${resource}/${id}`, data)));
return { data: ids };
},
getManyReference: async (resource, params) => {
// For related records (e.g., post comments)
const res = await api.get(`/${resource}`, {
params: { [params.target]: params.id },
});
return { data: res.data.items, total: res.data.total };
},
getMany: async (resource, { ids }) => {
const res = await api.get(`/${resource}`, {
params: { ids: ids.join(',') },
});
return { data: res.data };
},
};
authProvider
import { AuthProvider } from 'react-admin';
export const authProvider: AuthProvider = {
login: async ({ username, password }) => {
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
if (!res.ok) throw new Error('Invalid username or password');
const { token, user } = await res.json();
localStorage.setItem('auth_token', token);
localStorage.setItem('auth_user', JSON.stringify(user));
},
logout: () => {
localStorage.removeItem('auth_token');
localStorage.removeItem('auth_user');
return Promise.resolve();
},
checkAuth: () =>
localStorage.getItem('auth_token') ? Promise.resolve() : Promise.reject(),
checkError: ({ status }) => {
if (status === 401 || status === 403) {
localStorage.removeItem('auth_token');
return Promise.reject();
}
return Promise.resolve();
},
getPermissions: () => {
const user = JSON.parse(localStorage.getItem('auth_user') ?? '{}');
return Promise.resolve(user.role ?? 'guest');
},
getIdentity: () => {
const user = JSON.parse(localStorage.getItem('auth_user') ?? '{}');
return Promise.resolve({
id: user.id,
fullName: user.name,
avatar: user.avatar,
});
},
};
Resource: List and Forms
// resources/users/UserList.tsx
import { List, Datagrid, TextField, EmailField, DateField, EditButton, DeleteButton, Filter, TextInput, SelectInput } from 'react-admin';
const UserFilter = () => (
<Filter>
<TextInput label="Search" source="q" alwaysOn />
<SelectInput source="role" choices={[
{ id: 'admin', name: 'Administrator' },
{ id: 'editor', name: 'Editor' },
{ id: 'user', name: 'User' },
]} />
</Filter>
);
export const UserList = () => (
<List filters={<UserFilter />} sort={{ field: 'createdAt', order: 'DESC' }}>
<Datagrid rowClick="edit" bulkActionButtons={false}>
<TextField source="id" />
<TextField source="name" label="Name" />
<EmailField source="email" />
<TextField source="role" label="Role" />
<DateField source="createdAt" label="Created" showTime />
<EditButton />
<DeleteButton />
</Datagrid>
</List>
);
// resources/users/UserEdit.tsx
import { Edit, SimpleForm, TextInput, SelectInput, required, email } from 'react-admin';
export const UserEdit = () => (
<Edit>
<SimpleForm>
<TextInput source="name" label="Name" validate={required()} />
<TextInput source="email" label="Email" validate={[required(), email()]} />
<SelectInput source="role" label="Role" choices={[
{ id: 'admin', name: 'Administrator' },
{ id: 'editor', name: 'Editor' },
{ id: 'user', name: 'User' },
]} />
</SimpleForm>
</Edit>
);
App.tsx
import { Admin, Resource } from 'react-admin';
import { dataProvider } from './dataProvider';
import { authProvider } from './authProvider';
import { UserList, UserEdit, UserCreate } from './resources/users';
import { ProductList, ProductEdit, ProductCreate } from './resources/products';
import PeopleIcon from '@mui/icons-material/People';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
export function AdminApp() {
return (
<Admin
dataProvider={dataProvider}
authProvider={authProvider}
basename="/admin"
>
<Resource
name="users"
list={UserList}
edit={UserEdit}
create={UserCreate}
icon={PeopleIcon}
options={{ label: 'Users' }}
/>
<Resource
name="products"
list={ProductList}
edit={ProductEdit}
create={ProductCreate}
icon={ShoppingCartIcon}
options={{ label: 'Products' }}
/>
</Admin>
);
}
Theme and Layout Customization
import { defaultTheme, Layout, AppBar, Menu } from 'react-admin';
const myTheme = {
...defaultTheme,
palette: {
primary: { main: '#1976d2' },
secondary: { main: '#dc004e' },
background: { default: '#f5f5f5' },
},
components: {
...defaultTheme.components,
RaDatagrid: {
styleOverrides: {
root: {
'& .RaDatagrid-headerCell': {
backgroundColor: '#f0f0f0',
},
},
},
},
},
};
const CustomLayout = (props: any) => (
<Layout {...props} appBar={AppBar} menu={Menu} />
);
<Admin theme={myTheme} layout={CustomLayout} />
Permission Management
// Show buttons only for needed roles
import { usePermissions, EditButton, DeleteButton } from 'react-admin';
const UserActions = () => {
const { permissions } = usePermissions();
return (
<>
{['admin', 'editor'].includes(permissions) && <EditButton />}
{permissions === 'admin' && <DeleteButton />}
</>
);
};
Timeline
- MVP (3–5 resources, standard CRUD, basic auth): 3–5 days
- Full panel (custom dataProvider, complex forms, file upload, permissions, custom theme): 2–3 weeks
- With dashboard, charts, custom components: another 1–2 weeks







