Data Table - Admin Components

NoteThis component is available after Medusa v2.4.0+.

The DataTable component in Medusa UI allows you to display data in a table with sorting, filtering, and pagination.

You can use this component in your Admin Extensions to display data in a table format, especially if they're retrieved from API routes of the Medusa application.

NoteRefer to the Medusa UI documentation for detailed information about the DataTable component and its different usages.

Example: DataTable with Data Fetching#

In this example, you'll create a UI widget that shows the list of products retrieved from the List Products API Route in a data table with pagination, filtering, searching, and sorting.

Start by initializing the columns in the data table. To do that, use the createDataTableColumnHelper from Medusa UI:

src/admin/routes/custom/page.tsx
1import {2  createDataTableColumnHelper,3} from "@medusajs/ui"4import { 5  HttpTypes,6} from "@medusajs/framework/types"7
8const columnHelper = createDataTableColumnHelper<HttpTypes.AdminProduct>()9
10const columns = [11  columnHelper.accessor("title", {12    header: "Title",13    // Enables sorting for the column.14    enableSorting: true,15    // If omitted, the header will be used instead if it's a string, 16    // otherwise the accessor key (id) will be used.17    sortLabel: "Title",18    // If omitted the default value will be "A-Z"19    sortAscLabel: "A-Z",20    // If omitted the default value will be "Z-A"21    sortDescLabel: "Z-A",22  }),23  columnHelper.accessor("status", {24    header: "Status",25    cell: ({ getValue }) => {26      const status = getValue()27      return (28        <Badge color={status === "published" ? "green" : "grey"} size="xsmall">29          {status === "published" ? "Published" : "Draft"}30        </Badge>31      )32    },33  }),34]

createDataTableColumnHelper utility creates a column helper that helps you define the columns for the data table. The column helper has an accessor method that accepts two parameters:

  1. The column's key in the table's data.
  2. An object with the following properties:
    • header: The column's header.
    • cell: (optional) By default, a data's value for a column is displayed as a string. Use this property to specify custom rendering of the value. It accepts a function that returns a string or a React node. The function receives an object that has a getValue property function to retrieve the raw value of the cell.
    • enableSorting: (optional) A boolean that enables sorting data by this column.
    • sortLabel: (optional) The label for the sorting button. If omitted, the header will be used instead if it's a string, otherwise the accessor key (id) will be used.
    • sortAscLabel: (optional) The label for the ascending sorting button. If omitted, the default value will be "A-Z".
    • sortDescLabel: (optional) The label for the descending sorting button. If omitted, the default value will be "Z-A".

Next, you'll define the filters that can be applied to the data table. You'll configure filtering by product status.

To define the filters, add the following:

src/admin/routes/custom/page.tsx
1// other imports...2import {3  // ...4  createDataTableFilterHelper,5} from "@medusajs/ui"6
7const filterHelper = createDataTableFilterHelper<HttpTypes.AdminProduct>()8
9const filters = [10  filterHelper.accessor("status", {11    type: "select",12    label: "Status",13    options: [14      {15        label: "Published",16        value: "published",17      },18      {19        label: "Draft",20        value: "draft",21      },22    ],23  }),24]

createDataTableFilterHelper utility creates a filter helper that helps you define the filters for the data table. The filter helper has an accessor method that accepts two parameters:

  1. The key of a column in the table's data.
  2. An object with the following properties:
    • type: The type of filter. It can be either:
      • select: A select dropdown allowing users to choose multiple values.
      • radio: A radio button allowing users to choose one value.
      • date: A date picker allowing users to choose a date.
    • label: The filter's label.
    • options: An array of objects with label and value properties. The label is the option's label, and the value is the value to filter by.

You'll now start creating the UI widget's component. Start by adding the necessary state variables:

src/admin/routes/custom/page.tsx
1// other imports...2import {3  // ...4  DataTablePaginationState,5  DataTableFilteringState,6  DataTableSortingState,7} from "@medusajs/ui"8import { useMemo, useState } from "react"9
10// ...11
12const limit = 1513
14const CustomPage = () => {15  const [pagination, setPagination] = useState<DataTablePaginationState>({16    pageSize: limit,17    pageIndex: 0,18  })19	const [search, setSearch] = useState<string>("")20	const [filtering, setFiltering] = useState<DataTableFilteringState>({})21  const [sorting, setSorting] = useState<DataTableSortingState | null>(null)22
23  const offset = useMemo(() => {24    return pagination.pageIndex * limit25  }, [pagination])26  const statusFilters = useMemo(() => {27    return (filtering.status || []) as ProductStatus28  }, [filtering])29
30  // TODO add data fetching logic31}

In the component, you've added the following state variables:

  • pagination: An object of type DataTablePaginationState that holds the pagination state. It has two properties:
    • pageSize: The number of items to show per page.
    • pageIndex: The current page index.
  • search: A string that holds the search query.
  • filtering: An object of type DataTableFilteringState that holds the filtering state.
  • sorting: An object of type DataTableSortingState that holds the sorting state.

You've also added two memoized variables:

  • offset: How many items to skip when fetching data based on the current page.
  • statusFilters: The selected status filters, if any.

Next, you'll fetch the products from the Medusa application. Assuming you have the JS SDK configured as explained in this guide, add the following imports at the top of the file:

src/admin/routes/custom/page.tsx
1import { sdk } from "../../lib/config"2import { useQuery } from "@tanstack/react-query"

This imports the JS SDK instance and useQuery from Tanstack Query.

Then, replace the TODO in the component with the following:

src/admin/routes/custom/page.tsx
1const { data, isLoading } = useQuery({2  queryFn: () => sdk.admin.product.list({3    limit,4    offset,5    q: search,6    status: statusFilters,7    order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined,8  }),9  queryKey: [["products", limit, offset, search, statusFilters, sorting?.id, sorting?.desc]],10})11
12// TODO configure data table

You use the useQuery hook to fetch the products from the Medusa application. In the queryFn, you call the sdk.admin.product.list method to fetch the products. You pass the following query parameters to the method:

  • limit: The number of products to fetch per page.
  • offset: The number of products to skip based on the current page.
  • q: The search query, if set.
  • status: The status filters, if set.
  • order: The sorting order, if set.

So, whenever the user changes the current page, search query, status filters, or sorting, the products are fetched based on the new parameters.

Next, you'll configure the data table. Medusa UI provides a useDataTable hook that helps you configure the data table. Add the following imports at the top of the file:

src/admin/routes/custom/page.tsx
1import {2  // ...3  useDataTable,4} from "@medusajs/ui"

Then, replace the TODO in the component with the following:

src/admin/routes/custom/page.tsx
1const table = useDataTable({2  columns,3  data: data?.products || [],4  getRowId: (row) => row.id,5  rowCount: data?.count || 0,6  isLoading,7  pagination: {8    state: pagination,9    onPaginationChange: setPagination,10  },11  search: {12    state: search,13    onSearchChange: setSearch,14  },15  filtering: {16    state: filtering,17    onFilteringChange: setFiltering,18  },19  filters,20  sorting: {21    // Pass the pagination state and updater to the table instance22    state: sorting,23    onSortingChange: setSorting,24  },25})26
27// TODO render component

The useDataTable hook accepts an object with the following properties:

  • columns: The columns to display in the data table. You created this using the createDataTableColumnHelper utility.
  • data: The products fetched from the Medusa application.
  • getRowId: A function that returns the unique ID of a row.
  • rowCount: The total number of products that can be retrieved. This is used to determine the number of pages.
  • isLoading: A boolean that indicates if the data is being fetched.
  • pagination: An object to configure pagination. It accepts with the following properties:
    • state: The pagination React state variable.
    • onPaginationChange: A function that updates the pagination state.
  • search: An object to configure searching. It accepts the following properties:
    • state: The search query React state variable.
    • onSearchChange: A function that updates the search query state.
  • filtering: An object to configure filtering. It accepts the following properties:
    • state: The filtering React state variable.
    • onFilteringChange: A function that updates the filtering state.
  • filters: The filters to display in the data table. You created this using the createDataTableFilterHelper utility.
  • sorting: An object to configure sorting. It accepts the following properties:
    • state: The sorting React state variable.
    • onSortingChange: A function that updates the sorting state.

Finally, you'll render the data table. But first, add the following imports at the top of the page:

src/admin/routes/custom/page.tsx
1import {2  // ...3  DataTable,4} from "@medusajs/ui"5import { SingleColumnLayout } from "../../layouts/single-column"6import { Container } from "../../components/container"

Aside from the DataTable component, you also import the SingleColumnLayout and Container components implemented in other Admin Component guides. These components ensure a style consistent to other pages in the admin dashboard.

Then, replace the TODO in the component with the following:

src/admin/routes/custom/page.tsx
1return (2  <SingleColumnLayout>3    <Container>4      <DataTable instance={table}>5        <DataTable.Toolbar className="flex flex-col items-start justify-between gap-2 md:flex-row md:items-center">6          <Heading>Products</Heading>7          <div className="flex gap-2">8            <DataTable.FilterMenu tooltip="Filter" />9            <DataTable.SortingMenu tooltip="Sort" />10            <DataTable.Search placeholder="Search..." />11          </div>12        </DataTable.Toolbar>13        <DataTable.Table />14        <DataTable.Pagination />15      </DataTable>16    </Container>17  </SingleColumnLayout>18)

You render the DataTable component and pass the table instance as a prop. In the DataTable component, you render a toolbar showing a heading, filter menu, sorting menu, and a search input. You also show pagination after the table.

Lastly, export the component and the UI widget's configuration at the end of the file:

src/admin/routes/custom/page.tsx
1// other imports...2import { defineRouteConfig } from "@medusajs/admin-sdk"3import { ChatBubbleLeftRight } from "@medusajs/icons"4
5// ...6
7export const config = defineRouteConfig({8  label: "Custom",9  icon: ChatBubbleLeftRight,10})11
12export default CustomPage

If you start your Medusa application and go to localhost:9000/app/custom, you'll see the data table showing the list of products with pagination, filtering, searching, and sorting functionalities.

Full Example Code#

src/admin/routes/custom/page.tsx
1import { defineRouteConfig } from "@medusajs/admin-sdk"2import { ChatBubbleLeftRight } from "@medusajs/icons"3import { 4  Badge,5  createDataTableColumnHelper,6  createDataTableFilterHelper,7  DataTable,8  DataTableFilteringState,9  DataTablePaginationState,10  DataTableSortingState,11  Heading,12  useDataTable,13} from "@medusajs/ui"14import { useQuery } from "@tanstack/react-query"15import { SingleColumnLayout } from "../../layouts/single-column"16import { sdk } from "../../lib/config"17import { useMemo, useState } from "react"18import { Container } from "../../components/container"19import { HttpTypes, ProductStatus } from "@medusajs/framework/types"20
21const columnHelper = createDataTableColumnHelper<HttpTypes.AdminProduct>()22
23const columns = [24  columnHelper.accessor("title", {25    header: "Title",26    // Enables sorting for the column.27    enableSorting: true,28    // If omitted, the header will be used instead if it's a string, 29    // otherwise the accessor key (id) will be used.30    sortLabel: "Title",31    // If omitted the default value will be "A-Z"32    sortAscLabel: "A-Z",33    // If omitted the default value will be "Z-A"34    sortDescLabel: "Z-A",35  }),36  columnHelper.accessor("status", {37    header: "Status",38    cell: ({ getValue }) => {39      const status = getValue()40      return (41        <Badge color={status === "published" ? "green" : "grey"} size="xsmall">42          {status === "published" ? "Published" : "Draft"}43        </Badge>44      )45    },46  }),47]48
49const filterHelper = createDataTableFilterHelper<HttpTypes.AdminProduct>()50
51const filters = [52  filterHelper.accessor("status", {53    type: "select",54    label: "Status",55    options: [56      {57        label: "Published",58        value: "published",59      },60      {61        label: "Draft",62        value: "draft",63      },64    ],65  }),66]67
68const limit = 1569
70const CustomPage = () => {71  const [pagination, setPagination] = useState<DataTablePaginationState>({72    pageSize: limit,73    pageIndex: 0,74  })75	const [search, setSearch] = useState<string>("")76	const [filtering, setFiltering] = useState<DataTableFilteringState>({})77  const [sorting, setSorting] = useState<DataTableSortingState | null>(null)78
79  const offset = useMemo(() => {80    return pagination.pageIndex * limit81  }, [pagination])82  const statusFilters = useMemo(() => {83    return (filtering.status || []) as ProductStatus84  }, [filtering])85
86  const { data, isLoading } = useQuery({87    queryFn: () => sdk.admin.product.list({88      limit,89      offset,90      q: search,91      status: statusFilters,92      order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined,93    }),94    queryKey: [["products", limit, offset, search, statusFilters, sorting?.id, sorting?.desc]],95  })96
97  const table = useDataTable({98    columns,99    data: data?.products || [],100    getRowId: (row) => row.id,101    rowCount: data?.count || 0,102    isLoading,103    pagination: {104      state: pagination,105      onPaginationChange: setPagination,106    },107    search: {108	    state: search,109	    onSearchChange: setSearch,110    },111    filtering: {112      state: filtering,113      onFilteringChange: setFiltering,114    },115    filters,116    sorting: {117      // Pass the pagination state and updater to the table instance118      state: sorting,119      onSortingChange: setSorting,120    },121  })122
123  return (124    <SingleColumnLayout>125      <Container>126        <DataTable instance={table}>127          <DataTable.Toolbar className="flex flex-col items-start justify-between gap-2 md:flex-row md:items-center">128            <Heading>Products</Heading>129            <div className="flex gap-2">130              <DataTable.FilterMenu tooltip="Filter" />131              <DataTable.SortingMenu tooltip="Sort" />132              <DataTable.Search placeholder="Search..." />133            </div>134          </DataTable.Toolbar>135          <DataTable.Table />136          <DataTable.Pagination />137        </DataTable>138      </Container>139    </SingleColumnLayout>140  )141}142
143export const config = defineRouteConfig({144  label: "Custom",145  icon: ChatBubbleLeftRight,146})147
148export default CustomPage
Was this page helpful?
Edit this page
Ask Anything
FAQ
What is Medusa?
How can I create a module?
How can I create a data model?
How do I create a workflow?
How can I extend a data model in the Product Module?
Recipes
How do I build a marketplace with Medusa?
How do I build digital products with Medusa?
How do I build subscription-based purchases with Medusa?
What other recipes are available in the Medusa documentation?
Chat is cleared on refresh
Line break