How can I modify the html output of a custom post type admin page?

The solution could involve the items below.

1. Edit Post admin page display

  1. Create a WordPress block.
    1. Register the block on the server with register_block_type.
    2. Register the block in the client with registerBlockType. This function’s block configuration object (with properties like those in block.json) has an edit property which is a function. This edit function outputs the HTML displayed on the Edit Post page. This is where you design the display for the HTML output of your custom post type admin page. It is inside this function that you would make API calls to verify if posts exist for regions and affiliates using calls to functions like getEntityRecords.
  2. When the custom post type is registered, your WordPress block needs to be assigned to the template option in the call to the server-side register_post_type function. (e.g. 'template' => array( array( 'namespace/your-block-name' ) ))
    1. If your plugin is registering this post type for you, get the custom post type name and use the code from this example. You can add your block(s) either client-side with JavaScript or server-side with PHP. Adding your block to the list of custom post type templates automatically displays the edit function HTML on the Edit Post page for posts of that custom post type. The sample below uses PHP:

        <?php
        function myplugin_register_template() {
            $post_type_object = get_post_type_object( 'CUSTOM_POST_TYPE' );
            $post_type_object->template = array(
                array( 'namespace/your-block-name' ),
            );
        }
        add_action( 'init', 'myplugin_register_template' );
      

The steps above require understanding block.json and how to create blocks. If you are not familiar with these aspects of WordPress, the links provided in this answer provide a starting point for learning.

2. Blocking creation of new posts

Block availability of Add New Post options

Blocking the availability of Add New Post options might make for a better developer experience since it could be easier than manipulating the post editor interface (e.g. removing the Publish button). And, it might make for a better user experience since it avoids allowing the user to start creating a post only to prevent the post from being saved.

Disabling the create_posts capability

The answer by Seamus Leahy to the question WordPress: Disable “Add New” on Custom Post Type could be an option for you. To implement this you can find inspiration from Nathan Powell’s answer to the question Change capability type of post type registered by plugin where it is shown how to use a register_post_type_args filter hook to change the capabilities of a custom post type registered by a plugin. In your case, rather than updating the capability_type property, you would be updating the capabilities array by changing the value for create_posts based on the existence of regions and affiliates.

Enabling the create_posts capability

If choosing this approach, a callback function would be needed to respond to the creation of regions or affiliates. If create_posts were false the creation of a region or affiliate that permits new custom posts to be created would have to trigger an update to set the create_posts capability to true. The save_post_{$post->post_type} action hook (demonstrated below), could be used to trigger the callback.

Delete invalid post on creation

Post creation action that cannot be blocked

I am not familiar with a WordPress action hook that can block post creation once the process has already started. However, you can use the save_post_{$post->post_type} WordPress action hook to catch the process immediately after the post is saved. At this point, you can verify that the application allows new posts. If new posts are not allowed, the newly created post can be immediately deleted before the user can access it. If such a post were deleted, you would need to send feedback to the user explaining what happened and why. The sample below demonstrates how the post could be deleted.

Untested code

/**
 * Delete invalid post on creation.
 * Replace CUSTOM_POST_TYPE with the name of your custom post type.
 * Be aware of the danger of infinite loops when using action hooks
 * like this one.
 * @see https://developer.wordpress.org/reference/hooks/save_post/#avoiding-infinite-loops
 *
 * @param int    $post_id Database ID for new post.
 * @param object $post    WP_Post object.
 * @param bool   $update  True if operation is an update to an existing post.
 *
 * @return void
 */
function delete_invalid_custom_post_type( int $post_id, \WP_Post $post, bool $update ) {

    // The $force_delete option controls if the post is sent to the Trash
    // or permanently deleted immediately. If sent to the trash you can
    // inspect the post if desired before permanently deleting it.
    $force_delete = false;

    // Exit the function if an existing post is being updated.
    if ( $update ) {
        return;
    }

    // Check for existence of Regions.
    $regions = \get_posts(
        array(
            'post_type'   => 'regions'
        )
    );

    // Check for existence of Affiliates.
    $affiliates = \get_posts(
        array(
            'post_type'   => 'affiliates'
        )
    );

    // Delete the new post if either $regions or $affiliates is empty.
    if ( empty( $regions ) || empty( $affiliates ) ) {
        \wp_delete_post( $post_id, $force_delete );
    }
}


add_action( 'save_post_CUSTOM_POST_TYPE', 'delete_invalid_custom_post_type', 10, 3 );