Skip to main content
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

Resource Metadata

Known Meta Properties

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;
}

Custom Meta Properties

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>
  );
}

Access Resource Information

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

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

Build docs developers (and LLMs) love