Resources are one of the fundamental concepts in Refine. A resource represents an entity in your application (like posts, users, products, orders) and defines how Refine should interact with it.
What is a Resource?
A resource connects:
- Backend API endpoint (e.g.,
/posts)
- Frontend routes (e.g.,
/posts, /posts/create, /posts/edit/:id)
- UI components (list, create, edit, show pages)
- Metadata (labels, icons, permissions)
import { Refine } from "@refinedev/core";
<Refine
resources={[
{
name: "posts", // API endpoint name
list: "/posts", // List page route
create: "/posts/create", // Create page route
edit: "/posts/edit/:id", // Edit page route
show: "/posts/show/:id", // Show page route
meta: { // Additional metadata
label: "Blog Posts",
icon: <PostIcon />,
canDelete: true,
},
},
]}
/>
Resource Interface
From packages/core/src/contexts/resource/types.ts:68-81:
export interface ResourceProps extends IResourceComponents {
/**
* Name of the resource - used in API calls
*/
name: string;
/**
* Unique identifier for the resource
* @default name of the resource
*/
identifier?: string;
/**
* Custom metadata for the resource
*/
meta?: ResourceMeta;
}
export interface IResourceComponents {
list?: string; // Route path for list page
create?: string; // Route path for create page
clone?: string; // Route path for clone page
edit?: string; // Route path for edit page
show?: string; // Route path for show page
}
Basic Resource Definition
Minimal Example
Only the name is required:
<Refine
resources={[
{ name: "posts" },
{ name: "users" },
{ name: "categories" },
]}
/>
Even without route definitions, Refine hooks can still interact with these resources through the data provider.
With Routes
Define routes for automatic navigation:
<Refine
resources={[
{
name: "posts",
list: "/posts",
create: "/posts/create",
edit: "/posts/edit/:id",
show: "/posts/show/:id",
},
]}
/>
Now Refine can automatically:
- Navigate between pages
- Generate breadcrumbs
- Create menu items
- Infer current resource from URL
From packages/core/src/contexts/resource/types.ts:26-62:
export interface KnownResourceMeta {
/**
* Display label (used in UI, breadcrumbs, sidebar)
*/
label?: string;
/**
* Hide resource from sidebar
*/
hide?: boolean;
/**
* Dedicated data provider for this resource
*/
dataProviderName?: string;
/**
* Parent resource for nesting
*/
parent?: string;
/**
* Whether resource supports delete operations
*/
canDelete?: boolean;
/**
* Actions to audit log
*/
audit?: ResourceAuditLogPermissions[];
/**
* Icon for sidebar/menus
*/
icon?: ReactNode;
}
You can add any custom metadata:
<Refine
resources={[
{
name: "posts",
meta: {
label: "Blog Posts",
icon: <FileTextIcon />,
canDelete: true,
// Custom properties
color: "blue",
category: "content",
order: 1,
},
},
]}
/>
Access custom meta in components:
import { useResource } from "@refinedev/core";
function MyComponent() {
const { resource } = useResource();
const color = resource?.meta?.color; // "blue"
const category = resource?.meta?.category; // "content"
return <div style={{ color }}>...</div>;
}
Resource Identifiers
When you have resources with the same name but different endpoints:
<Refine
dataProvider={{
default: restDataProvider("https://api.example.com"),
cms: cmsDataProvider("https://cms.example.com"),
}}
resources={[
{
name: "posts",
identifier: "blog-posts",
list: "/posts",
meta: {
dataProviderName: "default",
},
},
{
name: "posts",
identifier: "cms-posts",
list: "/cms/posts",
meta: {
dataProviderName: "cms",
},
},
]}
/>
Use identifier in hooks:
const { data } = useList({
resource: "blog-posts", // Uses identifier
});
const { data: cmsData } = useList({
resource: "cms-posts",
});
Nested Resources
Create hierarchical resource structures:
<Refine
resources={[
{
name: "cms",
meta: {
label: "CMS",
icon: <FolderIcon />,
},
},
{
name: "posts",
list: "/posts",
meta: {
parent: "cms",
label: "Blog Posts",
},
},
{
name: "pages",
list: "/pages",
meta: {
parent: "cms",
label: "Pages",
},
},
]}
/>
This creates a nested sidebar structure:
CMS
├── Blog Posts
└── Pages
Nested resources work even if the parent resource doesn’t have its own routes.
Resource Actions
Resources support these standard actions:
List
Display a list/table of records:
{
name: "posts",
list: "/posts",
}
Create
Create a new record:
{
name: "posts",
create: "/posts/create",
}
Edit
Edit an existing record:
{
name: "posts",
edit: "/posts/edit/:id",
}
Show
Display a single record:
{
name: "posts",
show: "/posts/show/:id",
}
Clone
Create a new record based on existing one:
{
name: "posts",
clone: "/posts/clone/:id",
}
Using Resources in Components
Infer from Route
Refine automatically infers the current resource from the URL:
// On route: /posts
function PostList() {
// Resource is automatically inferred as "posts"
const { data } = useList();
return <Table dataSource={data} />;
}
Explicit Resource
Specify resource explicitly:
function Dashboard() {
// Fetch posts even though we're not on the posts route
const { data: posts } = useList({ resource: "posts" });
const { data: users } = useList({ resource: "users" });
return (
<div>
<PostsWidget data={posts} />
<UsersWidget data={users} />
</div>
);
}
Use useResource hook:
import { useResource } from "@refinedev/core";
function MyComponent() {
const { resource, resources, identifier, id, action } = useResource();
// resource: Current resource object
// resources: All resources array
// identifier: Current resource identifier
// id: Record ID from URL (if on edit/show page)
// action: Current action ("list", "create", "edit", "show")
return (
<div>
<h1>{resource?.meta?.label}</h1>
<p>Current action: {action}</p>
{id && <p>Record ID: {id}</p>}
</div>
);
}
Resource-Based Navigation
Navigate to Resource Pages
import { useGo } from "@refinedev/core";
function NavigationExample() {
const go = useGo();
return (
<div>
<button
onClick={() => {
go({
to: {
resource: "posts",
action: "list",
},
});
}}
>
View Posts
</button>
<button
onClick={() => {
go({
to: {
resource: "posts",
action: "create",
},
});
}}
>
Create Post
</button>
<button
onClick={() => {
go({
to: {
resource: "posts",
action: "edit",
id: 123,
},
});
}}
>
Edit Post #123
</button>
</div>
);
}
Dynamic Resources
Resources can be added dynamically:
import { Refine } from "@refinedev/core";
import { useState, useEffect } from "react";
function App() {
const [resources, setResources] = useState([]);
useEffect(() => {
// Fetch resources from API or config
fetchResources().then((data) => {
setResources(data);
});
}, []);
return (
<Refine
dataProvider={dataProvider}
resources={resources}
>
{/* Your app */}
</Refine>
);
}
Resource Permissions
Combine with Access Control Provider:
import { CanAccess } from "@refinedev/core";
function PostList() {
return (
<div>
<CanAccess resource="posts" action="create">
<CreateButton />
</CanAccess>
<Table
dataSource={data}
rowActions={(record) => (
<>
<CanAccess
resource="posts"
action="edit"
params={{ id: record.id }}
>
<EditButton />
</CanAccess>
<CanAccess
resource="posts"
action="delete"
params={{ id: record.id }}
>
<DeleteButton />
</CanAccess>
</>
)}
/>
</div>
);
}
Best Practices
1. Consistent Naming
Use consistent naming between backend and frontend:
// Good
{ name: "blog_posts" } // Matches API endpoint /blog_posts
// Less ideal
{ name: "posts" } // API endpoint is /blog_posts
If names must differ, handle it in the data provider.
2. Meaningful Labels
Provide user-friendly labels:
{
name: "product_categories",
meta: {
label: "Product Categories", // Better than "product_categories"
},
}
3. Icon Consistency
Use consistent icon styles:
import { FileTextIcon, UsersIcon, ShoppingCartIcon } from "lucide-react";
resources={[
{ name: "posts", meta: { icon: <FileTextIcon /> } },
{ name: "users", meta: { icon: <UsersIcon /> } },
{ name: "products", meta: { icon: <ShoppingCartIcon /> } },
]}
4. Route Patterns
Follow consistent route patterns:
// Good - Consistent pattern
{
name: "posts",
list: "/posts",
create: "/posts/create",
edit: "/posts/edit/:id",
show: "/posts/show/:id",
}
// Also good - Different pattern but consistent
{
name: "posts",
list: "/posts",
create: "/posts/new",
edit: "/posts/:id/edit",
show: "/posts/:id",
}
5. Organize by Feature
Group related resources:
resources={[
// Content Management
{ name: "posts", meta: { parent: "cms" } },
{ name: "pages", meta: { parent: "cms" } },
{ name: "media", meta: { parent: "cms" } },
// E-commerce
{ name: "products", meta: { parent: "shop" } },
{ name: "orders", meta: { parent: "shop" } },
{ name: "customers", meta: { parent: "shop" } },
// System
{ name: "users", meta: { parent: "system" } },
{ name: "settings", meta: { parent: "system" } },
]}
Common Patterns
Read-Only Resource
{
name: "analytics",
list: "/analytics",
show: "/analytics/:id",
// No create/edit - read-only
meta: {
canDelete: false,
},
}
Create-Only Resource
{
name: "tickets",
list: "/tickets",
create: "/tickets/create",
show: "/tickets/:id",
// No edit - tickets can't be edited once created
}
Hidden Resource
{
name: "internal_logs",
// No routes - used only for data fetching
meta: {
hide: true, // Don't show in sidebar
},
}
TypeScript Support
import { ResourceProps } from "@refinedev/core";
import { ReactNode } from "react";
interface CustomMeta {
color?: string;
category?: string;
order?: number;
}
const resources: ResourceProps[] = [
{
name: "posts",
list: "/posts",
meta: {
label: "Blog Posts",
color: "blue",
category: "content",
order: 1,
} as CustomMeta,
},
];
Next Steps
Routing
Learn about routing and navigation
Data Fetching
Fetch and manage resource data
Authorization
Control resource access
Providers
Understand the provider system