Skip to main content
Providers are the backbone of Refine’s architecture. They are adapter interfaces that allow Refine to work with any backend service, authentication system, or third-party integration without being tied to a specific implementation.

What is a Provider?

A provider is an object that implements a specific interface. Refine calls methods on these providers to perform operations like fetching data, authenticating users, or checking permissions.
import { DataProvider, AuthProvider } from "@refinedev/core";

// Example provider structure
const dataProvider: DataProvider = {
  getList: async (params) => { /* ... */ },
  getOne: async (params) => { /* ... */ },
  create: async (params) => { /* ... */ },
  update: async (params) => { /* ... */ },
  deleteOne: async (params) => { /* ... */ },
  getApiUrl: () => "https://api.example.com",
};

const authProvider: AuthProvider = {
  login: async (params) => { /* ... */ },
  logout: async (params) => { /* ... */ },
  check: async (params) => { /* ... */ },
  onError: async (error) => { /* ... */ },
  getIdentity: async () => { /* ... */ },
};

Data Provider

The Data Provider is the only required provider and handles all CRUD operations.

Interface Definition

From packages/core/src/contexts/data/types.ts:441-487:
export type DataProvider = {
  // Required methods
  getList: <TData extends BaseRecord = BaseRecord>(
    params: GetListParams,
  ) => Promise<GetListResponse<TData>>;

  getOne: <TData extends BaseRecord = BaseRecord>(
    params: GetOneParams,
  ) => Promise<GetOneResponse<TData>>;

  create: <TData extends BaseRecord = BaseRecord, TVariables = {}>(
    params: CreateParams<TVariables>,
  ) => Promise<CreateResponse<TData>>;

  update: <TData extends BaseRecord = BaseRecord, TVariables = {}>(
    params: UpdateParams<TVariables>,
  ) => Promise<UpdateResponse<TData>>;

  deleteOne: <TData extends BaseRecord = BaseRecord, TVariables = {}>(
    params: DeleteOneParams<TVariables>,
  ) => Promise<DeleteOneResponse<TData>>;

  getApiUrl: () => string;

  // Optional methods
  getMany?: <TData extends BaseRecord = BaseRecord>(
    params: GetManyParams,
  ) => Promise<GetManyResponse<TData>>;

  createMany?: <TData extends BaseRecord = BaseRecord, TVariables = {}>(
    params: CreateManyParams<TVariables>,
  ) => Promise<CreateManyResponse<TData>>;

  updateMany?: <TData extends BaseRecord = BaseRecord, TVariables = {}>(
    params: UpdateManyParams<TVariables>,
  ) => Promise<UpdateManyResponse<TData>>;

  deleteMany?: <TData extends BaseRecord = BaseRecord, TVariables = {}>(
    params: DeleteManyParams<TVariables>,
  ) => Promise<DeleteManyResponse<TData>>;

  custom?: <TData extends BaseRecord = BaseRecord>(
    params: CustomParams,
  ) => Promise<CustomResponse<TData>>;
};

Implementation Example

import { DataProvider } from "@refinedev/core";
import axios from "axios";

const API_URL = "https://api.example.com";

export const dataProvider: DataProvider = {
  getList: async ({ resource, pagination, filters, sorters, meta }) => {
    const { current = 1, pageSize = 10 } = pagination ?? {};
    
    const { data } = await axios.get(`${API_URL}/${resource}`, {
      params: {
        _start: (current - 1) * pageSize,
        _limit: pageSize,
        // Apply filters and sorters
      },
    });

    return {
      data: data.items,
      total: data.total,
    };
  },

  getOne: async ({ resource, id, meta }) => {
    const { data } = await axios.get(`${API_URL}/${resource}/${id}`);
    return { data };
  },

  create: async ({ resource, variables, meta }) => {
    const { data } = await axios.post(`${API_URL}/${resource}`, variables);
    return { data };
  },

  update: async ({ resource, id, variables, meta }) => {
    const { data } = await axios.patch(
      `${API_URL}/${resource}/${id}`,
      variables,
    );
    return { data };
  },

  deleteOne: async ({ resource, id, meta }) => {
    const { data } = await axios.delete(`${API_URL}/${resource}/${id}`);
    return { data };
  },

  getApiUrl: () => API_URL,
};
Refine provides pre-built data providers for popular backends like REST, GraphQL, Supabase, and more. Check the Data Providers section for ready-to-use implementations.

Multiple Data Providers

You can use multiple data providers for different resources:
import { Refine } from "@refinedev/core";
import restDataProvider from "@refinedev/simple-rest";
import graphqlDataProvider from "./graphql-provider";

<Refine
  dataProvider={{
    default: restDataProvider("https://api.example.com"),
    graphql: graphqlDataProvider("https://graphql.example.com"),
  }}
  resources={[
    {
      name: "posts",
      // Uses default provider
    },
    {
      name: "users",
      meta: {
        dataProviderName: "graphql", // Uses graphql provider
      },
    },
  ]}
/>

Auth Provider

Handles authentication and authorization logic.

Interface Definition

From packages/core/src/contexts/auth/types.ts:66-78:
export type AuthProvider = {
  login: (params: any) => Promise<AuthActionResponse>;
  logout: (params: any) => Promise<AuthActionResponse>;
  check: (params?: any) => Promise<CheckResponse>;
  onError: (error: any) => Promise<OnErrorResponse>;
  register?: (params: any) => Promise<AuthActionResponse>;
  forgotPassword?: (params: any) => Promise<AuthActionResponse>;
  updatePassword?: (params: any) => Promise<AuthActionResponse>;
  getPermissions?: (params?: any) => Promise<PermissionResponse>;
  getIdentity?: (params?: any) => Promise<IdentityResponse>;
};

Response Types

// Login, logout, register, forgotPassword, updatePassword
export type AuthActionResponse = {
  success: boolean;
  redirectTo?: string;
  error?: Error;
  successNotification?: {
    message: string;
    description?: string;
  };
};

// check method
export type CheckResponse = {
  authenticated: boolean;
  redirectTo?: string;
  logout?: boolean;
  error?: Error;
};

// onError method
export type OnErrorResponse = {
  redirectTo?: string;
  logout?: boolean;
  error?: Error;
};

Implementation Example

From examples/app-crm-minimal/src/providers/auth.ts:
import type { AuthProvider } from "@refinedev/core";

export const authProvider: AuthProvider = {
  login: async ({ email, password }) => {
    try {
      const response = await fetch("https://api.example.com/auth/login", {
        method: "POST",
        body: JSON.stringify({ email, password }),
        headers: { "Content-Type": "application/json" },
      });

      const { token } = await response.json();
      localStorage.setItem("access_token", token);

      return {
        success: true,
        redirectTo: "/",
      };
    } catch (error) {
      return {
        success: false,
        error: {
          name: "LoginError",
          message: "Invalid email or password",
        },
      };
    }
  },

  logout: async () => {
    localStorage.removeItem("access_token");
    return {
      success: true,
      redirectTo: "/login",
    };
  },

  check: async () => {
    const token = localStorage.getItem("access_token");
    
    if (token) {
      return {
        authenticated: true,
      };
    }

    return {
      authenticated: false,
      redirectTo: "/login",
      logout: true,
    };
  },

  onError: async (error) => {
    if (error.statusCode === 401 || error.statusCode === 403) {
      return {
        logout: true,
        redirectTo: "/login",
      };
    }

    return { error };
  },

  getIdentity: async () => {
    const token = localStorage.getItem("access_token");
    if (!token) return null;

    const response = await fetch("https://api.example.com/auth/me", {
      headers: { Authorization: `Bearer ${token}` },
    });

    const user = await response.json();
    return user;
  },

  getPermissions: async () => {
    const token = localStorage.getItem("access_token");
    if (!token) return null;

    const response = await fetch("https://api.example.com/auth/permissions", {
      headers: { Authorization: `Bearer ${token}` },
    });

    const permissions = await response.json();
    return permissions;
  },
};
All methods return promises. Refine handles redirects, notifications, and error states based on the returned values.

Access Control Provider

Enables role-based access control (RBAC) and permission management.

Interface Definition

From packages/core/src/contexts/accessControl/types.ts:82-85:
export type AccessControlProvider = {
  can: (params: CanParams) => Promise<CanReturnType>;
  options?: AccessControlOptions;
};

export type CanParams = {
  resource?: string;
  action: string;
  params?: {
    resource?: IResourceItem;
    id?: BaseKey;
    [key: string]: any;
  };
};

export type CanReturnType = {
  can: boolean;
  reason?: string;
};

Implementation Example

From examples/access-control-casbin/src/App.tsx:
import { AccessControlProvider } from "@refinedev/core";
import { newEnforcer } from "casbin";

const role = "editor";

const accessControlProvider: AccessControlProvider = {
  can: async ({ action, params, resource }) => {
    const enforcer = await newEnforcer(model, adapter);
    
    // Check permissions based on resource and action
    if (action === "delete" || action === "edit" || action === "show") {
      const can = await enforcer.enforce(
        role,
        `${resource}/${params?.id}`,
        action,
      );
      
      return {
        can,
        reason: can ? undefined : "Unauthorized",
      };
    }

    return {
      can: await enforcer.enforce(role, resource, action),
    };
  },
};
Use the <CanAccess> component or useCan hook to conditionally render UI based on permissions.

Router Provider

Integrates Refine with your routing library.

Interface Definition

From packages/core/src/contexts/router/types.ts:72-79:
export type RouterProvider = {
  go?: () => GoFunction;
  back?: () => BackFunction;
  parse?: () => ParseFunction;
  Link?: React.ComponentType<
    React.PropsWithChildren<{ to: string; [prop: string]: any }>
  >;
};

export type GoFunction = (config: GoConfig) => void | string;
export type BackFunction = () => void;
export type ParseFunction = () => ParseResponse;

Implementation Example

import { RouterProvider } from "@refinedev/core";
import { useNavigate, useLocation, useParams, Link } from "react-router";

const routerProvider: RouterProvider = {
  go: () => {
    const navigate = useNavigate();
    return ({ to, query, hash, options, type }) => {
      // Implementation
    };
  },
  
  back: () => {
    const navigate = useNavigate();
    return () => navigate(-1);
  },
  
  parse: () => {
    const location = useLocation();
    const params = useParams();
    return () => {
      // Parse and return current route info
    };
  },
  
  Link,
};
Refine provides router providers for React Router, Next.js, Remix, and other popular routing solutions.

Notification Provider

Handles user notifications (success, error, info messages).
import { NotificationProvider } from "@refinedev/core";
import { notification } from "antd";

export const notificationProvider: NotificationProvider = {
  open: ({ message, description, type, key }) => {
    notification[type]({
      message,
      description,
      key,
    });
  },
  close: (key) => {
    notification.close(key);
  },
};

i18n Provider

Enables internationalization support.
import { I18nProvider } from "@refinedev/core";
import i18n from "i18next";

export const i18nProvider: I18nProvider = {
  translate: (key, options) => i18n.t(key, options),
  changeLocale: (lang) => i18n.changeLanguage(lang),
  getLocale: () => i18n.language,
};

Live Provider

Enables real-time updates via WebSockets, SSE, or other protocols.
import { LiveProvider } from "@refinedev/core";
import { createClient } from "graphql-ws";

const wsClient = createClient({
  url: "wss://api.example.com/graphql",
});

export const liveProvider: LiveProvider = {
  subscribe: ({ channel, types, params, callback }) => {
    // Subscribe to real-time events
  },
  unsubscribe: (subscription) => {
    // Unsubscribe from events
  },
  publish: ({ channel, type, payload, date }) => {
    // Publish events (optional)
  },
};

Audit Log Provider

Tracks changes and user actions.
import { AuditLogProvider } from "@refinedev/core";

export const auditLogProvider: AuditLogProvider = {
  create: (params) => {
    // Log create action
  },
  update: (params) => {
    // Log update action
  },
  get: (params) => {
    // Retrieve audit logs
  },
};

Best Practices

1. Error Handling

Always handle errors gracefully in providers:
getOne: async ({ resource, id }) => {
  try {
    const { data } = await api.get(`/${resource}/${id}`);
    return { data };
  } catch (error) {
    throw {
      message: error.message,
      statusCode: error.response?.status || 500,
    };
  }
}

2. Type Safety

Use TypeScript for type-safe providers:
import { DataProvider, BaseRecord } from "@refinedev/core";

interface IPost extends BaseRecord {
  title: string;
  content: string;
}

const dataProvider: DataProvider = {
  getOne: async <TData extends BaseRecord = BaseRecord>(
    params: GetOneParams,
  ): Promise<GetOneResponse<TData>> => {
    // Implementation
  },
};

3. Use Meta for Custom Data

The meta parameter is available in all provider methods for custom data:
const { data } = useOne({
  resource: "posts",
  id: 1,
  meta: {
    headers: { "X-Custom-Header": "value" },
    fields: ["id", "title", "author.name"],
  },
});

Next Steps

Data Fetching

Learn how to fetch and manage data

Authentication

Implement authentication flows

Authorization

Set up access control

Data Provider Tutorial

Build a custom data provider

Build docs developers (and LLMs) love