Custom slugs with dates & IDs on Custom Post Type

Yes, it’s not hard to do that.

  1. Just register your post type and enable rewriting for that post type, i.e. set rewrite to true.

  2. Once registered, modify its permalink structure like so:

    global $wp_rewrite;
    $wp_rewrite->extra_permastructs['<post type>']['struct'] = '<your structure here>';
    
  3. Use the post_type_link filter to replace rewrite tags like %post_id% in the permalink URL.

    If you need to use custom rewrite tags like %post_date% in the permalink structure, you can add the custom tags using add_rewrite_tag(), after you set the structure.

  4. Remember to flush the rewrite rules (i.e. re-save your permalinks) every time you changed the rewrite args, including when you simply set rewrite to true or false.

    You can programmatically flush the rules using flush_rewrite_rules(), however, it should only be used when necessary, e.g. upon plugin activation.

    As for the non-programmatic way, simply visit the Permalink Settings admin page without having to click the “save” button.

Working examples for you

  1. This is for the structure post-type-1-slug/post-name-year-month-day where a sample URL might look like https://example.com/type1/hello-world-2023-02-20/, and I’m using a custom rewrite tag %post_date% for the year-month-day part. (It’s up to you if you’d rather use multiple tags, e.g. %year%, %monthnum%, and %day%)

    add_action( 'init', 'my_register_type1_cpt' );
    function my_register_type1_cpt() {
        register_post_type( 'type1', [
            'public'   => true,
            'label'    => 'Type 1',
            'rewrite'  => true,
            // other args
        ] );
    
        global $wp_rewrite;
        $wp_rewrite->extra_permastructs['type1']['struct'] = 'type1/%type1%-%post_date%';
    
        // Add a structure tag for dates in either of these form: 2023-02-20 or 2023-2-20.
        add_rewrite_tag( '%post_date%', '(\d{4}-\d{1,2}-\d{1,2})' );
    
        // If you use %year%, %monthnum%, %day% and/or %post_id%, then those are core
        // structure tags in WordPress, hence you do not need to add them manually.
        // However, you still need to manually replace them in the permalink URL!
    }
    
    add_filter( 'post_type_link', 'my_type1_post_type_link', 10, 2 );
    function my_type1_post_type_link( $post_link, $post ) {
        if ( $post && 'type1' === $post->post_type ) {
            $post_date = wp_date( 'Y-m-d', strtotime( $post->post_date ) );
    
            return str_replace( '%post_date%', $post_date, $post_link );
        }
    
        return $post_link;
    }
    
  2. This is for the structure post-type-2-slug/post-name-id where a sample URL might look like https://example.com/type2/hello-world-123/ with 123 being the post ID.

    add_action( 'init', 'my_register_type2_cpt' );
    function my_register_type2_cpt() {
        register_post_type( 'type2', [
            'public'   => true,
            'label'    => 'Type 2',
            'rewrite'  => true,
            // other args
        ] );
    
        global $wp_rewrite;
        $wp_rewrite->extra_permastructs['type2']['struct'] = 'type2/%type2%-%post_id%';
    }
    
    add_filter( 'post_type_link', 'my_type2_post_type_link', 10, 2 );
    function my_type2_post_type_link( $post_link, $post ) {
        if ( $post && 'type2' === $post->post_type ) {
            return str_replace( '%post_id%', $post->ID, $post_link );
        }
    
        return $post_link;
    }
    

Note: %type1% and %type2% are the post name/slug and you don’t need to replace it manually, i.e. WordPress will do that for you.