This guide explains how to add a new non-hierarchical (flat) taxonomy to ChroniclePress and wire it up to the built-in picker UI. The system is designed so that a Layer or third-party plugin can do this entirely through WordPress filters.
How It Works #
The picker system has three moving parts:
- Your taxonomy β registered with WordPress, attached to whichever post type(s) you need
- A field definition β added to a field group via the
cp_field_groupsfilter, using'type' => 'flat_taxonomy' CP_Flat_Tax_Pickerβ the core class that handles rendering, REST API, and saving automatically for any field definition with that type
You provide steps 1 and 2. Step 3 requires no work from you.
Step 1 β Register Your Taxonomy #
Register your taxonomy as you normally would in WordPress, with two important details:
- Set
'hierarchical' => false(the picker is designed for flat taxonomies only) - Set
'meta_box_cb' => falseto suppress the WordPress default tag box (ChroniclePress native picker replaces it)
class My_Occupation_Taxonomy {
public function __construct() {
add_action( 'init', array( $this, 'register_taxonomy' ), 5 );
}
public function register_taxonomy() {
register_taxonomy(
'cp_occupation', // your taxonomy slug
array(), // post types added below
array(
'hierarchical' => false, // REQUIRED: flat only
'meta_box_cb' => false, // REQUIRED: suppress WP default UI
'labels' => array(
'name' => __( 'Occupations', 'my-layer' ),
'singular_name' => __( 'Occupation', 'my-layer' ),
'search_items' => __( 'Search Occupations','my-layer' ),
'all_items' => __( 'All Occupations', 'my-layer' ),
'edit_item' => __( 'Edit Occupation', 'my-layer' ),
'update_item' => __( 'Update Occupation','my-layer' ),
'add_new_item' => __( 'Add New Occupation','my-layer' ),
'new_item_name' => __( 'New Occupation', 'my-layer' ),
'not_found' => __( 'No occupations found.','my-layer' ),
'menu_name' => __( 'Occupations', 'my-layer' ),
),
'show_ui' => true,
'show_in_menu' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'occupation' ),
'show_in_rest' => true,
'rest_base' => 'occupations',
'capabilities' => array(
'manage_terms' => 'manage_categories',
'edit_terms' => 'manage_categories',
'delete_terms' => 'manage_categories',
'assign_terms' => 'edit_posts',
),
)
);
// Attach to whichever ChroniclePress post type(s) you need.
register_taxonomy_for_object_type( 'cp_occupation', 'cp_person' );
}
}
new My_Occupation_Taxonomy();
Step 2 β Add the Field to a Group #
Use the cp_field_groups filter to inject your field into an existing group, or to add a new group of your own.
Option A β Inject into an existing group #
This adds an Occupation picker to the Identity & Background group on the People edit screen.
add_filter( 'cp_field_groups', function ( $groups, $post_type ) {
if ( 'cp_person' !== $post_type ) {
return $groups;
}
// Find the 'identity' group and append your field.
foreach ( $groups as &$group ) {
if ( 'identity' !== ( $group['id'] ?? '' ) ) {
continue;
}
$group['fields'][] = array(
'key' => 'cp_person_occupation', // the POST key / hidden input name
'label' => __( 'Occupation', 'my-layer' ),
'type' => 'flat_taxonomy', // tells ChroniclePress to use the picker
'taxonomy' => 'cp_occupation', // your taxonomy slug
'placeholder' => __( 'Set occupationβ¦', 'my-layer' ),
'help' => __( 'Primary occupation or trade of this person.', 'my-layer' ),
'span' => 2, // span both columns (optional)
);
break;
}
return $groups;
}, 10, 2 );
Option B β Add a new group of your own #
This creates a dedicated Occupation group on the People edit screen.
add_filter( 'cp_field_groups', function ( $groups, $post_type ) {
if ( 'cp_person' !== $post_type ) {
return $groups;
}
$groups[] = array(
'id' => 'occupation',
'label' => __( 'Occupation', 'my-layer' ),
'icon' => 'dashicons-hammer',
'columns' => 1,
'fields' => array(
array(
'key' => 'cp_person_occupation',
'label' => __( 'Occupation', 'my-layer' ),
'type' => 'flat_taxonomy',
'taxonomy' => 'cp_occupation',
'placeholder' => __( 'Set occupationβ¦', 'my-layer' ),
'help' => __( 'Primary occupation or trade of this person.', 'my-layer' ),
'span' => 1,
),
),
);
return $groups;
}, 10, 2 );
That’s everything required. The picker UI renders automatically, the REST endpoints (GET/POST to /wp-json/chroniclepress/v1/flat-terms) are already registered by CP_Flat_Tax_Picker, and saving is handled transparently on save_post.
Step 3 β Reading the Saved Value #
The picker saves the selected term ID via wp_set_post_terms(). Read it like any other taxonomy assignment:
// Get the selected term object for a person post.
$terms = wp_get_post_terms( $post_id, 'cp_occupation' );
if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
$occupation = $terms[0]; // WP_Term object
echo esc_html( $occupation->name ); // e.g. "Blacksmith"
}
Because it is a real WordPress taxonomy, all standard WP functions work:
// Query all people with a specific occupation.
$blacksmiths = get_posts( array(
'post_type' => 'cp_person',
'tax_query' => array(
array(
'taxonomy' => 'cp_occupation',
'field' => 'slug',
'terms' => 'blacksmith',
),
),
) );
// Get the term ID directly (e.g. for use in featured card fields).
$term_ids = wp_get_post_terms( $post_id, 'cp_occupation', array( 'fields' => 'ids' ) );
$term_id = ! empty( $term_ids ) ? (int) $term_ids[0] : 0;
Step 4 β Exposing the Field to the Featured Card Block (Optional) #
If you want your new taxonomy field to appear as a displayable field in the Featured Card block, hook into cp_featured_card_fields:
add_filter( 'cp_featured_card_fields', function ( $map ) {
$map['cp_person'][] = array(
'key' => 'cp_person_occupation_display', // matches the picker's 'key' in get_field_groups
'label' => __( 'Occupation', 'my-layer' ),
'field_type' => 'taxonomy_term', // passed through $def to your resolver
'taxonomy' => 'cp_occupation',
'default' => false,
);
return $map;
} );
add_filter( 'cp_featured_card_resolve_field', function ( $value, $post_id, $key, $def ) {
// Only handle our custom field type; pass everything else through unchanged.
if ( 'taxonomy_term' !== ( $def['field_type'] ?? '' ) ) {
return $value; // null β let core handle it
}
$taxonomy = $def['taxonomy'] ?? '';
if ( ! $taxonomy ) {
return '';
}
$terms = wp_get_post_terms( $post_id, $taxonomy, array( 'fields' => 'names' ) );
if ( is_wp_error( $terms ) || empty( $terms ) ) {
return '';
}
return $terms[0]; // returns the term name as a string
}, 10, 4 );
Multiple Pickers on the Same Post Type #
You can add as many flat taxonomy fields as you need. Each must have a unique key (used as the HTML input name). The picker instances are completely independent.
add_filter( 'cp_field_groups', function ( $groups, $post_type ) {
if ( 'cp_person' !== $post_type ) {
return $groups;
}
$groups[] = array(
'id' => 'classification',
'label' => __( 'Classification', 'my-layer' ),
'icon' => 'dashicons-category',
'columns' => 2,
'fields' => array(
array(
'key' => 'cp_person_occupation',
'label' => __( 'Occupation', 'my-layer' ),
'type' => 'flat_taxonomy',
'taxonomy' => 'cp_occupation',
'placeholder' => __( 'Set occupationβ¦', 'my-layer' ),
),
array(
'key' => 'cp_person_social_class',
'label' => __( 'Social Class', 'my-layer' ),
'type' => 'flat_taxonomy',
'taxonomy' => 'cp_social_class',
'placeholder' => __( 'Set social classβ¦', 'my-layer' ),
),
),
);
return $groups;
}, 10, 2 );
Field Definition Reference #
Every flat_taxonomy field definition supports the following keys:
| Key | Required | Type | Description |
|---|---|---|---|
key | β | string | Unique input name. Used as the $_POST key and the hidden input’s name attribute. Convention: cp_{posttype}_{fieldname}. |
type | β | string | Must be 'flat_taxonomy'. |
taxonomy | β | string | The taxonomy slug passed to register_taxonomy(). |
label | β | string | Displayed above the picker. |
placeholder | β | string | Shown in the trigger when nothing is selected. Defaults to "Selectβ¦". |
help | β | string | Small grey hint shown below the picker. |
span | β | int | Column span within the group grid. Set to 2 (or the group’s column count) for full width. The picker auto-spans to full width regardless, but setting span makes the cell width explicit. |
REST API Reference #
CP_Flat_Tax_Picker registers two endpoints that the JS picker uses internally. You can also call them directly if needed.
GET /wp-json/chroniclepress/v1/flat-terms #
Returns terms for any registered taxonomy.
| Parameter | Required | Description |
|---|---|---|
taxonomy | β | Taxonomy slug |
q | β | Search string. Omit to return all terms. |
per_page | β | Max results (default 100, max 200) |
GET /wp-json/chroniclepress/v1/flat-terms?taxonomy=cp_occupation&q=black
[
{ "id": 12, "name": "Blacksmith", "count": 4 },
{ "id": 19, "name": "Blackbird Farmer", "count": 1 }
]
Requires: edit_posts capability.
POST /wp-json/chroniclepress/v1/flat-terms #
Creates a new term. Returns the existing term if the name already exists (case-sensitive match), preventing duplicates.
// Request body
{ "taxonomy": "cp_occupation", "name": "Cooper" }
// Response
{ "id": 23, "name": "Cooper" }
Requires: manage_categories capability.
