natbox.io

CaptchaBox / Docs / Django

Add a captcha to Django forms

Last updated 6 November 2026 · By Mohammad AlSaraf

About 5 minutes. Django 3.2 or newer; no package to install.

You will add a form field, CaptchaBoxField. Put it on any form and form.is_valid() checks the captcha for you.

1. Get your keys

Sign in to the CaptchaBox client area (a free account is enough) and add your site with its domain. You get a site key for the page and a secret for your server. Keep the secret out of your HTML and out of git: put it in an environment variable.

Add them to settings.py (read them from the environment in production):

CAPTCHABOX_SITE_KEY = os.environ["CAPTCHABOX_SITE_KEY"]
CAPTCHABOX_SECRET = os.environ["CAPTCHABOX_SECRET"]

2. The field

Save this as captchabox.py in your app:

"""CaptchaBox for Django: a form field. Copy this file into your app.

settings.py:  CAPTCHABOX_SITE_KEY = "..."   CAPTCHABOX_SECRET = "..."
forms.py:     from .captchabox import CaptchaBoxField
              class ContactForm(forms.Form):
                  email = forms.EmailField()
                  captcha = CaptchaBoxField()
Test keys that always pass, on any domain: cb_test_pass / cbs_test_pass
"""
import json
import urllib.parse
import urllib.request

from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.html import format_html

VERIFY_URL = "https://captcha.natbox.io/v1/siteverify"


def verify_captchabox(token, secret=None):
    """Asks CaptchaBox whether a token is valid. Each token works once, for 5 minutes."""
    if not token:
        return False
    data = urllib.parse.urlencode({"secret": secret or settings.CAPTCHABOX_SECRET, "response": token}).encode()
    try:
        with urllib.request.urlopen(VERIFY_URL, data, timeout=10) as r:
            return json.load(r).get("success") is True
    except OSError:
        return False


class CaptchaBoxWidget(forms.Widget):
    """Shows the widget. The widget itself adds a hidden captchabox-response field to the form."""

    def __init__(self, attrs=None, theme="auto", size="normal"):
        super().__init__(attrs)
        self.theme, self.size = theme, size

    def render(self, name, value, attrs=None, renderer=None):
        return format_html(
            '<script src="https://captcha.natbox.io/v1/widget.js" async defer></script>'
            '<div class="captchabox" data-sitekey="{}" data-theme="{}" data-size="{}"></div>',
            settings.CAPTCHABOX_SITE_KEY, self.theme, self.size)

    def value_from_datadict(self, data, files, name):
        return data.get("captchabox-response", "")


class CaptchaBoxField(forms.Field):
    widget = CaptchaBoxWidget
    default_error_messages = {"captcha": "The captcha check failed. Please try again."}

    def __init__(self, **kwargs):
        kwargs.setdefault("label", "")
        super().__init__(**kwargs)

    def validate(self, value):
        if not verify_captchabox(value):
            raise ValidationError(self.error_messages["captcha"], code="captcha")

3. Use it in a form

from django import forms
from .captchabox import CaptchaBoxField

class ContactForm(forms.Form):
    email = forms.EmailField()
    message = forms.CharField(widget=forms.Textarea)
    captcha = CaptchaBoxField()

Render the form as usual ({{ form.as_p }} and {% csrf_token %}). The field shows the widget, and is_valid() fails with "The captcha check failed" if the token is missing, forged, used or expired.

It works on ModelForms too, and on the admin login if you subclass AuthenticationForm and add the field.

A complete one-file example

Run it with python example_project.py runserver next to captchabox.py:

"""A one-file Django site that uses captchabox.py, to try it out.
Run: pip install django && python example_project.py runserver 0.0.0.0:8000
"""
import os
import sys

from django.conf import settings

settings.configure(
    DEBUG=True,
    SECRET_KEY="change-me",
    ROOT_URLCONF=__name__,
    ALLOWED_HOSTS=["*"],
    MIDDLEWARE=["django.middleware.csrf.CsrfViewMiddleware"],
    TEMPLATES=[{"BACKEND": "django.template.backends.django.DjangoTemplates"}],
    CAPTCHABOX_SITE_KEY=os.environ.get("CAPTCHABOX_SITE_KEY", "cb_test_pass"),
    CAPTCHABOX_SECRET=os.environ.get("CAPTCHABOX_SECRET", "cbs_test_pass"),
)

from django import forms  # noqa: E402
from django.http import HttpResponse  # noqa: E402
from django.middleware.csrf import get_token  # noqa: E402
from django.urls import path  # noqa: E402
from django.utils.html import format_html  # noqa: E402

from captchabox import CaptchaBoxField  # noqa: E402


class ContactForm(forms.Form):
    email = forms.EmailField()
    captcha = CaptchaBoxField()


def contact(request):
    form = ContactForm(request.POST or None)
    if request.method == "POST" and form.is_valid():
        # The visitor passed: send the email, save the message, etc.
        return HttpResponse('<p id="result">Thanks, your message was sent.</p>')
    return HttpResponse(format_html(
        '<!doctype html><meta charset="utf-8"><title>Contact</title>'
        '<form method="post"><input type="hidden" name="csrfmiddlewaretoken" value="{}">{}<button>Send</button></form>',
        get_token(request), form.as_p()))


urlpatterns = [path("", contact)]

if __name__ == "__main__":
    from django.core.management import execute_from_command_line
    execute_from_command_line(sys.argv)

Trying it out? The test keys cb_test_pass (site key) and cbs_test_pass (secret) always pass, on any domain including localhost. Swap in your own keys from the client area before going live.

Other platforms: all CaptchaBox docs. Questions? Open a ticket, we reply within 24 hours.