ACF composer: Create custom fields for a CPT via the options page

Hey there,

I’m trying to give a client the ability to change the fields on a CPT from the options page repeater.

When I register an options page via the ACF Composer, I reference get_field(“field_name”, “option”) inside a Fields’ file.

To achieve that, I had to register the options page and its custom fields early in the setup.php file as following:

$parent = acf_add_options_page([
    'page_title' => 'Theme Options',
    'menu_title' => 'Theme Options',
    'menu_slug'  => 'theme-options',
]);

acf_add_options_sub_page(array(
    'page_title'    => 'Options',
    'menu_title'    => 'Options',
    'parent_slug'   => $parent['menu_slug'],
));

acf_add_local_field_group(array(
    'key' => 'group_5fc3255c2b7eb',
    'title' => 'Stockists',
    'fields' => array(
        array(
            'key' => 'field_5fc325713bedd',
            'label' => 'Stockists',
            'name' => 'stockists',
            'type' => 'repeater',
            'sub_fields' => array(
                array(
                    'key' => 'field_5fc3257d3bede',
                    'label' => 'logo',
                    'name' => 'logo',
                    'type' => 'image',
                ),
                array(
                    'key' => 'field_5fc325883bedf',
                    'label' => 'name',
                    'name' => 'name',
                    'type' => 'text',
                ),
            ),
        ),
    ),
    'location' => array(
        array(
            array(
                'param' => 'options_page',
                'operator' => '==',
                'value' => 'acf-options-options',
            ),
        ),
    ),
));

Is there a better way to achieve that with the cleaner file structure ACF Composer provides?

ACF fields are just WordPress meta/custom fields with the ACF layer on top to handle things like repeaters. If the field you need to get inside a field definition is simple, all you have to do is use internal WP functions to get its value instead of ACF functions–i.e. use get_option() instead of get_field() in this case. Even if your field is more complex (i.e. it’s a repeater) you can get and compile the values w/o using get_field(), i.e. (I haven’t used this in a long time, but it should more or less work):

function get_repeat_meta($repeater_field, $inner_fields = array(), $post_id) {
  $count = get_post_meta($post_id, $repeater_field, true);
  if($count < 1): return; endif; // If there aren't any fields, just stop now
  $i = 0;
  while($i < $count):
    foreach ($inner_fields as $field):
      $field_contents[$i][$field] = get_post_meta(
        $post_id, 
        $repeater_field.'_'.$i.'_'.$field, 
        true
      );
    endforeach;
    $i++;
  endwhile;
  return $field_contents;
}
1 Like

That worked!

I still can’t wrap my head around why the ACF layer fails to get those values based on where they were registered from if all it does is reach to the database.

Will dig into the inner workings of its functions after delivering this project.

Thanks for the quick and helpful reply.

I don’t have a full understanding of the ACF internals, but generally the deal is: If you try and do something w/ ACF functions before the acf/init hook fires, it’s not going to work correctly. I’m assuming that ACF does something on that hook that sets up…some kind of interpretation engine or something. Frustratingly, things won’t fail, they’ll just behave differently–i.e. get_field('repeater_field') will return 2 if there are two rows in the field, instead of the rows themselves; pretty useless. Sometimes you can get around that by hooking whatever it is you want to do into acf/init–sometimes not.

1 Like

Here’s the working code if anyone is interested:

// …/app/Options/ThemeOptions.php

    public function fields()
    {
        $themeOptions = new FieldsBuilder('theme_options');

        $themeOptions
            ->addRepeater('stockists')
                ->addImage('logo')
                ->addText('name')
            ->endRepeater();

        return $themeOptions->build();
    }

// …/app/Fields/BreadStockists.php

    public function fields()
    {
        $breadStockists = new FieldsBuilder('bread_stockists');

        $breadStockists
            ->setLocation('post_type', '==', 'bread');

        $stockists_field = 'options_stockists';
        $stockist_name_field = 'name';
        $stockistsCount = get_option($stockists_field);
    
        if ($stockistsCount < 1) {
            return;
        }

        $i = 0;
        while ($i < $stockistsCount) {
            $stockist = get_option("{$stockists_field}_{$i}_{$stockist_name_field}");
            $breadStockists->addUrl($stockist);
            $i++;
        }

        return $breadStockists->build();
    }

This allows admins to add brands from the theme options page

Which then dynamically creates a URL field for each brand on the CPT

This topic was automatically closed after 42 days. New replies are no longer allowed.