WP Manifestindependent plugin directory
manifest / forms / wenprise-forms

Wenprise Forms WP Registry grade A. No security issues foundWP RegistryNo security issues foundOpen the reportA

Extend Nette Forms for WordPress use.

★ 6stars
v3.1.3latest · 5mo ago

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/iwillhappy1314/wenprise-forms/archive/refs/heads/master.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Extend Nette Forms for WordPress use.

Basic usage: Nette Form

Features

  • Supports both Composer library mode and WordPress plugin mode
  • Extends Nette Forms with WordPress-ready fields and UI widgets
  • Includes form validation, conditional display, datastore integration, and AJAX-friendly inputs

Requirements

  • PHP >=8.3
  • ext-json
  • nette/forms ^3.2
  • nette/utils ^4.1
  • WordPress >=6.0 (recommended)

Installation

Option A: Install as a Composer library

composer require wenprise/forms

Option B: Install as a WordPress plugin

  1. Clone or download this repository into wp-content/plugins/wenprise-forms.
  2. Run composer install inside the plugin directory.
  3. Activate Wenprise Forms in WordPress admin.

Runtime Modes

Composer library mode

  • Your project is responsible for loading Composer autoload.
  • Your code decides when and where to bootstrap the form objects.

WordPress plugin mode

  • Plugin entry file wenprise-forms.php loads vendor/autoload.php automatically.
  • Plugin bootstraps with new \Wenprise\Forms\Init();.

Quick Start (Minimal Working Example)

Forms has its own HTTP request handling flow. Instantiate it before head_sent, usually on init.

<?php
use Wenprise\Forms\Form;
use Wenprise\Forms\Helpers;
use Wenprise\Forms\Renders\DefaultFormRender;

add_action('init', function () {
    global $demo_form;

    $demo_form = Helpers::get_form();
    $demo_form->setRenderer(new DefaultFormRender('horizontal'));
    $demo_form->setMethod('POST');
    $demo_form->setAction(home_url('/'));
    $demo_form->addText('first_name', 'First Name')->setRequired();
    $demo_form->addSubmit('send', 'Save');
});

add_shortcode('wenprise_form_demo', function () {
    global $demo_form;

    if ($demo_form->isSuccess()) {
        $values = $demo_form->getValues();
        $first_name = $values->first_name;
        // Save or process your value here.
    }

    return (string) $demo_form;
});

Usage

Quick start

Forms includes its own HTTP request handling flow, so instantiate it before head_sent, usually in the init action.

add_action('init', function ()
{
    global $form;

    // Get forms object
    $form = Helpers::get_form();
});

Create a Form instance

use Wenprise\Forms\Form;

$form = new Form;

$form->setRenderer(new \Wenprise\Forms\Renders\DefaultFormRender('horizontal'));

// Set form method
$form->setMethod( 'POST' );
$form->setAction('https://www.example.com');

// Set form field
$form->addText('first_name', 'First Name');

// Set submit button
$form->addSubmit( 'send', 'Save' );

// Validate form and get form data
if ( $form->isSuccess() ) {

    $values = $form->getValues();

    $first_name = $values->first_name;

}   

Frontend Assets

Some fields depend on JavaScript/CSS widgets (for example: Slider, Chosen, Date Picker, Signature, Star Rating, Autocomplete, Chained Select).

  • Ensure assets are properly enqueued in your theme/plugin runtime.
  • Prefer testing one advanced field at a time when integrating into existing themes.

Security Notes

  • Always include CSRF protection for public forms ($form->addCsrf(...)).
  • For AJAX endpoints, validate nonce and capability before processing input.
  • Sanitize incoming data and escape output in templates.
  • Do not trust client-side validation alone; keep validation rules on the server side.

Set Required

$form->addText('first_name', 'First Name')
     ->setRequired();

Add rule

$form->addPassword('re_password', 'Password again:')
     ->addRule($form::EQUAL, 'Password mismatch', $form['password']);

Rule Documentation

Add field description

$form->addText('first_name', 'First Name')
     ->setOption('description', 'This is your first name.');

With Html

$form->addTextArea('description', Html::el('p')
    ->setHtml('This number remains hidden. <a href="...">Terms of service.</a>')
    );

Set condition display

$form->addColorPicker('first_name3', 'First Name')
     ->setHtmlAttribute('data-cond', '[name=first_name2] == 2');

Control width

setWidth() supports both Bootstrap-based renderers and TailwindGridFormRender.

$form->addText('first_name', 'First Name')
     ->setWidth(4);

You can also define responsive widths with breakpoint names:

$form->addText('first_name', 'First Name')
     ->setWidth(6, 'base')
     ->setWidth(3, 'md')
     ->setWidth(4, 'lg');

Or use the shorthand form:

$form->addText('first_name', 'First Name')
     ->setWidth(6, 3, 4);

The shorthand above means:

  • base = 6
  • md = 3
  • lg = 4

With DefaultFormRender('vertical') and InlineFormRender(), setWidth() is mapped to Bootstrap-style classes such as rs-col-md-4.

With TailwindGridFormRender('vertical'), setWidth() is mapped to grid span classes such as rs-grid-form__span-md-4.

DefaultFormRender('horizontal') keeps each field full width by design, so width settings are ignored there.

Repeater

Use addRepeater() to build repeatable field groups.

use Wenprise\Forms\Renders\GridFormRender;

$form = new \Wenprise\Forms\Form('contacts_form');
$form->setRenderer(new GridFormRender('vertical'));

$form->addRepeater('contacts', 'Contacts', function (\Wenprise\Forms\Container $row): void {
    $row->addText('name', 'Name')->setWidth(6, 3, 4);
    $row->addText('phone', 'Phone')->setWidth(6, 3, 4);
    $row->addText('email', 'Email')->setWidth(12, 6, 4);
}, 1, 5);

$form->addSubmit('send', 'Save');

addRepeater() arguments:

  • $name: repeater field name
  • $label: repeater label
  • $factory: callback used to define each row container
  • $copy_count: initial row count
  • $max_copies: optional maximum row count

Submitted values are returned as nested arrays and can be stored by the built-in datastore layer.

Multiple Submit Button

$form->addSubmit('save', 'Save');
$form->addSubmit('delete', 'Delete');

if ($form->isSuccess()) {
    if ($form['save']->isSubmittedBy()) {
        ....
    }

    if ($form['delete']->isSubmittedBy()) {
        ....
    }
}

Use html in label or description

$confirm = Html::el('span')->setHtml('I agree the <a href="#">Terms of service.</a>');

$form->addCheckbox('confirm', $confirm)->setOption('description', $confirm);

Set Datastore

$form->setDatastore(new \Wenprise\Forms\Datastores\PostMetaDatastore(1));

$form->save();

Custom Settings Page Example

Use OptionsDatastore to build a WordPress admin settings page quickly.

<?php
use Wenprise\Forms\Form;
use Wenprise\Forms\Datastores\OptionsDatastore;
use Wenprise\Forms\Renders\AdminFormRender;

add_action('admin_menu', function () {
    add_menu_page(
        'Wenprise Form Settings',
        'Form Settings',
        'manage_options',
        'wenprise-form-settings',
        'render_wenprise_form_settings_page'
    );
});

function render_wenprise_form_settings_page() {
    $form = new Form('wenprise_form_settings');
    $form->setRenderer(new AdminFormRender('vertical'));
    $form->setMethod('POST');

    $form->addText('wprs_company_name', 'Company Name')
        ->setDefaultValue(get_option('wprs_company_name', ''));

    $form->addText('wprs_support_email', 'Support Email')
        ->setRequired()
        ->addRule($form::EMAIL, 'Please enter a valid email address.')
        ->setDefaultValue(get_option('wprs_support_email', ''));

    $form->addCheckbox('wprs_enable_notifications', 'Enable Email Notifications')
        ->setDefaultValue((bool) get_option('wprs_enable_notifications', false));

    $form->addSubmit('save', 'Save Settings');

    $form->setDatastore(new OptionsDatastore());
    $form->save();

    echo '<div class="wrap"><h1>Wenprise Form Settings</h1>';
    $form->render();
    echo '</div>';
}

Tab Groups

Use tab groups to split long forms into multiple panels.

$form = new \Wenprise\Forms\Form();

$form->addTab('basic', 'Basic', true);
$form->addText('first_name', 'First Name');
$form->addText('email', 'Email');

$form->addTab('advanced', 'Advanced');
$form->addTextArea('notes', 'Notes');
$form->endTab();

// This submit button is outside tab groups.
$form->addSubmit('send', 'Save');

// Template context:
echo $form;

Tabs are rendered automatically through the existing render flow ($form->render() / echo $form), so no extra render method is required.

Submit buttons created inside tab groups are rendered outside tab panes automatically, so the save action stays visible.

You can also use underscore naming method:

$form->add_tab('basic', 'Basic', true);
echo $form;

Stepper Groups

Use stepper groups to render a progress bar with previous and next step actions.

$form = new \Wenprise\Forms\Form();

$form->addStep('campaign', 'Select master blaster campaign settings', true);
$form->addText('campaign_name', 'Campaign Name');
$form->endStep();

$form->addStep('ad_group', 'Create an ad group');
$form->addText('group_name', 'Group Name');
$form->endStep();

$form->addStep('ad', 'Create an ad');
$form->addTextArea('ad_content', 'Ad Content');
$form->endStep();

$form->addSubmit('send', 'Save');

echo $form;

You can also use underscore naming method:

$form->add_step('campaign', 'Campaign', true);

If you render inside a shortcode callback, return the form HTML instead of echoing:

add_shortcode('wenprise_form_demo', function () use ($form) {
    return (string) $form;
});

Fields

nonce field

$form->addCsrf('postform', 'Nonce invalid');

WordPress Tinymce editor

Settings

$form->addEditor('post_extra', 'Extra content', []);

Ajax uploader

$form->addAjaxUpload('photos', 'Photos', true, )
             ->setUrl(  admin_url( 'admin-ajax.php?action=upload' ) );

Uploader backend sample.

add_action('wp_ajax_upload', 'ajax_uploader');
add_action('wp_ajax_nopriv_upload', 'ajax_uploader');

function ajax_uploader()
{

    require_once(ABSPATH . 'wp-admin/includes/image.php');
    require_once(ABSPATH . 'wp-admin/includes/file.php');
    require_once(ABSPATH . 'wp-admin/includes/media.php');

    $attachment_id = media_handle_upload('file', 0);

    $thumb_url  = wp_get_attachment_thumb_url($attachment_id);
    $origin_url = wp_get_attachment_url($attachment_id);

    $thumb = get_post($attachment_id);

    $file_data = [
        'id'       => $attachment_id,
        'original' => $thumb->post_title,
        'size'     => $thumb->size,
        'state'    => 'SUCCESS',
        'title'    => $thumb->post_title,
        'type'     => $thumb->post_mime_type,
        'thumb'    => $thumb_url,
        'url'      => $origin_url,
    ];

    wp_send_json($file_data, 200);

    return false;
}

Slider input

Setting

$form->addSlider('price', 'Price', []);

Data Picker

Setting

$form->AddBirthdaypicker('_birthday', 'Date of Birth', [
    'format' => 'YYYY-MM-DD', 
    'template' => 'YYYY-MM-DD',
    'minYear' => '1900',
    'maxYear' => date("Y")
]);

Color Picker

Setting

$form->addColorPicker('color', 'Color', []);

Chosen

$choices = [
    'php'         => 'PHP',
    'javascript'  => 'JavaScript',
    'css'         => 'CSS',
    'java'        => 'Java',
];

$form->addChosen('category', 'Category', $choices);

Multi Chosen

$form->addMultiChosen('post_tags', 'Tags', $choices);

Signature Field

$options = [
   'width'      => '500',
   'height'     => '250',
   'border'     => '#999',
   'background' => '#f3f3f3',
];

$form->addSignature('first_name9', 'First Name', $options);

Star rating input

$options = [
    'displayOnly' => false,
    'showClear'   => false,
    'theme'       => 'krajee-svg',
    'step'        => 1,
    'min'         => 1,
    'max'         => 5,
];

$form->addStarRating('rating', 'Rating', $options);

Image Select

$options = [
    'light' => 'https://via.placeholder.com/64/EEEEEE/000000/?text=Light',
    'dark'  => 'https://via.placeholder.com/64/000000/FFFFFF/?text=Dark',
];

$form->addImagePicker('theme', 'Theme', $options);

Autocomplete Input

Source is an array or a url returns an array.

$form->addAutocomplete('name', 'Name')->setSource([
    [
        'value' => 'aaa',
        'data'  => 'AAA',
    ],
    [
        'value' => 'bbb',
        'data'  => 'BBB',
    ],
    [
        'value' => 'ccc',
        'data'  => 'CCC',
    ],
]);

If Source is an ajax url, the backend need return data as bellow.

suggestions: [
  { "value": "United Arab Emirates", "data": "AE" },
  { "value": "United Kingdom",       "data": "UK" },
  { "value": "United States",        "data": "US" }
]

Table input

$fields = [
    [
        'name'    => 'name',
        'display' => 'Product Name',
        'type'    => 'text',
    ],
    [
        'name'    => 'quantity',
        'display' => 'Quantity',
        'type'    => 'text',
    ],
    [
        'name'    => 'price',
        'display' => 'Unit Price',
        'type'    => 'text',
    ],
];

$values = [
    [
        'name'     => 'Macbook Pro',
        'quantity' => '1',
        'price'    => '8500',
    ],
    [
        'name'     => 'Pixel XL',
        'quantity' => '2',
        'price'    => '8500',
    ],
];


$form->addTableInput('table', 'Table', [], $fields)
     ->setDefaultValue($values);

Group Input

Set a prefix or suffix for text input.

$form->addGroupInput('day1', 'Day')
     ->setPrefix('Email')
     ->setSuffix('gmail.com');

SMS input

Send SMS code

$form->addSmsInput('phone', 'Cellphone', )
             ->setUrl(admin_url('admin-ajax.php?action=validate_cellpone'));

Backend Example


/**
 * Send SMS verification code.
 */
add_action('wp_ajax_validate_cellpone', 'validate_cellpone');
add_action('wp_ajax_nopriv_validate_cellpone', 'validate_cellpone');
function validate_cellpone()
{
    $phone = Input::get( 'phone', null );

    if ( ! $phone && is_user_logged_in() ) {
        $phone = OpenAuth::get_open_id( 'phone', get_current_user_id() );
    }

    $random = mt_rand( 100000, 999999 );

    // Save the verification code before sending SMS.
    $code       = PhoneCode::query()->firstOrCreate( [ 'phone' => $phone ] );
    $code->code = $random;
    $code->save();

    $msg = Helper::send_sms( $phone, $code->code );

    wp_send_json( $msg, '200' );
};

/**
 * SMS backend example using Yunpian API.
 */
function send_sms( $mobile, $content )
{
    $config = [
        'apikey' => 'xxxxx',
        'tpl_id' => '123456',
    ];

    // Template API endpoint
    $url = "https://sms.yunpian.com/v2/sms/tpl_single_send.json";

    $args = [
        'body' => [
            'apikey'    => $config[ 'apikey' ],
            'mobile'    => $mobile,

            // Template SMS payload
            'tpl_id'    => $config[ 'tpl_id' ],
            'tpl_value' => "#code#=$content",
        ],
    ];

    $result = json_decode( wp_remote_retrieve_body( wp_remote_post( $url, $args ) ) );

    // Return message based on gateway response
    return [
        'code' => $result->code,
        'msg'  => $result->msg,
    ];
}

Captcha Input

 $form->AddCaptcha('captcha', 'Captcha')
             ->setUrl(admin_url('admin-ajax.php?action=get_captcha'));

Chained Select

$form->addChainedSelect('chained', 'Chained Select', [
        'url'        => get_theme_file_uri('cityData.min.json'),
        'selects'    => ['province', 'city', 'area'],
        'emptyStyle' => 'none',
    ], ['province', 'city', 'area'])->setDefaultValue([001, 002, 003]);

Captcha backend sample.

/**
 * Captcha backend sample.
 * WordPress core do not use sessions, use cookie and transient instead 
 *
 * Run composer require gregwar/captcha to install requirement
 */
add_action('wp_ajax_get_captcha', 'get_captcha');
add_action('wp_ajax_nopriv_get_captcha', 'get_captcha');
function get_captcha($type)
{
    header('Content-type: image/jpeg');

    $builder    = new CaptchaBuilder();
    $captcha_id = wp_generate_uuid4();
    $expires = MINUTE_IN_SECONDS * 5;

    setcookie('wprs-security-captcha-id', $captcha_id, time() + $expires);
    set_transient($captcha_id, $builder->getPhrase(), $expires);

    $builder->build()
            ->output();
}
// Validate captcha
$captcha_id      = $_COOKIE[ 'wprs-security-captcha-id' ];
$session_captcha = get_transient($captcha_id);

Releases

Newest 25 of 60 recorded releases.

Tag
Published
v3.1.3 latest
May 19, 2026 5mo ago
May 19, 2026 5mo ago
May 19, 2026 5mo ago
May 18, 2026 5mo ago
May 14, 2026 5mo ago
May 14, 2026 5mo ago
May 14, 2026 5mo ago
May 14, 2026 5mo ago
May 14, 2026 5mo ago
Aug 27, 2025 1 year ago
Aug 27, 2025 1 year ago
Dec 24, 2024 1 year ago
Dec 16, 2024 1 year ago
Dec 16, 2024 1 year ago
Sep 19, 2024 2 years ago
Aug 15, 2023 3 years ago
Aug 15, 2023 3 years ago
Aug 11, 2023 3 years ago
Aug 6, 2023 3 years ago
Aug 5, 2023 3 years ago
May 27, 2023 3 years ago
Feb 15, 2023 3 years ago
Feb 14, 2023 3 years ago
Feb 14, 2023 3 years ago
Dec 16, 2022 3 years ago
All releases on GitHub →

These releases are tags only. The author does not attach a packaged zip, so there are no download counts to report.

A grade

Security

Wenprise Forms 1.4.1 · audited by WP Registry

No security issues found.

Audited release
1.4.1
Findings
none
Worst severity
—
Content hash
2dc44bc895384e615942b3d6…

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.