TechEarl

How to Set Default Values in ACF Select Fields

ACF Select fields have a Default Value setting in the field group editor that handles the simple case. For dynamic defaults (computed from another field, role-based, or per-post-type), the acf/load_value filter is the right tool.

Ishan Karunaratne⏱️ 5 min readUpdated
Share thisCopied
Set static defaults in the field group, dynamic defaults via acf/load_value, multi-select array defaults, and behavior when the default does not match.

ACF Select fields support a Default Value setting in the field group editor for the simple case. For dynamic defaults that depend on another field's value, the current user's role, the post type, or any other runtime condition, the acf/load_value filter is the right tool. Multi-select fields take an array default. Here is each pattern with concrete examples.

Jump to:

The static default in the field group editor

The simplest case. In the field group editor:

  1. Edit the Select field.
  2. Find the "Default Value" setting.
  3. Enter the option value (not the label) that should be selected by default.
  4. Save.

For a Select field with options low : Low, medium : Medium, high : High, entering medium as the Default Value makes new posts default to "Medium" unless the editor changes it.

The default applies when the field is rendered in the editor for a NEW post or for an existing post that has no saved value yet. Once the editor saves the post (even without changing the field), the value is locked to whatever was selected. Subsequent loads read the saved value, not the default.

Dynamic defaults via acf/load_value

acf/load_value runs when ACF loads a field's value for display in the editor. If you return a non-null value from the filter, that value becomes the field's default for that render. The filter receives the existing value, the post ID, and the field config:

php
add_filter( 'acf/load_value/name=priority', function ( $value, $post_id, $field ) {
    // Only set a default if no value exists
    if ( $value !== null && $value !== '' ) {
        return $value;
    }

    // Return your dynamic default
    return 'high';
}, 10, 3 );

Three variants of the filter name:

  • acf/load_value/name=priority: by field name.
  • acf/load_value/key=field_abc123: by field key (more reliable when name collisions are possible).
  • acf/load_value/type=select: by field type (applies to every Select field on the site; rarely what you want).

The early-return when $value already exists is important: without it, you would overwrite saved values on existing posts every time the post is loaded.

Default for multi-select Select fields

For Select fields with "Select multiple values" enabled, the default is an array of option values, not a single value:

php
add_filter( 'acf/load_value/name=tags', function ( $value, $post_id, $field ) {
    if ( ! empty( $value ) ) return $value;
    return ['blue', 'green']; // both options pre-ticked
}, 10, 3 );

The field group editor's "Default Value" setting for multi-select expects values one per line (each line is one default option). The dynamic version uses a PHP array.

If you accidentally return a single string for a multi-select field, ACF normalizes it to an array containing that string, so it does not break, but it is cleaner to return arrays consistently.

Default based on the parent post or related data

When the default for a child post should come from its parent (or another related post), acf/load_value is the right hook because it has access to the current post ID:

php
add_filter( 'acf/load_value/name=industry', function ( $value, $post_id, $field ) {
    if ( ! empty( $value ) ) return $value;

    $parent_id = wp_get_post_parent_id( $post_id );
    if ( $parent_id ) {
        return get_field( 'industry', $parent_id );
    }

    return $value;
}, 10, 3 );

The new child post inherits the parent's industry value as a default. The editor can override it but starts with a sensible suggestion.

Default based on the current user's role

Pre-populating fields based on who is creating the post:

php
add_filter( 'acf/load_value/name=visibility', function ( $value, $post_id, $field ) {
    if ( ! empty( $value ) ) return $value;

    $user = wp_get_current_user();
    if ( in_array( 'subscriber', (array) $user->roles, true ) ) {
        return 'private';
    } elseif ( in_array( 'contributor', (array) $user->roles, true ) ) {
        return 'pending-review';
    }

    return 'public';
}, 10, 3 );

Subscribers default to private posts; contributors default to pending-review; everyone else defaults to public. The editor can change it; the default just nudges to the safe option for the role.

What happens when the default value does not exist as an option

A common bug: you change the Select field's options (renaming medium to mid-priority) but forget to update the Default Value setting. The Default Value still says medium, which is no longer a valid option.

Behavior: the field appears with no option selected. ACF does not throw an error; the default just silently does not apply.

Fix: keep the Default Value in sync with the actual option values. For projects where options change over time, prefer the acf/load_value filter where you can guard against invalid defaults:

php
add_filter( 'acf/load_value/name=priority', function ( $value, $post_id, $field ) {
    if ( ! empty( $value ) ) return $value;

    $valid_choices = array_keys( $field['choices'] );
    $default = 'high';

    return in_array( $default, $valid_choices, true ) ? $default : '';
}, 10, 3 );

Defaults on legacy posts that pre-date the field

When you add a Select field to a field group that already has data, existing posts have no meta value for the new field. get_field returns the default if acf/load_value handles it (or returns empty otherwise).

For the read-side (in templates), the static default in the field group editor applies during read too, so get_field( 'priority' ) returns the default value even for posts that never explicitly saved one. This works for static defaults but does NOT apply to acf/load_value dynamic defaults (those only run during editor render).

If you need the dynamic default to apply on the front-end read too:

php
function get_priority_with_default( $post_id ) {
    $value = get_field( 'priority', $post_id );
    if ( empty( $value ) ) {
        $value = 'high'; // your dynamic default logic
    }
    return $value;
}

Or backfill existing posts via a one-time WP-CLI script (covered in the related context of Why ACF True/False Fields Sometimes Behave Unexpectedly which has the same issue for True/False fields).

For the broader programmatic-write reference, see How to Update ACF Fields Programmatically. For agency-scale ACF migration patterns, see Using AI with WP-CLI for Faster WordPress Operations.

Sources

Authoritative references this article was fact-checked against.

TagsWordPressACFSelect Field

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

How to Get ALT Text from an ACF Image Field

Getting alt text from an ACF Image field depends on the field's Return Format. Image Array gives you the alt directly. ID and URL formats need a wp_get_attachment helper. Plus the cleanest pattern for always-correct alt output.

How to Count Rows in an ACF Repeater Field

Counting ACF Repeater rows is three short patterns: count() on the raw field, get_field_count() inside a loop, and a faster meta-only count that skips loading the rows. Each has its right use case.

How to rsync Only the Files find Selected

rsync has no native time filter, so the standard trick is to let find pick the files and feed the list to rsync. The one-liner is find ... -print0 | rsync --files-from=- --from0, and the failure mode is always the same: the paths in the list have to be relative to the rsync source argument. The breakdown, the dry run habit, and when rsync's own filters make find unnecessary.