Term Meta Generator
Term fields are three hooks, not one: the add form, the edit form, and a save that fires on both. All three generated together, so nothing silently fails to save.
Scope
4 fields on category terms, 3 exposed to REST. Hooks: category_add_form_fields, category_edit_form_fields, created_category, edited_category.
Fields
4 fields
Accent colouracme_accent
REST
Taglineacme_tagline
REST
Archive layoutacme_layout
REST
Feature on the home pageacme_featured
REST
Where it appears
Add-term form
The div-based markup the new-term screen expects.
Edit-term form
The table-row markup the edit screen expects — different from the add form.
Terms-table column
Shows the first field as a column in the terms list.
<?php // Generated with WP CodeKit — powered by GrowQuest (https://growquest.io). /** * Plugin Name: category term fields * Description: Adds 4 fields to category terms. * Version: 1.0.0 * Requires PHP: 7.4 * Text Domain: acme */ defined( 'ABSPATH' ) || exit; /** * Register the term meta keys. */ function acme_register_meta() { register_term_meta( 'category', 'acme_accent', array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'sanitize_callback' => 'sanitize_hex_color', 'description' => __( 'Used for the archive header.', 'acme' ), 'auth_callback' => static function () { return current_user_can( 'manage_categories' ); }, ) ); register_term_meta( 'category', 'acme_tagline', array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'sanitize_callback' => 'sanitize_text_field', 'description' => __( 'Shown under the term name on its archive.', 'acme' ), 'auth_callback' => static function () { return current_user_can( 'manage_categories' ); }, ) ); register_term_meta( 'category', 'acme_layout', array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'sanitize_callback' => 'sanitize_key', 'auth_callback' => static function () { return current_user_can( 'manage_categories' ); }, ) ); register_term_meta( 'category', 'acme_featured', array( 'type' => 'boolean', 'single' => true, 'show_in_rest' => false, 'sanitize_callback' => 'rest_sanitize_boolean', 'auth_callback' => static function () { return current_user_can( 'manage_categories' ); }, ) ); } add_action( 'init', 'acme_register_meta' ); /** * Fields on the add-term form. */ function acme_add_form_fields() { wp_nonce_field( 'acme_save_term_meta', 'acme_term_nonce' ); echo '<div class="form-field term-accent-wrap">'; printf( '<label for="%1$s">%2$s</label>', 'acme_accent', esc_html( __( 'Accent colour', 'acme' ) ) ); printf( '<input type="color" id="%1$s" name="%1$s" value="%2$s" class="regular-text" />', 'acme_accent', esc_attr( '' ) ); printf( '<p>%s</p>', esc_html( __( 'Used for the archive header.', 'acme' ) ) ); echo '</div>'; echo '<div class="form-field term-tagline-wrap">'; printf( '<label for="%1$s">%2$s</label>', 'acme_tagline', esc_html( __( 'Tagline', 'acme' ) ) ); printf( '<input type="text" id="%1$s" name="%1$s" value="%2$s" class="regular-text" />', 'acme_tagline', esc_attr( '' ) ); printf( '<p>%s</p>', esc_html( __( 'Shown under the term name on its archive.', 'acme' ) ) ); echo '</div>'; echo '<div class="form-field term-layout-wrap">'; printf( '<label for="%1$s">%2$s</label>', 'acme_layout', esc_html( __( 'Archive layout', 'acme' ) ) ); echo '<select id="acme_layout" name="acme_layout">'; foreach ( array( 'grid' => __( 'Grid', 'acme' ), 'list' => __( 'List', 'acme' ), ) as $value => $label ) { printf( '<option value="%1$s"%2$s>%3$s</option>', esc_attr( $value ), selected( '', $value, false ), esc_html( $label ) ); } echo '</select>'; echo '</div>'; echo '<div class="form-field term-featured-wrap">'; printf( '<label for="%1$s">%2$s</label>', 'acme_featured', esc_html( __( 'Feature on the home page', 'acme' ) ) ); printf( '<input type="checkbox" id="%1$s" name="%1$s" value="1"%2$s />', 'acme_featured', checked( (bool) '', true, false ) ); echo '</div>'; } add_action( 'category_add_form_fields', 'acme_add_form_fields' ); /** * Fields on the edit-term form. * * @param WP_Term $term The term being edited. */ function acme_edit_form_fields( $term ) { wp_nonce_field( 'acme_save_term_meta', 'acme_term_nonce' ); $accent = get_term_meta( $term->term_id, 'acme_accent', true ); echo '<tr class="form-field term-accent-wrap">'; printf( '<th scope="row"><label for="%1$s">%2$s</label></th>', 'acme_accent', esc_html( __( 'Accent colour', 'acme' ) ) ); echo '<td>'; printf( '<input type="color" id="%1$s" name="%1$s" value="%2$s" class="regular-text" />', 'acme_accent', esc_attr( $accent ) ); printf( '<p class="description">%s</p>', esc_html( __( 'Used for the archive header.', 'acme' ) ) ); echo '</td></tr>'; $tagline = get_term_meta( $term->term_id, 'acme_tagline', true ); echo '<tr class="form-field term-tagline-wrap">'; printf( '<th scope="row"><label for="%1$s">%2$s</label></th>', 'acme_tagline', esc_html( __( 'Tagline', 'acme' ) ) ); echo '<td>'; printf( '<input type="text" id="%1$s" name="%1$s" value="%2$s" class="regular-text" />', 'acme_tagline', esc_attr( $tagline ) ); printf( '<p class="description">%s</p>', esc_html( __( 'Shown under the term name on its archive.', 'acme' ) ) ); echo '</td></tr>'; $layout = get_term_meta( $term->term_id, 'acme_layout', true ); echo '<tr class="form-field term-layout-wrap">'; printf( '<th scope="row"><label for="%1$s">%2$s</label></th>', 'acme_layout', esc_html( __( 'Archive layout', 'acme' ) ) ); echo '<td>'; echo '<select id="acme_layout" name="acme_layout">'; foreach ( array( 'grid' => __( 'Grid', 'acme' ), 'list' => __( 'List', 'acme' ), ) as $value => $label ) { printf( '<option value="%1$s"%2$s>%3$s</option>', esc_attr( $value ), selected( $layout, $value, false ), esc_html( $label ) ); } echo '</select>'; echo '</td></tr>'; $featured = get_term_meta( $term->term_id, 'acme_featured', true ); echo '<tr class="form-field term-featured-wrap">'; printf( '<th scope="row"><label for="%1$s">%2$s</label></th>', 'acme_featured', esc_html( __( 'Feature on the home page', 'acme' ) ) ); echo '<td>'; printf( '<input type="checkbox" id="%1$s" name="%1$s" value="1"%2$s />', 'acme_featured', checked( (bool) $featured, true, false ) ); echo '</td></tr>'; } add_action( 'category_edit_form_fields', 'acme_edit_form_fields', 10, 2 ); /** * Save the fields. Fires on both create and edit. * * @param int $term_id The term. */ function acme_save_term_meta( $term_id ) { if ( ! isset( $_POST['acme_term_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['acme_term_nonce'] ), 'acme_save_term_meta' ) ) { return; } if ( ! current_user_can( 'manage_categories' ) ) { return; } if ( isset( $_POST['acme_accent'] ) ) { $value = sanitize_hex_color( wp_unslash( $_POST['acme_accent'] ) ); if ( '' === $value || null === $value ) { delete_term_meta( $term_id, 'acme_accent' ); } else { update_term_meta( $term_id, 'acme_accent', $value ); } } if ( isset( $_POST['acme_tagline'] ) ) { $value = sanitize_text_field( wp_unslash( $_POST['acme_tagline'] ) ); if ( '' === $value || null === $value ) { delete_term_meta( $term_id, 'acme_tagline' ); } else { update_term_meta( $term_id, 'acme_tagline', $value ); } } if ( isset( $_POST['acme_layout'] ) && in_array( sanitize_key( $_POST['acme_layout'] ), array( 'grid', 'list' ), true ) ) { update_term_meta( $term_id, 'acme_layout', sanitize_key( $_POST['acme_layout'] ) ); } if ( ! empty( $_POST['acme_featured'] ) ) { update_term_meta( $term_id, 'acme_featured', 1 ); } else { delete_term_meta( $term_id, 'acme_featured' ); } } add_action( 'created_category', 'acme_save_term_meta' ); add_action( 'edited_category', 'acme_save_term_meta' ); /** * Add a column to the terms table. * * @param array $columns Existing columns. * @return array */ function acme_column_head( $columns ) { $columns['accent'] = __( 'Accent colour', 'acme' ); return $columns; } add_filter( 'manage_edit-category_columns', 'acme_column_head' ); /** * Fill the column. * * @param string $content Current content. * @param string $column Column name. * @param int $term_id Term ID. * @return string */ function acme_column_content( $content, $column, $term_id ) { if ( 'accent' !== $column ) { return $content; } return esc_html( (string) get_term_meta( $term_id, 'acme_accent', true ) ); } add_filter( 'manage_category_custom_column', 'acme_column_content', 10, 3 );