Web Development

Building a DataTables Solution Using Craft CMS

Craft cms datatables integration

Whether you're new to Craft CMS or have been using this incredible framework for some time, extending it can sometimes be challenging.

One of the challenges I recently encountered for a new client's site was building an Ajax-friendly "tables" solution. Since I tend to lean toward custom development over plugins, I decided to tackle this challenge head-on.

Most web developers are familiar with DataTables, the standard for building an organized, responsive table, typically powered by a database.

The challenge was whether to build it using Twig templates or through a custom controller.

I'll dive into both options.

What is DataTables?

First up, though, what is DataTables? In my opinion, it's the most flexible and versatile jQuery plugin that turns standard HTML tables into feature-rich, interactive tables. It provides a straightforward way to implement complex table functionalities without extensive custom coding.

Some of the key features of DataTables include:

  • Pagination: Automatically splits data into pages, reducing load times and improving user experience.
  • Search: Provides instant search capabilities across all table data, making it easy to find specific information quickly.
  • Sorting: Allows column sorting by clicking on the column headers, supporting multiple data types, including text, numbers, and dates.
  • Ajax Integration: Enables loading data from various sources, including server-side processing for handling large datasets efficiently.
  • Customization: Offers extensive customization options, from styling to advanced configuration settings, allowing developers to tailor the table to specific needs.

If you're opposed to jQuery or looking for a vanilla javascript tables solution, this guide might still help you with some key concepts of accessing Craft entries. I personally love jQuery and still use it today in most projects.

A Simple Twig-based Example

Ok, let's get into the basics.

If you're compiling less than 1,000 rows of data, a custom controller is completely unnecessary. Craft can easily retrieve your data, and DataTables can render your table with some basic structure.

Let's say you have employee data you would like to display. The table might look like this:

Name Position Start date Salary
John Doe CEO 02/15/1990 $300,000
Jane Smith Accountant 12/25/2005 $100,000
Bob Johnson Programmer 10/05/2015 $200,000

It's a fairly straightforward table display.

Now, let's put the same table into a Twig template, retrieve our data, and add DataTables.

{% set entries = craft.entries.section('employees').all() %}

<table id="example" class="display">
            <th>Start date</th>
        {% for entry in entries %}
            <td>{{ entry.name }}</td>
            <td>{{ entry.position }}</td>
            <td>{{ entry.startDate|date('m/d/Y') }}</td>
            <td>{{ entry.salary }}</td>
        {% endfor %}

    $(function() {

That's all it takes! The above example will provide a fully functional DataTables integration for your website.

Of course, you will need to include jQuery and the DataTables scripts. Make sure jQuery is loaded first.

A Controller-based DataTables Solution

Whether you're building a custom Craft CMS plugin or module, you will need a controller to process the logic and fetch your results using Ajax.

The link above covers some basics for starting a plugin or module. This example below will only focus on the controller component.

First, the HTML can be drastically simplified into the following:

<table id="example" class="display"></table>

Invoking DataTables gets a bit more involved. There are two methods to retrieve your data, either using GET or POST.

With GET, you will not need to factor CSFR. Also, since POST is generally more secure and doesn't limit us with URL length, I'll provide that example to be comprehensive.

    $(function() {
        new DataTable('#example', {
            language: {
                search: "_INPUT_",
                searchPlaceholder: "Search Employees"
            lengthMenu: [ 10, 25, 50, 100 ],
            responsive: true,
            autoWidth: false,
            serverSide: true,
            processing: true,
            ajax: {
                url: '{{ url("datatable") }}',
                type: 'POST',
                data: function (d) {
                    $.extend(d, {'{{ craft.app.config.general.csrfTokenName|e('js') }}' : '{{ craft.app.request.csrfToken|e('js') }}'});
                error: function (xhr, error, code) {
                    console.log(xhr, code);
            columns: [
                { title: 'Name' },
                { title: 'Position' },
                { title: 'Start date' },
                { title: 'Salary' }
            order: [[ 4, "desc" ]]

This is a complete DataTables POST example with extra critical components. Let's discuss.

First, if you're using CSRF (and definitely should), your Ajax call will fail without including it in a key/value pair as outlined.

I'm also providing a reload() call on error, should the CSRF expire with an additional debugging option for the developer tools console.

And to get fancy, I've included a custom search placeholder, paging lengths, and ordering on the fourth column.

Now, let's get into the controller needed to process the data.

namespace yournamespace\yourplugin\controllers;

use craft\web\Controller;
use craft\elements\Entry;
use yii\web\Response;

class DataTableController extends Controller
    protected array|int|bool $allowAnonymous = true;

    public function actionEntries(): Response

        $request = \Craft::$app->getRequest();
        $start = $request->getBodyParam('start', 0);
        $length = $request->getBodyParam('length', 10);
        $search = $request->getBodyParam('search');
        $searchValue = isset($search['value']) ? $search['value'] : '';

        // Base query
        $baseQuery = Entry::find()->section('employees');

        // Apply search filter if search value is provided
        if ($searchValue) {
            $baseQuery->search('*' . $searchValue . '*');

        // Total records count
        $totalRecords = Entry::find()->section('employees')->count();
        $filteredRecords = $baseQuery->count();

        // Fetch the paginated records with search applied
        $employees = $baseQuery

        // Format the data as required by DataTables
        $data = [];
        foreach ($employees as $entry) {
            $data[] = [
                'name' => $entry->name,
                'position' => $entry->position,
                'startDate' => $entry->startDate->format('m/d/Y'),
                'salary' => $entry->salary,

        return $this->asJson([
            'draw' => $request->getBodyParam('draw'),
            'recordsTotal' => $totalRecords,
            'recordsFiltered' => $filteredRecords,
            'data' => $data

Quite a bit is happening here, so let me briefly explain.

First, I'm going to assume you know how to define a route, either through an event handler or route registration.

Next, for the search to work properly, you must enable the "Use this field’s values as search keywords" in the field settings. If the search is not working, try updating the search index using: php craft resave/entries --update-search-index at the command line.

This controller assumes you have a section called employees with the appropriate fields. Again, this is only an example -- you can call the section and fields anything you'd like to suit your needs.

The example controller code provides paging and sorting. Most everything is here to get you on the right track!


In this guide, we walked through the process of integrating DataTables with Craft CMS to display dynamic tables. We covered how to set up a basic environment, create a custom controller to handle POST requests and initialize DataTables with server-side processing.

Here are some additional resources you might find helpful:

By leveraging the flexibility and power of DataTables, you can enhance your Craft CMS projects with feature-rich tables that support your unique data display needs!