Jump to content

A go on using ProcessWire forms with CAPTCHA

Recommended Posts

Hi everybody,

As much as we hate the CAPTCHA, it is still used. I decided to share my solution of using ProcessWire forms with CAPTCHA.

My goals were quite simple:

1. Create a form and customize it.

2. Get an Inputfield for a page field and append this Inputfield to the form.

3. Display a CAPTCHA.

4. Process the form and check its fields for correctness.

5. Check the CAPTCHA.

6. If steps 4-5 fail, display the form back with the user input intact.

What I found to be not quite easy was how to push a different CAPTCHA into an already submitted form. (A spoiler: this is done quite smoothly with the power of ProcessWire's Inputfields.)

Let's start, then.

My Form class has several methods:

  • appendFields() accepts an array of field names, gets their Inputfields from a template and appends these to the form
  • displayForm() adds a CAPTCHA and returns the form's HTML 
  • processForm() checks the CSRF session ID and user input and delegates CAPTCHA verification
  • getCAPTCHA() returns the html for the CAPTCHA
  • checkCAPTCHA() checks the user input against the CAPTCHA and sets the verification flag to true or false.

That's mostly it. Now to the code itself.

class Form

    public  $form;
    public  $captchaVerificationResult; // captcha verification flag (bool)    
    public function __construct () {
        // form is created upon class instantiation

        $form = wire('modules')->get("InputfieldForm");
        $form->action = wire('page')->path;
        $form->method = 'post';
        $form->attr("id+name", 'form');
        $form->attr("class", 'pure form'); // I use PureCSS class name here as an example
        // how form items are displayed can be customized

        $form->setMarkup(array (
            'list' => "\n{out}\n",
            'item' => "\n\t{out}\n\t",
            'item_label' => "\n\t\t<label class='pure-form' for='{for}'>{out}</label>",
            'item_content' => "\n\t{out}\n\t",
            'item_error' => "\n<p><span class='ui-state-error'>{out}</span></p>",
            'item_description' => "\n<p class='description'>{out}</p>",
            'item_head' => "\n<h2>{out}</h2>",
            'item_notes' => "\n<p class='notes'>{out}</p>",
        // form classes can also be customized

        $form->setClasses(array (
            'list' => 'Inputfields',
            'list_clearfix' => 'ui-helper-clearfix',
            'item' => '',
            'item_required' => '',
            'item_error' => 'ui-state-error InputfieldStateError',
            'item_collapsed' => '',
            'item_column_width' => '',
            'item_column_width_first' => '',

        $this->form = $form;

    public function appendFields ($fields) {
         * $fields is an array of field names
         * for each field name a corresponding Inputfield is fetched from a page
         * and appended to the form

        $page = wire('pages')->get("999999999"); // this is the page that has your fields

        foreach ($fields as $f_name) {

            $field = wire('fields')->get($f_name);
            $if = $field->getInputfield($page); // this page must have field name $f_name among its fields!           


         * CAPTCHA placeholder
         * create an InputfieldMarkup field,
         * give it an ID and append to the form
         * Later this Inputfield can be easily found by its ID.

        $inputfield = new InputfieldMarkup; // inputfield markup is appendable to form!        
        $inputfield->id = 'captcha1'; // id of the InputfieldMarkup with CAPTCHA


         * finally, the SUBMIT button
        $submit = wire('modules')->get('InputfieldSubmit');
        $submit->skipLabel = Inputfield::skipLabelBlank; // remove the label
        $submit->name = 'submit_save';
        $submit->value = "Save";


    public function get () {
        return $this->form;

    public function displayForm ($form) {

         * Each time the form is displayed, the CAPTCHA should be there
         * So, behore doing $form->render() let's get an InputFieldMarkup field by ID 
         * and pour some fresh CAPTCHA in it
        $form->children->findOne("id=captcha1")->value .= $this->getCAPTCHA(); 

        return $form->render();

    public function processForm ($form) {

        try {
        } catch (WireCSRFException $e) {            
            die ($this->displayForm($form));
        $this->checkCAPTCHA(); // sets class property $captcha_verification_result to false/true

        if (!$this->captcha_verification_result) {

            $form->children->findOne("id=captcha1")->value = "Wrong captcha, try again <br />"; // this will go before the CAPTCHA code
        else {
            // let's do some simple checks
            // you can and should do more of yours, of course
            $email = $form->get("b_email");
            if (filter_var($email->value, FILTER_VALIDATE_EMAIL)) {
                $email->error = "Incorrect email format";

            if ($form->getErrors()) {
            // the form contains errors, display it with user input
            else {

                echo "Thanks for the submission!"; // everything is just right

    public function getCAPTCHA () {        
         * $captcha_html = your CAPTCHA code
        return $captcha_html; // have a lovely CAPTCHA

    public function checkCAPTCHA () {
         * get some response and check it

        if ($response->failed) {

            $this->captchaVerificationResult = false;
        else {
            $this->captchaVerificationResult = true;


Here's how this can be used:

$form = new Form();

$formFields = array ("name", "email");


$newForm = $form->get();

if ($input->post->submit_save) {

    // user submitted the form, process it and check for errors
    // the form was processed successfully because nothing die()'d. Now it's time to play with what's been submitted.

else {
    echo $form->displayForm($newForm); // if it's not been submitted, just display it and the CAPTCHA

With this being just a generic idea on how to do form processing with CAPTCHA, I hope portions of this code will prove helpful in your development.

Your comments, corrections and suggestions are very much welcome.

  • Like 6

Share this post

Link to post
Share on other sites

Hats off for that code and thanks for putting the comments.

The best captcha I ever saw was a puzzle so easy that even

a child could do it, like parking a car with drag and drop,

(still) too difficult for (current) captcha breakers.

  • Like 1

Share this post

Link to post
Share on other sites

Hats off for that code and thanks for putting the comments.

The best captcha I ever saw was a puzzle so easy that even

a child could do it, like parking a car with drag and drop,

(still) too difficult for (current) captcha breakers.

Is this code you used or wrote yourself. I would love to add a little bit on top of the honey pot we used. 

Share this post

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By Peter Knight
      I have a few web forms which require testing on a weekly basis and I don't want the recipients (administrators) to receive these test emails.
      What would be a good way to test approx 15 forms from the front end and have the test delivered a list of secondary administrator recipients?
      I'm thinking that I could have some kind of config file which watches for a trigger word or email and then understands that it's a test and to bypass the normal admins?
      All of the forms ask for an email address so I could setup an email such as 'testform@email.not' etc which my config file (hook?) would watch for.
      Or is there a better way to do this?
      Additionally, I have a few extra requirements...
      Forms should goto an alternative success page. This is because I don't want my test to skew my Google Analytics conversion tracking Forms would need to be tested from the front-end and not the PW admin area Any advice appreciated.
      BTW I realise this should be posted in the proper FormBuilder support forum. I am in the process of renewing my license for access to that support forum.
    • By J_Szwarga
      What is the best practice for having a form (only accessible to a logged in user) save the user's progress between sections of the form? Fieldsets? Multiple forms?
      Use case: I have a very long application form that would take 30 minutes for the applicant to fill out and I would hate for all progress to be lost due to user error. The progress needs to be logged into the user's page.
    • By Guy Incognito
      I'm trying to implement a front-end image upload form for user profile pictures, to a field called 'profile_picture'. The code is based on the various examples found around these forums but isn't yet 100% right.
      The form successfully submits the image to the server and updates the field in the dashboard. When you submit the firm, the page reloads and uploads the image but the template still loads the old image path (now broken path as the old image has been removed).
      I can only get the new image to show if I hit enter in the address bar forcing the page to reload.
      Any ideas? Is it a caching issue, or something to do with the order of the script? It makes no difference if I call the image after the upload form.
      <?php //Display current user image $userImg = $user->profile_picture->first(); echo '<img src="'.$userImg->url.'">'; $upload_path = $config->paths->assets . "files/avatar_uploads/"; $f = new WireUpload('userimage'); $f->setMaxFiles(1); $f->setMaxFileSize(1*1024*1024); $f->setOverwrite(true); $f->setDestinationPath($upload_path); $f->setValidExtensions(array('jpg', 'jpeg', 'png', 'gif')); if($input->post->form_submit) { if(!is_dir($upload_path)) { if(!wireMkdir($upload_path)) throw new WireException("No upload path!"); } $files = $f->execute(); if ($f->getErrors()) { foreach($files as $filename) @unlink($upload_path . $filename); foreach($f->getErrors() as $e) echo $e; } else { $user->of(false); $user->profile_picture->removeAll(); // wirearray (line added by @horst: explanation is three posts beneath) $user->profile_picture = $upload_path . $files[0]; $user->save(); $user->of(true); @unlink($upload_path . $files[0]); } } ?> <form class="forum-form" accept-charset="utf-8" action="./" method="post" enctype="multipart/form-data" > <input type="file" id="attach" name="userimage" accept="image/jpg,image/jpeg,image/gif,image/png" /> <input type="submit" name="form_submit" value="Submit"/> </form>
    • By flydev 👊🏻
      Google reCAPTCHA for ProcessWire.
      This module simply adds reCAPTCHA V2 or Invisible reCAPTCHA to your form.

      How To Install
      Download the zip file at Github or from the modules repository Drop the module files in /site/modules/MarkupGoogleRecaptcha In your admin, click Modules > Refresh Click "install" for "MarkupGoogleRecaptcha" Official install/uninstall doc: http://modules.processwire.com/install-uninstall/
      You must create an API key prior to use this module. Goto https://www.google.com/recaptcha/admin to create your own. Next, add the API keys information to the module's settings.

      Call the module : $captcha = $modules->get("MarkupGoogleRecaptcha"); Call $captcha->getScript(); somewhere to get the javascript used by reCAPTCHA Render reCAPTCHA in a standard HTML <form></form> by calling $captcha->render() or Render reCAPTCHA in an InputfieldForm by passing as argument your form to the render function: $captcha->render($form) Call verifyResponse() to get the result. It return TRUE if the challenge was successful.  
      Using ProcessWire's form API : $out = ''; $captcha = $modules->get("MarkupGoogleRecaptcha"); // if submitted, check response if ($captcha->verifyResponse() === true) { $out .= "Hi " . $input->post["name"].", thanks for submitting the form!"; } else { $form = $modules->get("InputfieldForm"); $form->action = $page->url; $form->method = "post"; $form->attr("id+name", "form"); $field = $this->modules->get('InputfieldText'); $field->name = "name"; $field->placeholder = "name"; $form->add($field); // CAPTCHA - our form as argument, the function will add an InputfieldMarkup to our form $captcha->render($form); // add a submit button $submit = $this->modules->get("InputfieldSubmit"); $submit->name = "submit"; $submit->value = 'Submit'; $form->add($submit); $out .= $form->render(); // include javascript $out .= $captcha->getScript(); } echo $out;  
      Example using plain HTML Form : $captcha = $modules->get("MarkupGoogleRecaptcha"); // if submitted check response if ($captcha->verifyResponse() === true) { $out .= "Hi " . $input->post["name"] . ", thanks for submitting the form!"; } else { $out .= "<form method='post' action='{$page->url}'>\n" . "\t<input type='text' name='name'>\n" . $captcha->render() // render reCaptcha . "\t<input type='submit'>\n" . "</form>\n"; $out .= $captcha->getScript(); } echo $out;  

    • By Kiwi Chris
      Forms are an essential part of most websites, and it's no surprise that there's an excellent premium module Form Builder but what if you're on a zero budget for whatever reason?
      It is possible to build forms quickly and easily by making use of a couple of free modules and the admin UI to give you a great deal of flexibility and speed of development, particularly if you need multiple forms on a website with different fields.
      1. First you're going to need to install a couple of modules:
      Form Template Processor
      Fieldtype Select External Option
      2. For each form that you want to display, create a template without a template file and add fields to it as you normally would. (eg I have formContact, formRegister etc)
      Tip: under the Advanced tab in the setup for each template, I add a tag Forms so that all my forms templates are nicely grouped together in admin.
      3. Create a new field of type Select External Option and call it formTemplate
      In the section Create options from any database table select
      templates as the source table id as the Option Value name as the Option Label 4. Create a new template file and call it renderForm.php (or whatever else you like)
      Add an email field to this form - This will be the email address that forms get submitted to. Add the formTemplate field you previously created to this form. This will allow you to select which of the templates you previously created such as formContact, formRegister etc you want to render. Add any other fields as usual that you want to render on the page. Add the following PHP code to the template file. $recipient = $page->email; $form = $modules->get('FormTemplateProcessor'); $form->template = $templates->get($page->formTemplate->label); // required $form->requiredFields = array('contactName', 'contactEmail', 'contactMesssage'); //Optional: This can be improved by having a field in the page template with a CSV list of required fields eg $form->requiredFields = explode(',', $page->requiredFields) $form->email = $recipient; // optional, sends form as email. FormTemplateProcessor can also save forms to the database. $content .= $form->render(); //generate the form to display. Note: this doesn't actually render the form at this point, but you have it in the $content variable ready to output wherever you want in your template.
      Add any template HTML or other PHP code and echo $content; wherever you want to render the form.
      5. Create a page using the renderForm template, and provide an email address, and select a form that you want to display.
      6. Use CSS to style the form as required.
      7. View your new page, and check that the form renders correctly.
      8. You can modify the templates you created at step 2 or create new ones as required if your requirements for what fields forms display changes.
      Note: The Form Template Processor module can also save form input as pages, and the FieldType Select External Option can be set up with filtering, so this solution can probably be refined further.
  • Create New...