# Contact Form Shortcode Without a Plugin

> Adds a [wpide_contact_form] shortcode with name, email and message fields that emails you each message, protected by a nonce, a hidden honeypot, a time trap and a per-visitor hourly limit.

- Type: PHP
- Runs: Run everywhere
- Category: [Forms](https://wpide.com/library/category/forms/)
- Tags: contact, shortcode, email
- Author: WPIDE Team
- Installs: 0
- Updated: 2026-10-28
- Page: https://wpide.com/library/form-contact-shortcode/

## Code

```php
$wpide_contact = array(
    'to'           => '',                                   // Recipient; empty = Settings > General admin email.
    'subject'      => 'New message from your website',
    'max_per_hour' => 3,                                    // Messages allowed per visitor per hour.
    'min_seconds'  => 3,                                    // Faster submissions are treated as bots.
);

add_action( 'template_redirect', function () use ( $wpide_contact ) {
    if ( empty( $_POST['wpide_contact_nonce'] ) || 'POST' !== strtoupper( isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : '' ) ) {
        return;
    }

    $back   = remove_query_arg( 'wpide_contact', wp_get_referer() ? wp_get_referer() : home_url( '/' ) );
    $finish = function ( $status ) use ( $back ) {
        wp_safe_redirect( add_query_arg( 'wpide_contact', $status, $back ) . '#wpide-contact' );
        exit;
    };

    if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['wpide_contact_nonce'] ) ), 'wpide_contact' ) ) {
        $finish( 'expired' );
    }

    $stamp = isset( $_POST['wpide_ts'] ) ? sanitize_text_field( wp_unslash( $_POST['wpide_ts'] ) ) : '';
    $parts = explode( '.', $stamp, 2 );
    $human = 2 === count( $parts ) && hash_equals( wp_hash( 'contact' . $parts[0] ), $parts[1] ) && time() - (int) $parts[0] >= $wpide_contact['min_seconds'];

    if ( ! empty( $_POST['wpide_website'] ) || ! $human ) {
        $finish( 'sent' ); // Pretend success so bots learn nothing.
    }

    $ip  = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
    $key = 'wpide_contact_' . md5( wp_salt() . $ip );
    if ( (int) get_transient( $key ) >= $wpide_contact['max_per_hour'] ) {
        $finish( 'limit' );
    }

    $name    = str_replace( array( '<', '>', '"' ), '', sanitize_text_field( wp_unslash( isset( $_POST['wpide_name'] ) ? $_POST['wpide_name'] : '' ) ) );
    $email   = sanitize_email( wp_unslash( isset( $_POST['wpide_email'] ) ? $_POST['wpide_email'] : '' ) );
    $message = sanitize_textarea_field( wp_unslash( isset( $_POST['wpide_message'] ) ? $_POST['wpide_message'] : '' ) );

    if ( '' === $name || ! is_email( $email ) || strlen( $message ) < 10 ) {
        $finish( 'invalid' );
    }

    $to   = is_email( $wpide_contact['to'] ) ? $wpide_contact['to'] : get_option( 'admin_email' );
    $body = "Name: $name\nEmail: $email\nPage: $back\n\n$message";
    $sent = wp_mail( $to, $wpide_contact['subject'], $body, array( 'Reply-To: ' . $name . ' <' . $email . '>' ) );

    set_transient( $key, (int) get_transient( $key ) + 1, HOUR_IN_SECONDS );
    $finish( $sent ? 'sent' : 'error' );
} );

add_shortcode( 'wpide_contact_form', function () {
    $messages = array(
        'sent'    => array( 'success', 'Thank you! Your message has been sent.' ),
        'invalid' => array( 'error', 'Please fill in your name, a valid email and a message of at least 10 characters.' ),
        'limit'   => array( 'error', 'You have sent several messages recently. Please try again later.' ),
        'expired' => array( 'error', 'The form expired. Please try again.' ),
        'error'   => array( 'error', 'Sorry, the message could not be sent. Please email us directly.' ),
    );
    $status = isset( $_GET['wpide_contact'] ) ? sanitize_key( $_GET['wpide_contact'] ) : '';
    $time   = (string) time();

    ob_start();
    ?>
    <div id="wpide-contact" class="wpide-form wpide-contact-form">
        <?php if ( isset( $messages[ $status ] ) ) : ?>
            <p class="wpide-form-notice wpide-form-<?php echo esc_attr( $messages[ $status ][0] ); ?>" role="<?php echo 'success' === $messages[ $status ][0] ? 'status' : 'alert'; ?>"><?php echo esc_html( $messages[ $status ][1] ); ?></p>
        <?php endif; ?>
        <form method="post" action="">
            <p><label for="wpide-contact-name">Name</label><br><input type="text" id="wpide-contact-name" name="wpide_name" required autocomplete="name" maxlength="100"></p>
            <p><label for="wpide-contact-email">Email</label><br><input type="email" id="wpide-contact-email" name="wpide_email" required autocomplete="email" maxlength="200"></p>
            <p><label for="wpide-contact-message">Message</label><br><textarea id="wpide-contact-message" name="wpide_message" rows="6" required minlength="10" maxlength="5000"></textarea></p>
            <p style="position:absolute;left:-9999px;" aria-hidden="true"><label>Website <input type="text" name="wpide_website" tabindex="-1" autocomplete="off"></label></p>
            <input type="hidden" name="wpide_ts" value="<?php echo esc_attr( $time . '.' . wp_hash( 'contact' . $time ) ); ?>">
            <?php wp_nonce_field( 'wpide_contact', 'wpide_contact_nonce', false ); ?>
            <p><button type="submit">Send message</button></p>
        </form>
    </div>
    <?php
    return ob_get_clean();
} );
```

## Notes

Add [wpide_contact_form] to any page (messages go to the admin email unless you set a recipient), and exclude that page from full-page caching so its security token stays fresh.

## How to use it

Install the free WPIDE plugin (https://wordpress.org/plugins/wpide/), find "Contact Form Shortcode Without a Plugin" in its snippet library and add it in one click; or create a new PHP snippet in WPIDE (or any code snippets plugin) and paste the code above. More snippets: https://wpide.com/library/
