Wordpress - How to create a custom post type without letting Wordpress assign a URL

One of the parameters for register_post_type() is publicly_queryable. Simply set this to false to prevent individual page creation. You may also wish to exclude from search, etc.

https://codex.wordpress.org/Function_Reference/register_post_type

In the example given in the WP Codex, you can see this parameter is set to true. Depending on your exact needs, you can hide the post type altogether with the public param or control levels of visibility with the explicit parameters, such pas publicly_queryable.

https://codex.wordpress.org/Function_Reference/register_post_type#Example

Example code from WP Codex

add_action( 'init', 'codex_book_init' );
/**
 * Register a book post type.
 *
 * @link http://codex.wordpress.org/Function_Reference/register_post_type
 */
function codex_book_init() {
    $labels = array( /*removed for example*/ );

    $args = array(
        'labels'             => $labels,
        'description'        => __( 'Description.', 'your-plugin-textdomain' ),
        'public'             => true,
        'publicly_queryable' => true,
        'show_ui'            => true,
        'show_in_menu'       => true,
        'query_var'          => true,
        'rewrite'            => array( 'slug' => 'book' ),
        'capability_type'    => 'post',
        'has_archive'        => true,
        'hierarchical'       => false,
        'menu_position'      => null,
        'supports'           => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' )
    );

    register_post_type( 'book', $args );
}

Post Type Archive It is important to note here that setting publicly_queryable to false will also hide the archive page of that post type. In the example code above for the post type book, the archive page at https://yourdomain.com/book would also be removed.


OK, so there are some arguments of register_post_type that you should use.

The crucial arguments for you are:

  • public - Controls how the type is visible to authors (show_in_nav_menus, show_ui) and readers (exclude_from_search, publicly_queryable). If it's false, then exclude_from_search will be true, publicly_queryable - false, show_in_nav_menus - false, and show_ui - false. So the CPT will be hidden all the way.

  • exclude_from_search - Whether to exclude posts with this post type from front end search results. Default: value of the opposite of public argument.

  • publicly_queryable - Whether queries can be performed on the front end as part of parse_request(). Default: value of public argument. So we have to et it true.

  • show_ui - Whether to generate a default UI for managing this post type in the admin. Default: value of public argument.

  • rewrite - Triggers the handling of rewrites for this post type. To prevent rewrites, set to false. Default: true and use $post_type as slug. So we have to set it false.

Below you can find the code:

$labels = array( /*removed for example*/ );

$args = array(
    'labels'             => $labels,
    'description'        => __( 'Description.', 'your-plugin-textdomain' ),
    'public'             => false,
    'show_ui'            => true,
    'rewrite'            => false,
    'capability_type'    => 'post',
    'hierarchical'       => false,
    /* ... Any other arguments like menu_icon, and so on */
    'supports'           => array( /* list of supported fields */ )
);

register_post_type( 'customer', $args );

This generator maybe helpful, if you don't want to learn all the arguments: https://generatewp.com/post-type/

And the list of all arguments, as always, you can find in Codex: https://codex.wordpress.org/Function_Reference/register_post_type