1

I'm building a custom block that allows an editor to choose a Meta Key from a dropdown menu:

enter image description here

However, I am unable to find a data store or API endpoint available (out of the box) that will provide a list of meta keys available for a particular post type -- or even just a list of all registered meta keys.

I could, of course, create my own endpoint to output this data -- and that may be the best route and will likely be my next approach -- however, I wanted to make sure there wasn't a native data store or API endpoint that I am missing.

Here is what I have tried:

/wp-json/wp/v2/types/MY_CUSTOM_POST_TYPE?context=edit&_wpnonce=NONCE

This is very close as it provides a plethora of information about the registered post type, but it does not provide information about meta fields registered with this associated endpoint.

Similarly, I have tried:

wp.data.select('core').getPostType('MY_CUSTOM_POST_TYPE'), which again, while close, does not provide registered meta fields.

To confirm, I have registered the meta data with my post type and this data appears correctly when using the API to query a collection or single "ordinance" (the name of my custom post type in this case).

/**
 * Registers Ordinance Meta Fields.
 */
function pan_ordinance_register_meta() {
    register_post_meta(
        'ordinance',
        'bill_author',
        [
            'type'         => 'string',
            'description'  => __( 'Bill Author', 'pan' ),
            'single'       => true,
            'show_in_rest' => true,
        ]
    );
    register_post_meta(
        'ordinance',
        'bill_location',
        [
            'type'         => 'string',
            'description'  => __( 'Bill Location', 'pan' ),
            'single'       => true,
            'show_in_rest' => true,
        ]
    );
    ...
);
5
  • what are you trying to implement that requires such a dropdown? Are you planning to include all meta keys or just the ones you registered here such as bill author etc? If you're registering the meta in code you wrote does that not mean you already know all the possible meta?
    – Tom J Nowell
    Jan 10, 2022 at 19:50
  • @TomJNowell - This would be used in the Query Loop Block and would allow the editor to specify the meta data they'd like to output - in the order and format of their choosing. -- It is very similar to the post title block. Jan 10, 2022 at 20:10
  • so this is a modification to the query block to filter/sort the results? Or do you mean a separate block that simply displays the selected meta of the current post and nothing else? Keep in mind that post meta wasn't designed so that you could filter/search for posts by their meta values, doing that is inherently expensive and gets progressively costlier/slower as the size of the meta table increases. It's also why categories and tags are stored in taxonomy tables, not post meta
    – Tom J Nowell
    Jan 10, 2022 at 20:13
  • The latter - "a separate block that simply displays the selected meta of the current post." While I know the Meta keys because I coded them, :) I cannot expect all editors to know them hence the ease of a dropdown menu that references the description. I did find this data in the endpoints property of /wp-json so that may be a valid option. Jan 10, 2022 at 20:17
  • For now, I ended up creating a custom data store as outlined here and this seems to work well. It keeps API calls to a minimum, important here as several of these elements could be added per page. Jan 11, 2022 at 5:34

2 Answers 2

1

In order to accomplish this, I created a custom data store and extracted the available meta keys from the routes object returned when hitting the wp-json/wp/v2/ endpoint in the editor. This approach decreased the number of API requests compared to that with which I started. The initial approach in the question would have made one call per block vs one per page when editing.

Disclaimer: There is probably a cleaner way than using the RegEx below to extract the meta fields from the routes, but it is a start.

Usage with Custom Data Store:

    const metaFields = useSelect(
        (select) => select('example-meta/data').getMetaFields(),
        []
    );

Custom Data Store Example:

import { createReduxStore, register } from '@wordpress/data';
import apiFetch from '@wordpress/api-fetch';
import { addQueryArgs } from '@wordpress/url';

const STORE_NAME = 'example-meta/data';

const DEFAULT_STATE = {
    metaFields: null,
};

const actions = {
    fetchFromAPI() {
        return {
            type: 'FETCH_META_FROM_API',
        };
    },
    setMetaFields(metaFields) {
        return {
            type: 'SET_META_FIELDS',
            data: metaFields,
        };
    },
};

const getMetaFieldsFromRoutes = (routes) => {
    const metaFields = {};

    const routePaths = Object.keys(routes);

    const pathRegEx = new RegExp(/\/wp\/v2\/([A-Za-z0-9-_]+)$/);
    const pathsToCheck = routePaths.filter((path) => pathRegEx.test(path));

    pathsToCheck.forEach((path) => {
        const { endpoints } = routes[path];

        if (endpoints) {
            endpoints.forEach((endpoint) => {
                if (endpoint?.args?.meta?.properties) {
                    const endpointMetaKeys = Object.keys(
                        endpoint.args.meta.properties
                    );
                    endpointMetaKeys.forEach((metaKey) => {
                        if (!metaFields.hasOwnProperty(metaKey)) {
                            metaFields[metaKey] =
                                endpoint.args.meta.properties[metaKey];
                        }
                    });
                }
            });
        }
    });
    return metaFields;
};

const store = createReduxStore(STORE_NAME, {
    reducer(state = DEFAULT_STATE, action) {
        switch (action.type) {
            case 'SET_META_FIELDS':
                return {
                    ...state,
                    metaFields: action.data,
                };
        }
        return state;
    },
    actions,
    selectors: {
        getMetaFields(state) {
            const { metaFields } = state;
            return metaFields;
        },
    },
    controls: {
        FETCH_META_FROM_API() {
            return apiFetch({
                path: addQueryArgs(`/`, {
                    _fields: 'routes',
                }),
                parse: true,
            });
        },
    },
    resolvers: {
        *getMetaFields() {
            const result = yield actions.fetchFromAPI();
            const { routes } = result;
            const metaFields = getMetaFieldsFromRoutes(routes);
            return actions.setMetaFields(metaFields);
        },
    },
});

register(store);
1

I've just run into a case where I have the same need.

I would expect select( 'core' ).getPostType( postType ) to return the meta keys for the given post type, but that doesn't seem to be the case. But it turns out the entity record for an individual post includes a meta property housing all the meta keys registered for that post type. Since it also provides the values, I ended up leveraging that data instead of fetching the value again via useEntityProp.

Here's the full approach I ended up taking for the block edit function:

// WordPress dependencies.
import { useBlockProps } from '@wordpress/block-editor';
import { registerBlockType } from '@wordpress/blocks';
import { SelectControl } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';

// Internal dependencies.
import metadata from './block.json';

const Edit = ( { attributes, context, isSelected, setAttributes } ) => {
    const { postId, postType } = context;

    const post = useSelect( ( select ) =>
        select( 'core' ).getEntityRecord( 'postType', postType, postId )
    );

    let options = [ '' ];

    if ( post?.meta ) {
        options = options.concat( Object.keys( post.meta ) );
    }

    return (
        <div { ...useBlockProps() }>
            { ( isSelected || ! attributes?.metaKey ) && (
                <SelectControl
                    label={ __( 'Meta key', 'dieline' ) }
                    onChange={ ( metaKey ) => setAttributes( { metaKey } ) }
                    options={ options.map( ( option ) => ( {
                        value: option,
                        label: option,
                    } ) ) }
                    value={ attributes.metaKey }
                />
            ) }
            { attributes?.metaKey && post?.meta?.[ attributes.metaKey ] }
        </div>
    );
};

registerBlockType( metadata.name, {
    edit: Edit,
} );

This doesn't account for values consisting of structured data, of course, but hopefully it's a helpful foundation!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.