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
- Cause 1: loose-comparison bugs
- Cause 2: empty-row defaults on legacy posts
- Cause 3: conditional logic visibility quirks
- Cause 4: meta_query shape gotchas
- Writing the field programmatically
- The diagnostic order
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 PHPtrue.0(string or int) becomes PHPfalse.- 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
$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:
$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:
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:
$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_fieldstill returns it on the front-end. If you want "hidden = cleared," you need anacf/save_posthook that clears the dependent fields when the parent toggles off.
For the clearing pattern:
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:
$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:
'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:
'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:
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 worksACF 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:
var_dump( get_field( 'is_featured', $post_id ) )to confirm whatget_fieldactually returns.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.- Check whether the field was added after the post existed. If yes, you have a backfill problem (Cause 2).
- If the template uses
get_post_metainstead ofget_field, switch toget_field(Cause 1). - If
meta_queryreturns wrong results, confirm you are usingvalue => '1'(string) and possibly theNOT EXISTSbranch (Cause 4). - 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.
- True/False field (Advanced Custom Fields docs)advancedcustomfields.com
- Boolean type (PHP manual)php.net





