Best practices for securing custom field data in Field Forge.
Output Escaping
Always escape field values before output. Field Forge does not auto-escape values returned by get_field().
// Text fields -- use esc_html()
echo esc_html( get_field( 'headline' ) );
// URLs -- use esc_url()
echo '<a href="' . esc_url( get_field( 'link' ) ) . '">Click</a>';
// HTML attributes -- use esc_attr()
echo '<div class="' . esc_attr( get_field( 'css_class' ) ) . '">';
// WYSIWYG fields are already sanitized via wp_kses_post()
// Safe to output directly
echo get_field( 'body_content' );Input Validation
Field Forge validates values on save. Text fields check maxlength, number fields check min/max/step, email fields validate format. When saving programmatically, ensure your values are valid:
// Sanitize before update_field()
$email = sanitize_email( $_POST['user_email'] );
if ( is_email( $email ) ) {
update_field( 'contact_email', $email, $post_id );
}
$price = floatval( $_POST['price'] );
if ( $price >= 0 ) {
update_field( 'price', $price, $post_id );
}REST API Security
Mutation endpoints (POST, PUT, DELETE) require the manage_options capability. Read endpoints (GET) require an authenticated user with editor-tier capabilities (read_private_posts or edit_posts); the per-post values endpoint (GET /fields/{post_id}) uses current_user_can('read_post', $post_id) so private posts are gated the same way WordPress core gates post meta. Anonymous requests always get 401. Field groups marked private (or restricted by location rules to administrators) are filtered out of non-admin GET responses. The public fieldforge property on core WordPress post endpoints only exposes opt-in fields as described in section 20. See section 20 for the full capability matrix per endpoint.
// Restrict REST API access to specific fields
add_filter( 'fieldforge/rest/public_field_allowed', function( $allowed, $field, $post_id ) {
return ( $field['name'] ?? '' ) === 'public_subtitle' ? true : $allowed;
}, 10, 3 );Password and Sensitive Fields
The password field type stores values as plain text. For sensitive data:
// Encrypt on save
add_action( 'fieldforge/value_updated', function( $post_id, $field_name ) {
if ( $field_name === 'api_secret' ) {
$raw = FIELDFORGE_Field_Values::instance()->get_value( 'api_secret', $post_id );
$encrypted = openssl_encrypt( $raw, 'aes-256-cbc', AUTH_KEY, 0, substr( AUTH_SALT, 0, 16 ) );
FIELDFORGE_Field_Values::instance()->update_raw( 'api_secret', $encrypted, $post_id );
}
}, 10, 2 );
// Decrypt on load
add_filter( 'fieldforge/load_value', function( $value, $field_name ) {
if ( $field_name === 'api_secret' && $value ) {
return openssl_decrypt( $value, 'aes-256-cbc', AUTH_KEY, 0, substr( AUTH_SALT, 0, 16 ) );
}
return $value;
}, 10, 2 );—