Skip to content

Reading values in PHP ​

Every entity VX can hold fields for has a facade class with the same field() method:

php
$class    = \VxFramework\Post::get( get_the_ID() );
$style    = \VxFramework\Term::get( $term_id );
$user     = \VxFramework\User::get( get_current_user_id() );

Option pages have the same facade: \VxFramework\Option_Page::get( 'site_settings' ).

field( $key ) returns a Field_Value, which you read in the shape you need:

php
echo esc_html( $class->field( 'tagline' )->string() );
$capacity = $class->field( 'capacity' )->int();
$is_free  = $class->field( 'free_entry' )->bool();

Reading a field that doesn't exist, or is empty, never throws. It returns an empty value, so ->string() gives '' and ->int() gives 0.

->int() drops decimals, and a Number or Stepper accepts them whatever its step. Read one with ->float(), or let ->value() return whichever of the two the stored value is.

php
if ( $class->field( 'tagline' )->filled() ) {
    echo '<p class="tagline">' . esc_html( $class->field( 'tagline' )->string() ) . '</p>';
}

The natural shape: value() ​

value() returns each field type's natural PHP shape, so you don't have to pick an accessor:

Field typesvalue() returns
Text, Textarea, Email, Phone, URL, Color, Rich text, dates and timesstring
Number, Stepper, Sliderint, float or null
Switcherbool
Select, Radio Buttons, Button Group[ 'value' => …, 'label' => … ]
Multiselect, Checkboxesa list of [ 'value', 'label' ]
Liststring[]
Image, Video, Audio, SVGa Media_Value, or null
GalleryMedia_Value[]
Select Post, Select User, Taxonomya facade, or a list of them
Group, Code, Date range, Range slideran associative array
Repeatera list of associative arrays
FilesMedia_Value[]
Post Relation, User Relationa facade, or a list of them
Locationan associative array
Field Table, Content sections, Recurring Datea list of associative arrays
Password, layout fieldsnull

raw() gives the value exactly as stored, and the typed accessors below force a specific shape.

Choices ​

php
$class->field( 'price_range' )->option();        // 'mid'   (the stored value)
$class->field( 'price_range' )->option_label();  // '$$'    (the label)
$class->field( 'amenities' )->options();         // [ 'wifi', 'parking' ]
$class->field( 'amenities' )->option_labels();   // [ 'Wi-Fi', 'Parking' ]

Labels come from the field's choices, so they match what dynamic tags show.

Media ​

php
$logo = $class->field( 'logo' )->image();          // ?Media_Value
echo esc_url( $logo?->url( 'medium' ) );           // a size, falling back to full
echo esc_attr( $logo?->alt() );

foreach ( $class->field( 'photos' )->gallery() as $photo ) {
    echo wp_get_attachment_image( $photo->id(), 'large' );
}

A Media_Value has id(), url( $size ), alt(), title(), caption(), description(), mime() and path().

Dates ​

php
$opens = $class->field( 'class_date' )->date();  // ?Date_Value
echo esc_html( $opens?->format( 'j F Y' ) );
$opens?->datetime();                               // ?DateTimeImmutable
$opens?->timestamp();

A stored date is the calendar date the editor picked, so it is read in the site's timezone (Settings → General): 2026-03-01 prints as 1 March wherever the site is.

Posts, users and terms ​

php
foreach ( $class->field( 'instructors' )->posts() as $instructor ) {   // \VxFramework\Post[]
    echo esc_html( $instructor->get_title() );
    echo esc_html( $instructor->field( 'tagline' )->string() );
}

$manager = $class->field( 'organizer' )->user();   // ?\VxFramework\User
$ids     = $class->field( 'instructors' )->ids();     // int[]
$styles  = $class->field( 'style' )->terms();      // \VxFramework\Term[]

Groups and rows ​

php
// Group: one record
$address = $class->field( 'address' )->group();
echo esc_html( $address?->field( 'city' )->string() );

// Repeater: a collection of rows
foreach ( $class->field( 'opening_hours' )->rows() as $row ) {
    printf( '%s: %s–%s', esc_html( $row->field( 'day' )->string() ),
        esc_html( $row->field( 'opens' )->string() ), esc_html( $row->field( 'closes' )->string() ) );
}

$days = $class->field( 'opening_hours' )->rows()->map( fn( $row ) => $row->field( 'day' )->string() );

Collections also have first(), last(), count(), filter(), is_empty() and array access.

For Content sections, check each row's layout before reading its fields. A row's field() only sees its own layout's fields:

php
foreach ( $page->field( 'sections' )->rows() as $section ) {
    if ( $section->layout() === 'hero' ) {
        echo esc_html( $section->field( 'heading' )->string() );
    }
}

Formatting output ​

Escape values for where you print them, as with any WordPress data. For formatted fields, use the render helpers, which apply the same cleaning VX uses everywhere:

php
echo \VxFramework\render_rich_text( $class->field( 'description' )->string() );
echo \VxFramework\render_code( $class->field( 'embed' )->raw() );         // <pre><code>, escaped

\VxFramework\render_markdown() renders a Markdown field as HTML. It's defined on every site, so a theme can call it unguarded; where nothing renders Markdown, it prints the text escaped, as paragraphs.

Each accepts the field's config array as a second argument, to respect per-field settings.

For a Location, \VxFramework\geo_component( $value, 'locality' ) returns one part of the address, and \VxFramework\geo_format( $value, '{street} {street_number}, {postcode} {locality}' ) builds a formatted string, dropping separators around empty parts.

These two exist only where the Location field does. In a theme that may run on a site without it, check first:

php
if ( function_exists( '\VxFramework\geo_format' ) ) {
    echo esc_html( \VxFramework\geo_format( $class->field( 'venue' )->value(), '{locality}, {region_code}' ) );
}

Finding posts ​

\VxFramework\Post::query( $args ) accepts the same arguments as get_posts() and returns \VxFramework\Post objects:

php
foreach ( \VxFramework\Post::query( [ 'post_type' => 'class', 'posts_per_page' => 10 ] ) as $class ) {
    echo esc_html( $class->get_title() );
}

Reading many posts ​

In a loop, reading the same field on every post costs a database query per post. Pass the list to \VxFramework\Post::prime() first, and the reads in the loop come from the cache:

php
$classes = \VxFramework\Post::query( [ 'post_type' => 'class', 'posts_per_page' => 20 ] );
\VxFramework\Post::prime( $classes );

foreach ( $classes as $class ) {
    echo esc_html( $class->field( 'level' )->string() );
}

prime() takes \VxFramework\Post objects, WP_Post objects or post IDs, such as a WP_Query's posts.

It also loads Post Relation fields in both directions, with one query per relation for the whole list rather than one per post.

Reading meta directly ​

Prefer the accessors. If you must read meta yourself: the field key is the meta key (no fieldset prefix); lists, groups, repeaters and media lists are stored as JSON strings; choice fields store values, not labels; and a Taxonomy field on a post is a normal term assignment, readable with get_the_terms().

Technical details for LLMs
  • Facades: \VxFramework\Post, \VxFramework\Term, \VxFramework\User (get(), query(), field(), has_field(), fields(), field_keys(), get_fieldset_values(), save_fieldset_values(); Post also get_id(), get_title(), get_link(), get_status(), and static prime( $posts ), which fires vxframework/fields/prime with ( 'post_type', $ids )).
  • \VxFramework\Option_Page (get( $key ), field()) reads option page values.
  • Field_Value: exists, key, type, label, config, raw, is_empty, filled, value, string, string_or_null, int, float, bool, url, option, option_label, options, option_labels, date, media, image, media_all, gallery, posts, post, users, user, terms, term, ids, group (→ Field_Row), rows (→ Field_Collection of Field_Row), count.
  • value() shapes come from the same descriptor as dynamic data; a custom field type can override php_value( array $field, $raw ) (non-null wins).
  • Content sections value() returns [ { id, layout, layout_label, fields } ] like REST.
  • \VxFramework\get() / \VxFramework\set() read and write VX configuration (vxframework_<group> options, resolvable groups merged with code), not field values.
  • Server-side VX Script: \VxFramework\Modules\Dynamic_Data\Expression::render( $expr, [ 'post_id' => … ] ) (string) and render_nullable().

Documentation for the VX Framework.