TechEarl

Why Your ACF Checkbox Field Returns an Array

ACF Checkbox fields return arrays because they support multiple selections. The shape varies by Return Format. Here's what each option returns, the patterns for rendering and querying, and when to switch to a Select or Radio field instead.

Ishan Karunaratne⏱️ 5 min readUpdated
Share thisCopied
ACF Checkbox returns arrays because they're multi-select. The 3 return formats, rendering patterns, query patterns, when to switch to Select or Radio.

ACF Checkbox fields return arrays because they are multi-select by design: the editor can tick more than one option. The exact shape of the returned array depends on the field's Return Format setting (Value, Label, or Both). If you expected a single value, you probably want a Select or Radio field instead. Here is what each return format gives you, how to render and query against checkbox values, and when to pick a different field type entirely.

Jump to:

Why an array, not a string

A checkbox group is a multi-select widget. The editor can tick zero, one, two, or all of the options. ACF stores the result as an array of the selected option values. Even if only one option is ticked, the value is ['option_one'], not 'option_one'. This is a deliberate design choice; it makes the contract uniform regardless of how many options the editor picked.

If the field is empty (nothing ticked), the value depends on the field setting "Allow Null". With null allowed, you get null or false. With null disallowed, you get an empty array [].

The three Return Formats

In the field group editor, the Checkbox field has a Return Format option with three values:

  • Value (default): returns the option's machine value (e.g., red, blue).
  • Label: returns the option's display label (e.g., Red, Bright Blue).
  • Both: returns an array of arrays, each with value and label keys.

Pick Value when you want to compare against known strings in code. Pick Label when you want to display the labels directly. Pick Both when you need both at the same time (rare).

Return Format: Value

php
$colors = get_field( 'colors' );
// $colors === ['red', 'blue'] or ['red'] or [] or null

This is the right default for code that compares against checkbox values:

php
if ( in_array( 'red', $colors, true ) ) {
    // show the red treatment
}

The Value format is also what you usually want when persisting checkbox values via update_field():

php
update_field( 'colors', ['red', 'blue'], $post_id );

The values you pass in must match the option values registered in the field group.

Return Format: Label

php
$colors = get_field( 'colors' );
// $colors === ['Bright Red', 'Cool Blue'] or [] or null

Useful when you only need to display the labels and never compare against them in code. Worth noting: the Label is what the editor sees in the wp-admin UI; the Value is what is stored in the database. Two options can share a label (though they shouldn't) but values must be unique.

Return Format: Both

php
$colors = get_field( 'colors' );
// $colors === [
//     ['value' => 'red',  'label' => 'Bright Red'],
//     ['value' => 'blue', 'label' => 'Cool Blue'],
// ]

Use this when you need both the machine value (for logic or URLs) and the display label (for human-facing text) in the same template:

php
foreach ( $colors as $option ) {
    printf(
        '<a href="/colors/%s">%s</a>',
        esc_attr( $option['value'] ),
        esc_html( $option['label'] )
    );
}

Rendering patterns

The basic list output:

php
$colors = get_field( 'colors' );
if ( $colors ) {
    echo '<ul>';
    foreach ( $colors as $color ) {
        printf( '<li>%s</li>', esc_html( $color ) );
    }
    echo '</ul>';
}

A comma-separated inline:

php
$colors = get_field( 'colors' );
if ( $colors ) {
    echo esc_html( implode( ', ', $colors ) );
}

A "pill" style:

php
$colors = get_field( 'colors' );
if ( $colors ) {
    foreach ( $colors as $color ) {
        printf(
            '<span class="pill pill-%s">%s</span>',
            esc_attr( $color ),
            esc_html( ucfirst( $color ) )
        );
    }
}

Querying posts by checkbox value

To query posts that have a specific checkbox value selected, the meta_query approach uses LIKE because the stored value is serialized:

php
$query = new WP_Query( [
    'post_type' => 'product',
    'meta_query' => [
        [
            'key' => 'colors',
            'value' => '"red"',
            'compare' => 'LIKE',
        ],
    ],
] );

The double quotes around red matter: ACF stores checkbox arrays as PHP-serialized data (e.g., a:2:{i:0;s:3:"red";i:1;s:4:"blue";}). Searching for "red" (with quotes) avoids matching partial strings.

This query approach is slow at scale because LIKE queries cannot use indexes efficiently. For sites with thousands of posts where checkbox values are queried frequently, the cleaner approach is to mirror the checkbox values into a custom taxonomy via the acf/save_post pattern in Useful Things You Can Do with acf/save_post. Then queries use tax_query which is much faster.

When to use Radio or Select instead

If the editor should only ever pick one option, do not use a Checkbox field. Use a Radio Button or Select field instead. The data shape becomes a single string (not an array), and the template code is much simpler:

php
$priority = get_field( 'priority' ); // 'high', 'medium', or 'low'
if ( $priority === 'high' ) {
    // ...
}

The conversion is easy: in the field group editor, change the Field Type from Checkbox to Radio or Select. If you have existing data (arrays with a single value), you will need to migrate it to single-value strings before the change takes full effect:

bash
# WP-CLI one-liner via wp eval
wp eval '
foreach ( get_posts( ["post_type" => "task", "posts_per_page" => -1, "fields" => "ids"] ) as $id ) {
    $val = get_post_meta( $id, "priority", true );
    if ( is_array( $val ) && count( $val ) === 1 ) {
        update_post_meta( $id, "priority", reset( $val ) );
    }
}
'

A common mistake: using a Checkbox field with the intent of "single-select but optional" and then surprised that single-tick gives an array. The right answer is a Select field with the "Allow Null" option enabled, not a Checkbox field with one option.

For deeper coverage of how ACF return formats work across field types, see ACF Image Field Returning an Array Instead of a URL?. For programmatic updates to Checkbox fields, see How to Update ACF Fields Programmatically.

Sources

Authoritative references this article was fact-checked against.

TagsWordPressACFForms

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

Why Your First ACF Repeater Row Appears Empty

An ACF Repeater where the first row's data looks blank is almost always one of three things: missing the_row(), incorrect sub-field name, or accidental data overwrite via update_field with the wrong field reference.

How to Update ACF Fields Programmatically

update_field() is the canonical way to write ACF data programmatically: the function signature, how to write to Repeater and Flexible Content fields, when to use field keys instead of names, options pages and user meta, and the gotchas that bite.