Skip to content

Examples - WordPress

Implementation examples for an environment with the plugin activated as a WordPress plugin. Uses schv_* helper functions.

Source code: packages/example/wordpress/


1. Basic Validation

Create a validator with schv_validator() and handle form submissions via the template_redirect hook.

php
<?php
/**
 * WordPress example: Basic validation
 *
 * Usage in a theme's functions.php or a custom plugin.
 * Requires the Schemable Validator plugin to be active.
 */

use Respect\Validation\Validator as v;

// Handle form submission (call from 'template_redirect' action)
function my_handle_contact_form(): void {
  if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['my_contact_submit'])) {
    return;
  }

  $schema = [
    'name'  => v::stringType()->length(1, 50),
    'email' => v::email(),
    'body'  => v::stringType()->length(1, 1000),
  ];

  $validator = schv_validator($schema);
  $result    = $validator->validate($_POST)->getResult();

  $all_valid = array_reduce($result, fn($carry, $item) => $carry && $item['is_valid'], true);

  if ($all_valid) {
    schv_form()->save($result);
    wp_redirect(home_url('/contact-complete/'));
    exit;
  }

  // Store errors for display
  global $my_contact_result;
  $my_contact_result = $result;
}
add_action('template_redirect', 'my_handle_contact_form');

// Render form (use as shortcode or in template)
function my_render_contact_form(): string {
  global $my_contact_result;
  ob_start();
  ?>
  <form method="post">
    <?php foreach (['name', 'email', 'body'] as $field): ?>
      <?php $error = $my_contact_result[$field]['errors'] ?? null; ?>
      <div>
        <label><?php echo esc_html($field); ?></label>
        <?php if ($field === 'body'): ?>
          <textarea name="<?php echo esc_attr($field); ?>"><?php echo esc_textarea($my_contact_result[$field]['value'] ?? ''); ?></textarea>
        <?php else: ?>
          <input type="text" name="<?php echo esc_attr($field); ?>" value="<?php echo esc_attr($my_contact_result[$field]['value'] ?? ''); ?>">
        <?php endif; ?>
        <?php if ($error): ?><p class="error"><?php echo esc_html($error); ?></p><?php endif; ?>
      </div>
    <?php endforeach; ?>
    <button type="submit" name="my_contact_submit">Submit</button>
  </form>
  <?php
  return ob_get_clean();
}

View on GitHub


2. Merging a Schema Editor definition with code

The Schema Editor (admin UI) defines primitive fields such as string, email, and enum. mergeJsonSchema() combines them with code-defined logic that the GUI cannot express: file uploads, conditional requirements, custom validators, and driver injection.

php
use SchemableValidator\SV;
use SchemableValidator\Adapters\Captcha\ReCaptchaV3Driver;
use SchemableValidator\Adapters\Native\NativeImageDriver;

// 1. Load the schema created via Schema Editor (slug: "contact")
$gui = schv_stored_schema('contact')->toJsonSchema();

// 2. Add code-side fields and conditions, then merge
$schema = SV::object([
  'avatar'       => SV::file(['image/jpeg', 'image/png'], ['maxWidth' => 4096]),
  'company_name' => SV::string()->min(1)->max(200)->optional(),
])->mergeJsonSchema($gui)
  ->when('type', SV::equal('company'), ['company_name']);

// 3. Validate with drivers
$result = $schema
  ->toValidator([
    'imageDriver'   => new NativeImageDriver(),
    'captchaDriver' => new ReCaptchaV3Driver('YOUR_SECRET'),
  ])
  ->validate($_POST)
  ->validateFiles($_FILES)
  ->validateCaptcha()
  ->getResult();

The GUI-defined fields (name, email, type) and the code-defined fields (avatar, company_name) are validated together. If the same field name appears in both, the code definition takes precedence.

To expose the merged schema as a REST endpoint for client-side consumption:

php
schv_register_schema('/contact', schv_stored_schema('contact'));

3. File Upload Validation

Validate $_FILES with validateFiles() and restrict the allowed MIME types.

php
<?php
/**
 * WordPress example: File upload validation
 */

use Respect\Validation\Validator as v;

function my_handle_upload_form(): void {
  if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['my_upload_submit'])) {
    return;
  }

  $schema = [
    'attachment' => v::key('error', v::equals(UPLOAD_ERR_OK))
      ->key('name', v::oneOf(v::extension('jpg'), v::extension('png'))),
  ];

  $result = schv_validator($schema)->validateFiles($_FILES)->getResult();

  global $my_upload_result;
  $my_upload_result = $result;

  $all_valid = !empty($result['attachment']) && $result['attachment'][0]['is_valid'];
  if ($all_valid) {
    // move_uploaded_file(...) here
    wp_redirect(home_url('/upload-complete/'));
    exit;
  }
}
add_action('template_redirect', 'my_handle_upload_form');

function my_render_upload_form(): string {
  global $my_upload_result;
  $error = $my_upload_result['attachment'][0]['errors'] ?? null;
  ob_start();
  ?>
  <form method="post" enctype="multipart/form-data">
    <div>
      <label>Attachment (jpg / png)</label>
      <input type="file" name="attachment">
      <?php if ($error): ?><p class="error"><?php echo esc_html($error); ?></p><?php endif; ?>
    </div>
    <button type="submit" name="my_upload_submit">Upload</button>
  </form>
  <?php
  return ob_get_clean();
}

View on GitHub


4. CSRF Token Protection

Embed a token in a hidden field with createToken() and verify it on submission using checkToken().

php
<?php
/**
 * WordPress example: CSRF token protection
 */

use Respect\Validation\Validator as v;
use SchemableValidator\Security\CsrfGuard;

function my_handle_csrf_form(): void {
  if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['my_csrf_submit'])) {
    return;
  }

  $csrf = new CsrfGuard();
  $validator = schv_validator(['message' => v::stringType()->notEmpty()]);

  if (!$csrf->checkToken($_POST['csrf_token'] ?? '')) {
    wp_die('Invalid CSRF token.', 'Security Error', ['response' => 403]);
  }

  $result = $validator->validate($_POST)->getResult();

  global $my_csrf_result;
  $my_csrf_result = $result;
}
add_action('template_redirect', 'my_handle_csrf_form');

function my_render_csrf_form(): string {
  global $my_csrf_result;
  $csrf  = new CsrfGuard();
  $token = $csrf->createToken();
  ob_start();
  ?>
  <form method="post">
    <input type="hidden" name="csrf_token" value="<?php echo esc_attr($token); ?>">
    <div>
      <label>Message</label>
      <input type="text" name="message" value="<?php echo esc_attr($my_csrf_result['message']['value'] ?? ''); ?>">
      <?php if (!empty($my_csrf_result['message']['errors'])): ?>
        <p class="error"><?php echo esc_html($my_csrf_result['message']['errors']); ?></p>
      <?php endif; ?>
    </div>
    <button type="submit" name="my_csrf_submit">Submit</button>
  </form>
  <?php
  return ob_get_clean();
}

View on GitHub


5. Mail Template Rendering

Use schv_template() to inject validated data into a WP options template.

php
<?php
/**
 * WordPress example: Email template rendering
 *
 * Requires the Schemable Validator plugin active.
 * Template body is editable from WP Admin > Settings > Schemable Validator.
 */

function my_send_confirmation_emails(): void {
  $form = schv_form();
  $data = $form->get();

  if (!$data) {
    return;
  }

  // $option_keys comes from Plugin::keysAll() — see 02-feature-guide.md for details.
  // Here we use hardcoded keys matching the Plugin constructor defaults.
  $template = schv_template([
    'aliases' => [
      'name'  => 'name',
      'email' => 'email',
      'body'  => 'body',
    ],
    'templates' => [
      'user'  => get_option('SCHV_REPLY_FORMAT_FOR_user',  ''),
      'admin' => get_option('SCHV_REPLY_FORMAT_FOR_admin', ''),
    ],
  ]);

  $to_user  = $data['email']['value'] ?? '';
  $to_admin = get_option('admin_email');

  if ($to_user) {
    wp_mail($to_user,  'Thank you for your inquiry',  $template->get('user'));
  }
  wp_mail($to_admin, 'New inquiry received', $template->get('admin'));
}

View on GitHub


6. Multi-page Form (Input → Confirm → Complete)

Use schv_form() to persist data in the session and implement a form spanning three pages.

php
<?php
/**
 * WordPress example: Multi-page form (input → confirm → complete)
 *
 * Create three pages with slugs: my-form-input, my-form-confirm, my-form-complete
 * Add shortcodes [my_form_input], [my_form_confirm], [my_form_complete] to each page.
 */

use Respect\Validation\Validator as v;

// ── Schema ────────────────────────────────────────────────────────────────────

function my_form_schema(): array {
  return [
    'name'  => v::stringType()->length(1, 50),
    'email' => v::email(),
    'body'  => v::stringType()->length(1, 1000),
  ];
}

// ── Step 1: Input ─────────────────────────────────────────────────────────────

add_action('template_redirect', function () {
  if ($_SERVER['REQUEST_METHOD'] !== 'POST' || ($_POST['my_form_step'] ?? '') !== 'input') {
    return;
  }

  $validator = schv_validator(my_form_schema());
  $result    = $validator->validate($_POST)->getResult();
  $all_valid = array_reduce($result, fn($c, $i) => $c && $i['is_valid'], true);

  if ($all_valid) {
    schv_form()->save($result);
    wp_redirect(home_url('/my-form-confirm/'));
    exit;
  }

  global $my_form_input_result;
  $my_form_input_result = $result;
});

add_shortcode('my_form_input', function (): string {
  global $my_form_input_result;
  $r = $my_form_input_result ?? [];
  ob_start(); ?>
  <form method="post">
    <input type="hidden" name="my_form_step" value="input">
    <?php foreach (['name', 'email'] as $f): ?>
      <p>
        <label><?php echo esc_html($f); ?></label>
        <input type="text" name="<?php echo esc_attr($f); ?>" value="<?php echo esc_attr($r[$f]['value'] ?? ''); ?>">
        <?php if (!empty($r[$f]['errors'])): ?><span><?php echo esc_html($r[$f]['errors']); ?></span><?php endif; ?>
      </p>
    <?php endforeach; ?>
    <p>
      <label>body</label>
      <textarea name="body"><?php echo esc_textarea($r['body']['value'] ?? ''); ?></textarea>
      <?php if (!empty($r['body']['errors'])): ?><span><?php echo esc_html($r['body']['errors']); ?></span><?php endif; ?>
    </p>
    <button type="submit">Next</button>
  </form>
  <?php return ob_get_clean();
});

// ── Step 2: Confirm ───────────────────────────────────────────────────────────

add_action('template_redirect', function () {
  if ($_SERVER['REQUEST_METHOD'] !== 'POST' || ($_POST['my_form_step'] ?? '') !== 'confirm') {
    return;
  }
  wp_redirect(home_url('/my-form-complete/'));
  exit;
});

add_shortcode('my_form_confirm', function (): string {
  $data = schv_form()->get();
  if (!$data) {
    return '<p>No data. Please <a href="' . esc_url(home_url('/my-form-input/')) . '">start over</a>.</p>';
  }
  ob_start(); ?>
  <dl>
    <?php foreach ($data as $field => $state): ?>
      <dt><?php echo esc_html($field); ?></dt>
      <dd><?php echo esc_html($state['value']); ?></dd>
    <?php endforeach; ?>
  </dl>
  <form method="post">
    <input type="hidden" name="my_form_step" value="confirm">
    <a href="<?php echo esc_url(home_url('/my-form-input/')); ?>"> Back</a>
    <button type="submit">Submit</button>
  </form>
  <?php return ob_get_clean();
});

// ── Step 3: Complete ──────────────────────────────────────────────────────────

add_shortcode('my_form_complete', function (): string {
  schv_form()->clear();
  return '<p>Thank you! Your message has been received.</p>';
});

View on GitHub