Skip to main content

Overview

While Refine provides data providers for many popular backend services, you may need to create a custom data provider to connect to your specific API. This guide will walk you through the process of building a custom data provider.

Data Provider Interface

A data provider is an object that implements the DataProvider interface. Here’s the TypeScript interface:
import type { DataProvider } from "@refinedev/core";

const dataProvider: DataProvider = {
  // Required methods
  getList: (params) => Promise,
  getOne: (params) => Promise,
  getMany: (params) => Promise,
  create: (params) => Promise,
  update: (params) => Promise,
  deleteOne: (params) => Promise,
  getApiUrl: () => string,
  
  // Optional methods
  createMany: (params) => Promise,
  updateMany: (params) => Promise,
  deleteMany: (params) => Promise,
  custom: (params) => Promise,
};

Building a Custom Data Provider

Let’s create a custom data provider for a REST API step by step.
1

Set up the basic structure

Create a file for your data provider and define the basic structure:
import type { DataProvider } from "@refinedev/core";
import axios from "axios";

export const dataProvider = (apiUrl: string): DataProvider => {
  return {
    getList: async ({ resource, pagination, filters, sorters, meta }) => {
      // Implementation
    },
    getOne: async ({ resource, id, meta }) => {
      // Implementation
    },
    getMany: async ({ resource, ids, meta }) => {
      // Implementation
    },
    create: async ({ resource, variables, meta }) => {
      // Implementation
    },
    update: async ({ resource, id, variables, meta }) => {
      // Implementation
    },
    deleteOne: async ({ resource, id, variables, meta }) => {
      // Implementation
    },
    getApiUrl: () => apiUrl,
  };
};
2

Implement getList

The getList method fetches a list of records with pagination, filtering, and sorting:
getList: async ({ resource, pagination, filters, sorters, meta }) => {
  const { current = 1, pageSize = 10, mode = "server" } = pagination ?? {};

  const url = `${apiUrl}/${resource}`;
  
  const query: Record<string, any> = {
    page: current,
    limit: pageSize,
  };

  // Add filters
  if (filters) {
    filters.forEach((filter) => {
      if (filter.operator === "eq") {
        query[filter.field] = filter.value;
      }
      // Handle other operators
    });
  }

  // Add sorting
  if (sorters && sorters.length > 0) {
    query.sort = sorters[0].field;
    query.order = sorters[0].order;
  }

  const { data } = await axios.get(url, { params: query });

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

Implement getOne

The getOne method fetches a single record by its ID:
getOne: async ({ resource, id, meta }) => {
  const url = `${apiUrl}/${resource}/${id}`;
  
  const { data } = await axios.get(url);

  return {
    data,
  };
}
4

Implement getMany

The getMany method fetches multiple records by their IDs:
getMany: async ({ resource, ids, meta }) => {
  const url = `${apiUrl}/${resource}`;
  
  const { data } = await axios.get(url, {
    params: {
      ids: ids.join(","),
    },
  });

  return {
    data,
  };
}
5

Implement create

The create method creates a new record:
create: async ({ resource, variables, meta }) => {
  const url = `${apiUrl}/${resource}`;
  
  const { data } = await axios.post(url, variables);

  return {
    data,
  };
}
6

Implement update

The update method updates an existing record:
update: async ({ resource, id, variables, meta }) => {
  const url = `${apiUrl}/${resource}/${id}`;
  
  const { data } = await axios.patch(url, variables);

  return {
    data,
  };
}
7

Implement deleteOne

The deleteOne method deletes a record:
deleteOne: async ({ resource, id, variables, meta }) => {
  const url = `${apiUrl}/${resource}/${id}`;
  
  const { data } = await axios.delete(url);

  return {
    data,
  };
}
8

Use your custom data provider

Now you can use your custom data provider in your Refine application:
import { Refine } from "@refinedev/core";
import { dataProvider } from "./dataProvider";

const App = () => {
  return (
    <Refine
      dataProvider={dataProvider("https://api.example.com")}
      // ... other props
    >
      {/* Your app content */}
    </Refine>
  );
};

Advanced Features

Handling Meta Parameters

The meta parameter allows you to pass custom data to your data provider methods:
getOne: async ({ resource, id, meta }) => {
  const headers = meta?.headers || {};
  
  const { data } = await axios.get(
    `${apiUrl}/${resource}/${id}`,
    { headers }
  );

  return { data };
}
Then use it in your hooks:
const { data } = useOne({
  resource: "posts",
  id: 1,
  meta: {
    headers: {
      "x-custom-header": "value",
    },
  },
});

Error Handling

Implement proper error handling in your data provider:
getOne: async ({ resource, id, meta }) => {
  try {
    const { data } = await axios.get(`${apiUrl}/${resource}/${id}`);
    return { data };
  } catch (error) {
    if (axios.isAxiosError(error)) {
      throw new Error(error.response?.data?.message || error.message);
    }
    throw error;
  }
}

Custom Method

Implement the custom method for special operations:
custom: async ({ url, method, payload, query, headers }) => {
  const { data } = await axios({
    url: `${apiUrl}${url}`,
    method,
    data: payload,
    params: query,
    headers,
  });

  return { data };
}

Complete Example

Here’s a complete example of a custom REST data provider:
import type { DataProvider } from "@refinedev/core";
import axios, { AxiosInstance } from "axios";

export const dataProvider = (
  apiUrl: string,
  httpClient: AxiosInstance = axios
): DataProvider => {
  return {
    getList: async ({ resource, pagination, filters, sorters }) => {
      const { current = 1, pageSize = 10 } = pagination ?? {};

      const query = {
        page: current,
        limit: pageSize,
      };

      const { data } = await httpClient.get(`${apiUrl}/${resource}`, {
        params: query,
      });

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

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

    getMany: async ({ resource, ids }) => {
      const { data } = await httpClient.get(`${apiUrl}/${resource}`, {
        params: { ids: ids.join(",") },
      });
      return { data };
    },

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

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

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

    getApiUrl: () => apiUrl,
  };
};

Testing Your Data Provider

Test your data provider with different resources and operations:
import { dataProvider } from "./dataProvider";

const provider = dataProvider("https://api.example.com");

// Test getList
const list = await provider.getList({
  resource: "posts",
  pagination: { current: 1, pageSize: 10 },
});

// Test getOne
const one = await provider.getOne({
  resource: "posts",
  id: "1",
});

// Test create
const created = await provider.create({
  resource: "posts",
  variables: { title: "New Post" },
});

Next Steps

Data Providers Overview

Learn more about Refine data providers

Build docs developers (and LLMs) love