TechEarl

Why ACF True/False Fields Sometimes Behave Unexpectedly

ACF True/False fields store 1 or 0 in the database but appear as booleans in PHP. Loose-comparison bugs, empty-row defaults, conditional logic visibility, and meta_query gotchas are the four causes of unexpected behavior. Here is the fix for each.

Ishan Karunaratne⏱️ 6 min readUpdated
Share thisCopied
ACF True/False stores 1 or 0 but appears as boolean in PHP. Four causes of unexpected behavior: loose comparison, defaults, conditional logic, meta_query.

ACF True/False fields store 1 or 0 (or sometimes empty string) in the database and hydrate to a PHP boolean when read via get_field. Four things cause unexpected behavior: loose comparison bugs in templates, empty-row defaults when the field was added after data already existed, conditional logic visibility quirks in the editor, and meta_query shape gotchas. The first one trips up almost every dev at some point.

Jump to:

What True/False actually stores

In wp_postmeta the value is 1 (when ticked) or 0 (when unticked). When the field was never saved at all (a post that pre-dates the field group registration), the meta row does not exist and get_post_meta returns empty string.

get_field hydrates the value:

  • 1 (string or int) becomes PHP true.
  • 0 (string or int) becomes PHP false.
  • Missing meta becomes PHP false.
  • Empty string becomes PHP false.

So in templates you can rely on if ( get_field( 'is_featured' ) ) returning a truthy/falsy value correctly. The trouble starts when you do anything else.

Cause 1: loose-comparison bugs

php
$is_featured = get_field( 'is_featured' ); // bool true/false
if ( $is_featured == 1 ) { // works for true, false, but ALSO matches "any truthy value"
    // ...
}

if ( $is_featured === true ) { // strict; works correctly
    // ...
}

PHP's loose comparison treats true == 1, true == '1', and true == 'yes' all as true. This is fine for ACF True/False fields (where the value is genuinely a boolean), but mixing in get_post_meta raw reads (which return strings) breaks this:

php
$raw = get_post_meta( $post_id, 'is_featured', true );
// $raw === '1' (string) when true, '0' (string) when false, '' when never saved

if ( $raw ) { // '0' is truthy in PHP! This branch runs even when ACF stores false
    // BUG: this runs when the field is explicitly set to false
}

PHP's (bool) '0' is false (the one string that is falsy without being empty), but (bool) '0.0' is true. The mismatch is a classic source of "the True/False field is not working" bugs.

Fix: always go through get_field, not get_post_meta, when reading ACF True/False values. get_field does the right hydration.

Cause 2: empty-row defaults on legacy posts

When you add a True/False field to a field group that is already in use, existing posts do not get a meta row for the new field until they are saved. Until then, get_field( 'is_featured', $old_post_id ) returns false (because the meta does not exist), which may or may not match what you intended for "legacy posts."

If the field's default value (set in the field group editor) is "false," you are fine; "false by default" is consistent with "no meta exists yet." If the default is "true," you have a discrepancy: new posts default to true, old posts read as false.

Fix options:

  • Backfill the field on all existing posts. A WP-CLI script:
php
WP_CLI::add_command( 'listing backfill-featured', function () {
    $posts = get_posts( [ 'post_type' => 'listing', 'posts_per_page' => -1, 'fields' => 'ids' ] );
    foreach ( $posts as $post_id ) {
        if ( get_post_meta( $post_id, 'is_featured', true ) === '' ) {
            update_field( 'is_featured', true, $post_id ); // or whatever default
        }
    }
    WP_CLI::success( count( $posts ) . ' posts processed' );
} );
  • Handle the missing-default case at read time:
php
$is_featured = get_post_meta( $post_id, 'is_featured', true ) === '' ? true : (bool) get_field( 'is_featured', $post_id );

Ugly but explicit. The first form (backfill) is cleaner.

Cause 3: conditional logic visibility quirks

When you use a True/False field as the trigger for ACF conditional logic ("show this field group only when is_featured is true"), the rule fires based on the editor's current state, not the saved state. This is usually what you want, but there are edge cases:

  • A conditional rule based on True/False can flicker briefly in the editor as ACF JavaScript initializes.
  • Conditional rules with negated comparisons ("show when is_featured != true") behave correctly but read awkwardly. Prefer positive logic where possible.
  • A field that becomes conditionally hidden retains its stored value. The editor cannot see it, but get_field still returns it on the front-end. If you want "hidden = cleared," you need an acf/save_post hook that clears the dependent fields when the parent toggles off.

For the clearing pattern:

php
add_action( 'acf/save_post', function ( $post_id ) {
    if ( ! get_field( 'is_featured', $post_id ) ) {
        update_field( 'featured_image', null, $post_id );
        update_field( 'featured_text', '', $post_id );
    }
}, 20 );

Cause 4: meta_query shape gotchas

Querying posts where a True/False field is true:

php
$query = new WP_Query( [
    'post_type' => 'product',
    'meta_query' => [
        [
            'key' => 'is_featured',
            'value' => '1', // string, not int, because meta_value is stored as string
        ],
    ],
] );

The value should be the string '1', not the integer 1 or the boolean true. The wp_postmeta.meta_value column is text, and the comparison is string-based.

To find posts where the field is false OR unset:

php
'meta_query' => [
    'relation' => 'OR',
    [ 'key' => 'is_featured', 'value' => '0' ],
    [ 'key' => 'is_featured', 'compare' => 'NOT EXISTS' ],
],

The NOT EXISTS branch catches posts that pre-date the field. Without it, you miss those legacy posts.

To find posts where the field is true:

php
'meta_query' => [
    [ 'key' => 'is_featured', 'value' => '1' ],
],

This is unambiguous because "never saved" rows do not have '1' in meta_value.

Writing the field programmatically

update_field accepts boolean, integer, or string:

php
update_field( 'is_featured', true, $post_id );  // canonical
update_field( 'is_featured', 1, $post_id );      // also works
update_field( 'is_featured', '1', $post_id );    // also works
update_field( 'is_featured', false, $post_id );  // canonical false
update_field( 'is_featured', 0, $post_id );      // also works

ACF normalizes all of these into the stored 1 or 0. Prefer the boolean form for code clarity; reach for the integer or string forms only when interfacing with data sources that produce those types.

For the full write-side reference, see How to Update ACF Fields Programmatically.

The diagnostic order

When an ACF True/False field appears to be misbehaving:

  1. var_dump( get_field( 'is_featured', $post_id ) ) to confirm what get_field actually returns.
  2. var_dump( get_post_meta( $post_id, 'is_featured', true ) ) to see what is in the database directly. If '', the field has never been saved on this post.
  3. Check whether the field was added after the post existed. If yes, you have a backfill problem (Cause 2).
  4. If the template uses get_post_meta instead of get_field, switch to get_field (Cause 1).
  5. If meta_query returns wrong results, confirm you are using value => '1' (string) and possibly the NOT EXISTS branch (Cause 4).
  6. If conditional logic is the issue, check whether the dependent fields are being cleared when the toggle goes off (Cause 3).

In my experience, Cause 2 (backfill problems on legacy posts) is the single most common surprise on long-lived sites. The fix is a one-time backfill script and then the pattern works reliably for years.

Sources

Authoritative references this article was fact-checked against.

TagsWordPressACFTrue/False

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