WordPress provides many excellent plugins for creating contact forms, registration forms, feedback forms, quote requests, and other types of forms without writing code. However, there are situations where a plugin does not provide the exact functionality you need, or you want complete control over how the form is rendered and processed.
In those cases, you can create a custom WordPress form using PHP and handle the submission directly inside your plugin.
But where should you start? What should the form action be? How does WordPress know that your custom form has been submitted? How should you validate the submitted data? And how can you protect the form against unauthorized requests?
This guide walks through the complete process of creating a simple custom WordPress form, detecting its submission, verifying a WordPress nonce, validating and sanitizing input, and safely processing the submitted data.
Why Create a Custom WordPress Form?
Form plugins are usually the easiest option when you need a standard contact or lead-generation form. However, a custom form can be useful when your requirements are more specific.
You may want a custom form when:
- You need custom fields that are not supported by your existing form plugin.
- You want to connect the form directly with your own WordPress functionality.
- You need to process submitted data in a custom way.
- You want to avoid adding another plugin for a relatively small feature.
- You need complete control over the HTML, validation, and processing logic.
- You are developing a custom WordPress plugin or theme.
A custom form also provides a useful learning exercise because it demonstrates several important WordPress concepts, including hooks, shortcodes, nonces, sanitization, validation, and form processing.
How WordPress Handles a Custom Form
A basic custom form normally has three parts:
- Form rendering: The form HTML is displayed on a WordPress page.
- Submission detection: WordPress checks whether the form has been submitted.
- Processing: The submitted values are validated, sanitized, and processed.
In this example, we will use a shortcode called \\[custom_form] to display the form.
The overall flow looks like this:
- A visitor opens a page containing
\\[custom_form]. - The shortcode generates the HTML form.
- The visitor enters their name and submits the form.
- The form sends the request back to the current page.
- WordPress loads the plugin.
- The plugin checks for the custom form's nonce field.
- The nonce is verified.
- The submitted fields are validated and sanitized.
- The application performs the required processing.
What Should the Form Action Be?
One common question when creating a custom WordPress form is what should be placed in the form's action attribute.
If you want the form to submit back to the current page, you can use an empty action:
<form method="post" action="">
This causes the browser to submit the form to the current URL.
Another approach is to explicitly provide the current WordPress URL. The important point is that you do not necessarily need to create a separate PHP page just to process the form. Your WordPress plugin can intercept the POST request using a WordPress hook.
For larger or more complex applications, WordPress also provides mechanisms such as admin-post.php and the REST API. The appropriate approach depends on how the form needs to work.
Using the WordPress init Hook
The init action is commonly used by WordPress plugins to run code during the WordPress loading process.
For a simple custom form, we can hook our processing function to init:
add_action( 'init', array( $this, 'init' ) );
This allows our plugin to inspect the incoming request and determine whether the custom form has been submitted.
We can then look for a specific POST field that uniquely identifies our form:
if ( ! empty( $_POST['nonce_custom_form'] ) ) { // Process the custom form.}
The important idea here is not that the nonce alone should be treated as proof that the request is a form submission. Rather, the form should contain an identifiable field or value, and the nonce should then be verified as a separate security check.
Registering the Shortcode
We can use a shortcode to place the form anywhere that supports WordPress shortcodes.
For example:
[custom_form]
Inside the plugin constructor, register the shortcode with:
add_shortcode( 'custom_form', array( $this, 'shortcode_handler' ) );
Whenever WordPress encounters \\[custom_form], it calls the shortcode handler and inserts the generated form HTML into the page.
Creating the Plugin Class
We can organize the functionality inside a simple PHP class. One important correction to the original example is that the PHP constructor should be named __construct() with two underscores before and after the word "construct".
class CustomForm {
public function __construct() {
add_action( 'init', array( $this, 'init' ) );
add_shortcode( 'custom_form', array( $this, 'shortcode_handler' ) );
}
}
This constructor is automatically executed when we create an instance of the class.
Creating the Form HTML
Our example only requires one field: the visitor's name.
The form can be generated by the shortcode handler:
public function shortcode_handler( $atts ) {
ob_start();
?>
<form method="post" action="">
<p>
<label for="custom-form-name">Name</label>
<input
id="custom-form-name"
name="name"
type="text"
value=""
required
/>
</p>
<?php
wp_nonce_field(
'handle_custom_form',
'nonce_custom_form'
);
?>
<p>
<input type="submit" value="Submit" />
</p>
</form>
<?php
return ob_get_clean();
}
Using output buffering makes the shortcode easier to read than building the complete HTML form inside one long PHP string.
Why Use a WordPress Nonce?
Security is one of the most important parts of handling WordPress form submissions.
A WordPress nonce helps verify that a request is associated with an expected action and protects against certain types of unauthorized or forged requests.
We can generate a nonce with:
wp_nonce_field( 'handle_custom_form', 'nonce_custom_form');
The first argument identifies the action, while the second argument specifies the name of the hidden nonce field.
The generated form contains a hidden field similar to:
<input type="hidden" name="nonce_custom_form" value="..." />
When the form is submitted, WordPress receives that value along with the other POST data.
Verifying the Nonce
Generating a nonce is only half of the process. The submitted nonce must also be verified before processing the request.
if (
empty( $_POST['nonce_custom_form'] ) ||
! wp_verify_nonce(
sanitize_text_field(
wp_unslash( $_POST['nonce_custom_form'] )
),
'handle_custom_form'
)
) {
wp_die( 'Security check failed.' );
}
The nonce should be verified before performing the main processing operation.
It is also important to understand that WordPress nonces are not authentication or authorization mechanisms by themselves. If an action requires a logged-in user or a particular capability, you should also use appropriate checks such as is_user_logged_in() and current_user_can().
Validating Form Fields
After the security check, validate the submitted fields.
For example, if the name field is required:
$name = isset( $_POST['name'] )
? sanitize_text_field( wp_unslash( $_POST['name'] ) )
: '';
if ( empty( $name ) ) {
wp_die( 'Please enter your name.' );
}
Validation answers the question: Is the submitted value acceptable for this field?
For example:
- A required name should not be empty.
- An email field should contain a valid email address.
- A number field should contain an expected numeric value.
- A URL field should contain a valid URL.
- A selection field should contain one of the allowed values.
Sanitizing Submitted Data
Validation and sanitization serve different purposes.
Validation determines whether data meets your requirements.
Sanitization removes or normalizes unwanted content before the value is stored or processed.
WordPress provides several sanitization functions for common situations.
$name = sanitize_text_field( wp_unslash( $_POST['name'] ));
For an email address, you can use:
$email = sanitize_email( wp_unslash( $_POST['email'] ));
Then validate the email:
if ( ! is_email( $email ) ) { wp_die( 'Please enter a valid email address.' );}
The correct sanitization function depends on what type of data you are receiving. Do not assume that one sanitization function is appropriate for every field.
Never Trust $_POST Data
Data submitted through a browser should always be treated as untrusted input.
A user can modify form fields, send requests without using your visible form, or submit unexpected values directly to your endpoint.
For that reason, do not immediately use values such as:
$_POST['name']
Instead, check that the value exists, unslash it where appropriate, sanitize it, and validate it before using it.
This is particularly important when submitted data will be stored in the database, displayed to other users, used in an email, or passed to another system.
Handling the Form Submission
Now we can put the security and validation steps together inside our init() method.
public function init() {
if ( empty( $_POST['nonce_custom_form'] ) ) {
return;
}
$nonce = sanitize_text_field(
wp_unslash( $_POST['nonce_custom_form'] )
);
if ( ! wp_verify_nonce( $nonce, 'handle_custom_form' ) ) {
wp_die( 'Security check failed.' );
}
$name = isset( $_POST['name'] )
? sanitize_text_field( wp_unslash( $_POST['name'] ) )
: '';
if ( empty( $name ) ) {
wp_die( 'Please enter your name.' );
}
// Continue with your application-specific processing here.
}
At this point, the submitted name has passed the basic security and validation checks. You can now perform the operation your application requires.
What Can You Do With the Submitted Data?
Once the form data has been validated and sanitized, the next step depends on the purpose of your form.
You could:
- Save the submission in a custom database table.
- Create a WordPress post or custom post type.
- Send an email notification.
- Update an existing WordPress user.
- Send the information to an external API.
- Create a lead in a CRM.
- Trigger another internal WordPress action.
- Store the information as post meta or user meta.
The important principle is to complete security and validation before performing these operations.
Saving Form Data in WordPress
If your custom form needs to store submissions, you have several options.
For simple information associated with a WordPress post, post meta may be appropriate. For user-related information, user meta can sometimes be used.
For large volumes of structured submissions, a dedicated custom database table may be more appropriate.
When working directly with the WordPress database, use the WordPress database abstraction layer and prepared queries instead of concatenating untrusted values into SQL.
global $wpdb;
$table_name = $wpdb->prefix . 'custom_form_submissions';
$wpdb->insert(
$table_name,
array(
'name' => $name,
),
array(
'%s',
)
);
The exact database structure will depend on your application requirements.
Sending an Email After Submission
If your form is being used as a contact or inquiry form, you may want to send an email after a successful submission.
WordPress provides the wp_mail() function for sending email.
$message = 'New form submission from: ' . $name;wp_mail( get_option( 'admin_email' ), 'New Custom Form Submission', $message);
For production websites, email deliverability should also be considered. Depending on the hosting environment, you may need an SMTP service or transactional email provider to improve delivery reliability.
Preventing Duplicate Form Submissions
A common issue with custom forms is duplicate submissions. A visitor may refresh the browser after submitting the form, causing the browser to attempt the POST request again.
A common solution is the Post/Redirect/Get (PRG) pattern.
Instead of rendering the final response directly after processing the POST request, process the submission and then redirect the visitor to another URL.
For example:
wp_safe_redirect( home_url( '/thank-you/' ));exit;
This gives the visitor a normal GET request for the confirmation page and reduces the chance of accidental resubmission when the page is refreshed.
Displaying Success and Error Messages
A good form should tell visitors what happened after they click the submit button.
Instead of stopping the request with a generic message, you can build a more user-friendly flow that displays:
- A success message after a valid submission.
- A clear error when required fields are missing.
- A validation message when an email address is invalid.
- A security error when the request fails the nonce check.
For more advanced forms, you can preserve the entered values and display field-specific validation errors instead of losing the entire form.
Complete Custom WordPress Form Plugin
The following example combines the main concepts into a small working plugin:
<?php
/**
* Plugin Name: Custom Form
* Plugin URI: https://www.scriptbaker.com/
* Description: Creates a custom form through a shortcode and handles form submissions.
* Version: 1.0
* Author: Tahir Yasin
* License: GPL2
*/
class CustomForm {
public function __construct() {
add_action( 'init', array( $this, 'init' ) );
add_shortcode( 'custom_form', array( $this, 'shortcode_handler' ) );
}
public function init() {
if ( empty( $_POST['nonce_custom_form'] ) ) {
return;
}
$nonce = sanitize_text_field(
wp_unslash( $_POST['nonce_custom_form'] )
);
if ( ! wp_verify_nonce( $nonce, 'handle_custom_form' ) ) {
wp_die( 'Security check failed.' );
}
$name = isset( $_POST['name'] )
? sanitize_text_field( wp_unslash( $_POST['name'] ) )
: '';
if ( empty( $name ) ) {
wp_die( 'Please enter your name.' );
}
/*
* Your custom processing logic goes here.
*
* Examples:
* - Save the data.
* - Send an email.
* - Call an API.
* - Create a custom post.
*/
wp_safe_redirect( home_url( '/thank-you/' ) );
exit;
}
public function shortcode_handler( $atts ) {
ob_start();
?>
<form method="post" action="">
<p>
<label for="custom-form-name">Name</label>
<input
id="custom-form-name"
name="name"
type="text"
required
/>
</p>
<?php
wp_nonce_field(
'handle_custom_form',
'nonce_custom_form'
);
?>
<p>
<input
type="submit"
value="Submit"
/>
</p>
</form>
<?php
return ob_get_clean();
}
}
new CustomForm();
You can then place the following shortcode inside a WordPress page or post:
[custom_form]
Common Mistakes When Creating WordPress Forms
1. Trusting raw POST values
Never assume that a value received from $_POST is safe. Always apply the appropriate security, sanitization, and validation steps.
2. Forgetting nonce verification
Custom forms should include appropriate request verification. A nonce is an important part of protecting many WordPress form actions.
3. Using the wrong constructor name
PHP class constructors use __construct(). A method named construct() does not act as the modern PHP constructor.
4. Echoing nonce output incorrectly
wp_nonce_field() outputs the nonce field by default. If you need it as a returned string, pass false as the fourth parameter.
5. Not escaping output
Sanitizing data before storage or processing is not the same as escaping data when displaying it. Use an appropriate escaping function when outputting dynamic values into HTML.
6. Processing data without checking the request
Your processing function should first determine whether the request is actually intended for your form. Do not process every POST request on the website.
7. Forgetting redirects after successful submissions
Redirecting after a successful POST can help prevent duplicate submissions and gives you a cleaner success-page experience.
How to Improve a Custom WordPress Form
The example above is intentionally simple. A production-ready form may need additional functionality depending on the project.
For example, you may want to add:
- Multiple form fields.
- Email validation.
- Phone number validation.
- Dropdowns and checkboxes.
- File uploads with strict validation.
- Custom error messages.
- Database storage.
- Email notifications.
- Spam protection.
- Rate limiting.
- AJAX submission.
- REST API integration.
- CRM integration.
- Custom success pages.
- Accessible labels and keyboard-friendly controls.
- Server-side validation in addition to client-side validation.
Custom Form Security Checklist
Before putting a custom form into production, review the following:
- Use a WordPress nonce for appropriate request verification.
- Do not trust raw
$_POSTvalues. - Unsplash incoming request values with
wp_unslash()where appropriate. - Sanitize data according to its expected type.
- Validate required fields on the server.
- Escape dynamic values when displaying them.
- Use prepared database queries when writing custom SQL.
- Validate uploaded files carefully if file uploads are supported.
- Consider spam protection for publicly accessible forms.
- Use HTTPS on production websites.
- Redirect after successful POST requests when appropriate.
- Test the form with invalid, missing, and unexpected values.
Testing Your WordPress Form
Do not test only the successful submission scenario. A custom form should be tested with different types of input.
For example, check what happens when:
- The name field is empty.
- The visitor enters a very long value.
- The nonce is missing.
- The nonce is invalid.
- An unexpected field is submitted.
- The visitor refreshes the page after submission.
- The form is submitted multiple times.
- JavaScript is disabled.
- The form is accessed from a mobile device.
- The form is navigated using a keyboard.
Testing these cases helps ensure that the form is not only functional but also reliable and secure.
Custom Forms vs WordPress Form Plugins
There is no universal requirement to build every form manually.
A form plugin can save development time when you need common features such as contact forms, notifications, conditional fields, integrations, and visual form builders.
A custom implementation can make more sense when the form is tightly connected to custom business logic or when you need complete control over how the submission is processed.
The right choice depends on the requirements, maintenance needs, performance considerations, and development resources of the website.
Frequently Asked Questions
How do I create a custom form in WordPress without a plugin?
You can create a custom form using HTML and PHP inside a custom plugin or theme functionality. The form can submit a POST request, while WordPress hooks such as init can be used to detect and process the request.
What should the action attribute of a WordPress form contain?
If the form should submit to the current page, an empty action attribute can be used. More advanced implementations can submit through WordPress's built-in request handlers or REST API depending on the application's requirements.
How does WordPress know that a custom form has been submitted?
WordPress receives the HTTP request and makes the submitted POST values available to PHP. Your plugin can check for a unique form field, hidden value, or other identifier before processing the request.
What is a WordPress nonce?
A WordPress nonce is a security token used to help verify that a request is associated with an expected action. Nonces are commonly used when processing forms and other requests that change data.
Is a WordPress nonce enough to secure a form?
No. A nonce is one part of a secure form implementation. You should also validate and sanitize input, escape output, use appropriate authorization checks where required, and protect against issues such as spam or abusive requests.
Should I sanitize or validate form data?
Usually, both are needed. Sanitization prepares input for safe use, while validation checks whether the value meets the application's requirements. The exact approach depends on the type of field and how the data will be used.
Can I save custom form submissions in the WordPress database?
Yes. Depending on the type and volume of information, you can use post meta, user meta, custom post types, or a dedicated database table. The storage method should match the structure and purpose of the data.
How can I prevent a form from being submitted twice?
One common approach is to process the POST request and then redirect the visitor to a confirmation page. This Post/Redirect/Get pattern reduces accidental duplicate submissions caused by refreshing the browser.
Can I send an email after a WordPress form is submitted?
Yes. WordPress provides wp_mail() for sending email. For production websites, you should also consider reliable SMTP or transactional email configuration to improve deliverability.
Can a custom WordPress form connect to an API or CRM?
Yes. After validating and sanitizing the submission, your plugin can send the required information to an external API or CRM using an appropriate HTTP request. API authentication, error handling, timeouts, and data privacy should be considered as part of the integration.
Should I use a custom form or a WordPress form plugin?
It depends on your requirements. A plugin is often convenient for standard forms and integrations, while a custom implementation can provide more control when the form requires specialized business logic or custom processing.
Conclusion
Creating a custom WordPress form does not require a complicated architecture. The basic process is straightforward: render the form, identify its submission, verify the request, validate and sanitize the submitted values, process the data, and provide an appropriate response to the visitor.
However, security should always be considered when accepting data from users. WordPress provides useful tools such as nonces, sanitization functions, validation helpers, database APIs, and HTTP APIs that can help you build reliable custom functionality.
For simple projects, this approach can be enough to create a lightweight custom form. For larger applications, you can extend the same foundation with database storage, email notifications, AJAX, REST API integrations, CRM connections, spam protection, and custom workflows.
Last updated: