Skip to content
+

Data providers

Bring tabular data to the frontend with server-side pagination and filtering.

Toolpad Studio functions are great to bring some backend state to the page, but they fall short when it comes to offering pagination and filtering capabilities from the server. Toolpad Studio offers a special construct to enable this use case: Data providers. Data providers abstract server-side collections. They could be database tables, REST APIs, or any data that represents a set of records that share a common interface. Data providers are defined as server-side objects and can be directly connected to a data grid to make it fully interactive.

Follow these steps to create a new data provider:

A data provider that iterates over a static list could look as follows:

import { createDataProvider } from '@toolpad/studio-runtime/server';
import DATA from './movies.json';

export default createDataProvider({
  async getRecords({ paginationModel: { start = 0, pageSize } }) {
    const records = DATA.slice(start, start + pageSize);
    return { records, totalCount: DATA.length };
  },
});

Pagination

The data provider supports two styles of pagination. Index based, and cursor based pagination.

Index based

This is the strategy where your data is paginated by returning data based on a start index and a page size. The getRecords method receives start and pageSize values in its paginationModel parameter and returns a set of records representing the page. Index based pagination is the default but you can explicitly enable this by setting paginationMode to 'index'.

export default createDataProvider({
  paginationMode: 'index',
  async getRecords({ paginationModel: { start = 0, pageSize } }) {
    const { page, totalCount } = await db.getRecords(start, pageSize);
    return {
      records: page,
      totalCount,
    };
  },
});

Cursor based

This is the strategy where your data is paginated by when it returns data based on a cursor and a page size. The getRecords method receives cursor and pageSize values in its paginationModel parameter and returns a set of records representing the page. You indicate the cursor of the next page with a cursor property in the result. Pass null to signal the end of the collection. You can enable Cursor based pagination by setting paginationMode to 'cursor'.

The cursor property of the paginationModel is null when Toolpad Studio fetches the initial page. Any result set returned from the getRecords function must be accompanied with a cursor property, a string which contains a reference to the next page. The cursor parameter in the paginationModel contains this value when fetching the subsequent page. Return null for this value to indicate the end of the sequence.

export default createDataProvider({
  paginationMode: 'cursor',
  async getRecords({ paginationModel: { cursor = null, pageSize } }) {
    const { page, nextPageCursor, totalCount } = await db.getRecords(
      cursor,
      pageSize,
    );
    return {
      records: page,
      cursor: nextPageCursor,
      totalCount,
    };
  },
});

Filtering

Toolpad Studio data sources support server-side filtering. You can implement a server-side filter by reading the filterModel property that is passed to the getRecords function. This model contains an items property and a logicOperator. By combining them you can achieve complex serverside filters.

export default createDataProvider({
  async getRecords({ filterModel }) {
    console.log(filterModel);
  },
});

For example, this could print the following if the corresponding column filters were applied in the data grid:

{
  logicOperator: 'and',
  items: [
    { field: 'first_name', operator: 'startsWith', value: 'L' },
    { field: 'last_name', operator: 'equals', value: 'Skywalker' },
  ]
}

Now the backend function receives the grid filter from the UI in its parameters.

Uncheck the column option "filterable" if you want to disable filtering for a certain column:

Disable filterable

Disable filterable

Sorting

Toolpad Studio data sources support server-side sorting. To achieve this you'll have to consume the sortModel property that is passed to the getRecords method:

export default createDataProvider({
  async getRecords({ sortModel }) {
    console.log(sortModel);
  },
});

Depending on which column has been set to sort by, this results in:

[{ field: 'name', sort: 'asc' }];

Now the backend function receives the grid sorting model from the UI in its parameters.

Uncheck the column option "sortable" if you want to disable sorting for a certain column:

Disable sortable

Disable sortable

Row editing

The data provider can be extended to automatically support row editing. To enable this, you'll have to add a updateRecord method to the data provider interface that accepts the id of the row that is to be edited, and an object containing all the updated fields from the row editing operation.

export default createDataProvider({
  async getRecords() {
    return prisma.users.findMany();
  },

  async updateRecord(id, data) {
    return prisma.users.update({ where: { id }, data });
  },
});

When this method is available in the data provider, each row gets an edit button. This edit button brings the row in edit mode. To commit the changes press the save button on the row that is in edit mode. To discard the changes use the cancel button.

You can disable the editing functionality for specific columns by unchecking the Editable option in the column definition.

Disable editable

Disable editable

Row creation

The data provider can be extended to support creating new rows. To enable this, you have to add a createRecord method to the data provider interface. This method receives an object with all values provided by the user in the creation UI.

export default createDataProvider({
  async getRecords() {
    return prisma.users.findMany();
  },

  async createRecord(data) {
    return prisma.users.create({ data });
  },
});

After you make this method available in the data provider, an "Add record" button appears in the data grid toolbar. Click this button and a new editable row appears at the top of the grid. Fill in the values and click the "Save" button to finish creating the row. You'll have to return the newly created row from the createRecord method so that the grid can update accordingly.

Deleting rows

The data provider can be extended to automatically support row deletion. To enable this, you'll have to add a deleteRecord method to the data provider interface that accepts the id of the row that is to be deleted.

export default createDataProvider({
  async getRecords({ paginationModel: { start = 0, pageSize } }) {
    return db.query(`SELECT * FROM users`);
  },

  async deleteRecord(id) {
    await db.query(`DELETE FROM users WHERE id = ?`, [id]);
  },
});

When a data provider contains a deleteRecord method, each row has a delete button. When the user clicks that delete button, the delete method gets called with the id of that row and after successful deletion, the data is reloaded.

Data provider delete

Delete action in data provider

API

See the documentation below for a complete reference to all of the functions and interfaces mentioned in this page.