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
/**
* 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();
}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.
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:
schv_register_schema('/contact', schv_stored_schema('contact'));3. File Upload Validation
Validate $_FILES with validateFiles() and restrict the allowed MIME types.
<?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();
}4. CSRF Token Protection
Embed a token in a hidden field with createToken() and verify it on submission using checkToken().
<?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();
}5. Mail Template Rendering
Use schv_template() to inject validated data into a WP options template.
<?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'));
}6. Multi-page Form (Input → Confirm → Complete)
Use schv_form() to persist data in the session and implement a form spanning three pages.
<?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>';
});