Using Cloudflare Turnstile in Editor

Friday 10th October, 2025
By Allan Jardine

If you visited the DataTables website over the last few weeks, you will have noticed that I ran a survey asking for feedback about DataTable, in order to help plot the course for future development (the survey has now ended, but feedback is always welcome in the forum!).

I used Editor to easily construct the survey form, and wrote about it in the last blog post. In this post, I'll discuss the anti-bot feature used in the form - Cloudflare Turnstile. Over the course of this article, I'll build an Editor field plugin to make use of Turnstile in Editor seamless for you and your users.

Example survey output

What is Turnstile?

If you've spent any time developing applications for the public internet, you'll know all too well how much havoc bots and spam can cause. Any public-facing application needs to be developed with the fact that it is likely to be attacked in mind, particularly those that aren't behind a login wall. You'll also likely be familar with captcha's such as reCaptcha or hCaptcha, and likely be equally sick of picking images that show traffic lights or bicycles.

Cloudflare Turnstile provides a simple checkbox option to end users that will verify they are not a bot - no annoying puzzle!

The way this works is on the client-side, you load a piece of JavaScript from Cloudflare, create an HTML element and then initialise it with this API. When the user clicks the displayed checkbox a token is generated and submitted to the server as part of the form. The server can then validate that token with Cloudflare, letting you easily verify that the form was submitted by a human.

Client-side with Editor

To integrate Turnstile with Editor on the client-side, we need to create an Editor field type plug-in, allowing us to then use field.type to specify a Turnstile field in any Editor form.

Most of the logic will happen when creating the form, as this is a read-only field, and it can't take an existing value on edit. Our requirements for this field are:

  • Insert the Turnstile widget where the field input would be
  • A new key is required for each form submission
  • The Turnstile JavaScript should be loaded automatically
  • The site key must be configurable.

Rather than detailing how to create a field type plug-in (see the documentation for that, or this older blog post), let's just jump stright into the code:

DataTable.Editor.fieldTypes.turnstile = {
    create: function (conf) {
        let ts;
        var init = function () {
            conf._widgetId = window.turnstile.render('#turnstile-container', {
                sitekey: conf.siteKey
            });
        };

        // Load the Turnstile script if it hasn't already been loaded on the page
        if (!window.turnstile) {
            ts = document.createElement('script');
            ts.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js';

            document.body.append(ts);
        }

        conf._input = document.createElement('div');
        conf._input.id = 'turnstile-container';

        // When the form is displayed, initialise Turnstile
        this.one('open', function () {
            if (window.turnstile) {
                init();
            }
            else if (ts) {
                // Wait for the script to load
                ts.onload = function () {
                    init();
                };
            }
        });

        this.on('postSubmit', function () {
            window.turnstile.reset(conf._widgetId);
        });

        return conf._input;
    },

    disable(conf) {},

    enable(conf) {},

    get: function (conf) {
        return turnstile.getResponse(conf._widgetId);
    },

    set: function (conf, val) {}
};

Breakdown:

  • Lines 5-7: Initialise Turnstile with a site key that is provided in the field's configuration object. This is wrapped in a function so it can be called from different locations. Note that the return value is stored in _widgetId - this is so it can be referred to in the get function.
  • Lines 11-16: If the global turnstile object isn't available, then the Turnstile JavaScript hasn't been loaded, and thus we need to do that. A script element is created and inserted into the document to do that.
  • Lines 18-19: This is the element into which the visible widget that the end user interacts with will be put. Turnstile uses a selector to pick the element out of the document, so we assign an id attribute to the element.
  • Lines 22-32: When the Editor for is displayed, we want to initialise the Turnstile widget. This can happen immediately if the Turnstile JavaScript has already been loaded, or we might need to wait for that to happen (from lines 11-16).
  • Lines 34-36: After a submit, we want the user to reconfirm that they are human - so use the API to reset the widget.
  • Line 46: When the form is submitted, get is called to get the value to send. This is the token that needs to be validated at the server-side.

Use

To make use of our new plug-in, you need to set the field.type option to turnstile for the field to be used. You'll also need to set field.name - this is the name of the parameter that we'll use on the server-side to validate the data, and a siteKey parameter. This might look something like:

const editor = new DataTable.Editor({
    ajax: '/api/submit',
    fields: [
        // ... other fields
        {
            label: 'Verify:',
            name: 'turnstile',
            type: 'turnstile',
            siteKey: '...my-site-key...'
        }
    ],
    table: '#myTable'
});

And that's it on the client-side!

Server-side

Once the client-side has submitted a validation token, we now need to validate that it is a valid token. Since the token is submitted as a regular Editor field, we can use Editor's field validation abilities. The server-side libraries that ship for Editor with PHP, Node.js and .NET all support custom validators, which we can use for Turnstile.

The Turnstile documentation also provides examples for how to perform the validation - we'll use the functions provided there here (please note that I haven't used the remote IP address option in the below for brevity, you might wish to include this as extra information for CloudFlare - see the CloudFlare Turnstile docs for details).

PHP

For validation, we want to create a field, but note that it doesn't exist on the database, so we use the ->get() and ->set() methods to disable reading and writing - we are only interested in if the token is valid or not! The field name is set to the same as the field name from above (field.name):

Field::inst( 'turnstile' )
    ->get(false)
    ->set(false)
    ->validator( function ( $val, $data, $field, $host ) {
        $secret_key = 'your-secret-key';
        $result = validateTurnstile($val, $secret_key);

        return $validation['success']
            ? true
            : 'Please verify that you are human!';
    } );

The validateTurnstile function is provided in the Turnstile documentation.

Node.js

Similar to the PHP version, we make use of validateTurnstile from the Turnstile documentation for JavaScript (note that this function uses a global SECRET_KEY token, rather than passing it into the function):

new Field("turnstile")
    .get(false)
    .set(false)
    .validator((val, data, host) => {
        const validation = await validateTurnstile(val);

        return validation.success
            ? true
            : 'Please verify that you are human!';
    })

.NET

The .NET variant is very similar in terms of the custom validator for an Editor field. In this case we make use of the TurnstileService class that the Turnstile documentation provides:

new Field("turnstile")
    .Get(false)
    .Set(false)
    .Validator((val, data, host) => {
        var validation = await _turnstileService.ValidateTokenAsync(val);

        return validation.Success
            ? null
            : "Please verify that you are human!";
    })

Wrapping it up

The plug-in developed here is available in the Editor plug-ins, and was used successfully during the DataTables survey.

In fact, it was so successful, if you've logged in to this site recently, you might have noticed that it is now part of the sign-up / registration form! I've resisted having a captcha for many years as I find them very frustrating, but Turnstile is such a low-resistance solution that I'm happy to integrate it, and as you've seen in this post, it is super easy to use in Editor.