2

I have 2 custom post types: software and hardware. For SEO reasons I would like to have the permalinks of single software and hardware pages like:

https://domain.com/custom-post-name/

But as default WordPress adds post-type slug on it like:

https://domain.com/post-type-slug/custom-post-name/

I've removed the slug with following script that makes a replacement of the string. Now the single page is reachable with both urls described above and Google search console find 2 pages with the exact same content which is not good for SEO. Is it possible to change the complete structure of a permalink and get rid of custom post type slug?

register_post_type( 'hardware',
    array (
        'labels' => $labels,
        'has_archive' => true,
        'public' => true,
        'supports' => array( 'title', 'editor', 'excerpt', 'custom-fields', 'thumbnail' ),
        'taxonomies' => array( 'hardware-post_tag', 'hardware-category' ),
        'exclude_from_search' => false,
        'capability_type' => 'post',
        'rewrite' => array( 'slug' => 'hardware' ),
    )
);

2
  • Please edit your question and let we know how did you use the register_post_type Commented Feb 1, 2016 at 17:10
  • ` //register post type register_post_type( 'hardware', array ( 'labels' => $labels, 'has_archive' => true, 'public' => true, 'supports' => array( 'title', 'editor', 'excerpt', 'custom-fields', 'thumbnail' ), 'taxonomies' => array( 'hardware-post_tag', 'hardware-category' ), 'exclude_from_search' => false, 'capability_type' => 'post', 'rewrite' => array( 'slug' => 'hardware' ), ) ); `
    – Rico
    Commented Feb 1, 2016 at 18:35

5 Answers 5

7

Note one Important thing to the above answer:

While it'll work fine from the first sight it will cause performance problems. All this code will be called on init hook so every page load will cause it to run and flush_rules() is very time expensive.

So it's recomended to call flush rules upon theme\plugin activation only. Also you can use add_permastruct functions without need to access global $wp_rewrite

Final improved solution would be:

add_action('init', 'my_custom_rewrite'); 
function my_custom_rewrite() {

    add_permastruct('hardware', '/%customname%/', false);
    add_permastruct('produkt', '/%customname%/', false);
}

add_filter( 'post_type_link', 'my_custom_permalinks', 10, 2 );
function my_custom_permalinks( $permalink, $post ) {
      return str_replace( '%customname%/', $post->post_name, $permalink );
}

 /* in case for plugin */
register_activation_hook(__FILE__,'my_custom_plugin_activate');
function my_custom_plugin_activate() {
    flush_rewrite_rules();
}


/* in case of custom Theme in Functions.php */
add_action('after_switch_theme', 'mytheme_setup');
function mytheme_setup () {
    flush_rewrite_rules();
}
1

I managed to solve it using {$field_no_prefix}_save_pre with post_name.

/**
 * Customize permalinks.
 *
 * @param string $post_name
 *
 * @return string
 *   Returns a name-SKU combo for products, if all components are available.
 */
function my_custom_permalinks( $post_name ) {

    if (
        ($_POST['post_type'] !== 'product')
    ||  ($_POST['post_status'] === 'auto-draft')
    ) {
        return $post_name;
    }

    $post_name = sanitize_title_with_dashes(
        { modify $_POST['post_title'] as you please }
    );

    return $post_name;
}
add_filter('name_save_pre', 'my_custom_permalinks', 1, 1);
1

If you look at the rewrite argument for register_post_type, there is a with_front option to disable the default base that is prepended to the custom post type permalink. If you set this to false, /blog/products/ for example becomes /products/.

https://developer.wordpress.org/reference/functions/register_post_type/#rewrite

'rewrite' => array(
    'slug' => 'products',
    'with_front' => false
)
1
  • So simple, sweet answer!
    – brasofilo
    Commented Jul 3 at 17:46
0

SOLVED

with usage of $wp_rewrite you can add a new permalink structure

add_action('init', 'my_custom_rewrite');
function my_custom_rewrite() {
    global $wp_rewrite;
    $wp_rewrite->add_permastruct('hardware', '/%customname%/', false);
    $wp_rewrite->add_permastruct('produkt', '/%customname%/', false);
    $wp_rewrite->flush_rules();
}

then you replace your custom tag with str_replace when you filter the link url

add_filter( 'post_type_link', 'my_custom_permalinks', 10, 2 );
function my_custom_permalinks( $permalink, $post ) {
    return str_replace( '%customname%/', $post->post_name, $permalink );
}

NOTE: if you use this function the way is written (removing the post type slug from permalink) you can expect bad issues cause there is no control if you make a custom post permalink the same as a page or standard post

1
  • 1
    It's a bad practice to flush rules on every page load (init action). You need to do it only once. Commented Apr 8, 2020 at 10:06
0

Anyone coming across this question who has encountered the dreaded link rot with the "following script" link mentioned in the question, here's the script courtesy of the Wayback Machine, with a correction to what I believe was an error in the code:

/**
 * Remove the slug from published post permalinks. Only affect our CPT though.
 */
function vipx_remove_cpt_slug( $post_link, $post, $leavename ) {
    if ( ! in_array( $post->post_type, array( 'your_post_type' ) ) || 'publish' != $post->post_status )
        return $post_link;
    $post_link = str_replace( '/' . $post->post_type . '/', '/', $post_link );
    return $post_link;
}
add_filter( 'post_type_link', 'vipx_remove_cpt_slug', 10, 3 );

function vipx_parse_request_tricksy( $query ) {
    // Only noop the main query
    if ( ! $query->is_main_query() )
        return;
    // Only noop our very specific rewrite rule match
    if ( 2 != count( $query->query )
        || ! isset( $query->query['page'] ) )
        return;
    // 'name' will be set if post permalinks are just post_name, otherwise the page rule will match
    if ( ! empty( $query->query['name'] ) )
        $query->set( 'post_type', array( 'post', 'your_post_type', 'page' ) );
}
add_action( 'pre_get_posts', 'vipx_parse_request_tricksy' );

Along with Mikhail's revision of 3ky's own solution, if like me it wasn't immediately obvious to you, I found it necessary to implement the vipx_parse_request_tricksy() function and corresponding action. But the vipx_remove_cpt_slug() function duplicates what my_custom_permalinks() does, so both aren't necessary—pick one?

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.