Skip Navigation

This is the technical support forum for WPML - the multilingual WordPress plugin.

Everyone can read, but only WPML clients can post here. WPML team is replying on the forum 6 days per week, 22 hours per day.

This topic contains 1 reply, has 1 voice.

Last updated by yilmazA-2 1 month, 2 weeks ago.

Author Posts
May 19, 2025 at 7:14 am #17046596

yilmazA-2

Hi WPML Support,

We’re experiencing a serious permalink routing issue with a custom post type (programma) in a multilingual (Dutch/English) setup.

🔧 Setup details:
CPT: programma

Taxonomy: programma_category

WPML: All strings and slugs are translated

URL format: /%programma_category%/%post_name%/

✅ URLs that should work:
Dutch: hidden link

English: hidden link

The URL appears correctly in the dashboard, and the custom filter post_type_link replaces %programma_category% with the translated slug.

🚨 Problem:
When I open the correct English URL (/en/program/friday-6-june/hans-dulfer/), WPML or WordPress redirects me to a different custom post type (artiesten) that happens to have the same slug hans-dulfer.

So:

The correct URL exists

The slug is unique within the programma post type

But the frontend loads the wrong post type (artiesten), even though the post_type in the link is programma

I confirmed that the query var detection (via template_redirect) still sees:

csharp
Copy
Edit
[post_type] => artiesten
🔍 What we already did:
We have enabled "Different slugs in different languages" for programma

Custom rewrite is handled: rewrite => array( 'slug' => 'programma/%programma_category%', 'with_front' => false )

We use a custom post_type_link filter to translate %programma_category% per language

We added a fix to request filter:

add_filter('request', function($vars) {
if (isset($vars['name']) && isset($vars['post_type']) && $vars['post_type'] === 'programma') {
$vars['post_type'] = array('programma');
}
return $vars;
});

Still, the page loads a different post (from artiesten) when the slug overlaps.

❓What we need help with:
How can we force WPML/WordPress to route the correct post type, based on the full URL structure (slug + taxonomy)?

Is there any WPML setting or conflict that could cause this incorrect match by slug?

Can WPML give preference to full URL resolution over just matching slugs?

Let me know if you need a debug info report. We’re available to provide access or reproduce it.

Thanks so much in advance!

May 19, 2025 at 3:41 pm #17049418

yilmazA-2

This is my current programma cpt.
/*==*/
<?php
/**
* Register the custom post type "programma".
*/
function my_custom_post_type_programma() {
$labels = array(
'name' => _x('Programma\'s', 'Post type general name', 'hello-elementor-child'),
'singular_name' => _x('Programma', 'Post type singular name', 'hello-elementor-child'),
'menu_name' => _x('Programma\'s', 'Admin Menu text', 'hello-elementor-child'),
'name_admin_bar' => _x('Programma', 'Add New on Toolbar', 'hello-elementor-child'),
'add_new' => __('Add New', 'hello-elementor-child'),
'add_new_item' => __('Add New Programma', 'hello-elementor-child'),
'new_item' => __('New Programma', 'hello-elementor-child'),
'edit_item' => __('Edit Programma', 'hello-elementor-child'),
'view_item' => __('View Programma', 'hello-elementor-child'),
'all_items' => __('All Programma\'s', 'hello-elementor-child'),
'search_items' => __('Search Programma\'s', 'hello-elementor-child'),
'not_found' => __('No programma found.', 'hello-elementor-child'),
'not_found_in_trash' => __('No programma found in Trash.', 'hello-elementor-child'),
'featured_image' => _x('Programma Cover Image', 'Featured image label', 'hello-elementor-child'),
'set_featured_image' => _x('Set cover image', 'Set featured image label', 'hello-elementor-child'),
'remove_featured_image' => _x('Remove cover image', 'Remove featured image label', 'hello-elementor-child'),
'use_featured_image' => _x('Use as cover image', 'Use featured image label', 'hello-elementor-child'),
'archives' => _x('Programma archives', 'Post type archive label', 'hello-elementor-child'),
'insert_into_item' => _x('Insert into programma', 'Media insert label', 'hello-elementor-child'),
'uploaded_to_this_item' => _x('Uploaded to this programma', 'Uploaded media label', 'hello-elementor-child'),
'filter_items_list' => _x('Filter programma list', 'Filter list screen reader text', 'hello-elementor-child'),
'items_list_navigation' => _x('Programma list navigation', 'Navigation screen reader text', 'hello-elementor-child'),
'items_list' => _x('Programma list', 'List screen reader text', 'hello-elementor-child'),
);

$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array('slug' => 'programma/%programma_category%', 'with_front' => false),

'capability_type' => 'post',
'has_archive' => true,
'hierarchical' => false,
'menu_position' => null,
'supports' => array('title', 'editor', 'excerpt', 'thumbnail', 'custom-fields'),
'menu_icon' => 'dashicons-calendar-alt',
'show_in_rest' => true,
);

register_post_type('programma', $args);
}

/**
* Register the custom taxonomy "programma_category".
*/
function my_custom_taxonomy_programma_category() {
$labels = array(
'name' => _x('Programma Categories', 'taxonomy general name', 'hello-elementor-child'),
'singular_name' => _x('Programma Category', 'taxonomy singular name', 'hello-elementor-child'),
'search_items' => __('Search Programma Categories', 'hello-elementor-child'),
'all_items' => __('All Programma Categories', 'hello-elementor-child'),
'parent_item' => __('Parent Programma Category', 'hello-elementor-child'),
'parent_item_colon' => __('Parent Programma Category:', 'hello-elementor-child'),
'edit_item' => __('Edit Programma Category', 'hello-elementor-child'),
'update_item' => __('Update Programma Category', 'hello-elementor-child'),
'add_new_item' => __('Add New Programma Category', 'hello-elementor-child'),
'new_item_name' => __('New Programma Category Name', 'hello-elementor-child'),
'menu_name' => __('Programma Categories', 'hello-elementor-child'),
);

$args = array(
'hierarchical' => true,
'labels' => $labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array('slug' => 'programma-category'),
'show_in_rest' => true,
);

register_taxonomy('programma_category', array('programma'), $args);
}

/**
* Filter the permalink structure for "programma" custom post type with WPML-aware category slug.
*/

// function my_custom_project_permalink($post_link, $post) {
// if ('programma' === get_post_type($post)) {
// $terms = wp_get_post_terms($post->ID, 'programma_category');
// $category_slug = ($terms && !is_wp_error($terms) && isset($terms[0])) ? $terms[0]->slug : 'uncategorized';

// if (function_exists('icl_object_id')) {
// $lang = apply_filters('wpml_current_language', null);

// if ($lang === 'en') {
// // English permalink structure
// $post_link = home_url("/en/programma/$category_slug/" . $post->post_name . '/');

// } else {
// // Other languages fallback
// //$post_link = str_replace('%programma_category%', $terms[0]->slug, $post_link);
// $post_link = home_url("/programma/$category_slug/" . $post->post_name . '/');
// }
// } else {
// // If WPML not active, fallback default replacing %programma_category%
// $post_link = str_replace('%programma_category%', $category_slug, $post_link);
// }
// }
// return $post_link;
// }

function my_custom_project_permalink($post_link, $post) {
if ('programma' === get_post_type($post)) {
$terms = wp_get_post_terms($post->ID, 'programma_category');
$category_slug = (!is_wp_error($terms) && !empty($terms)) ? $terms[0]->slug : 'uncategorized';

// WPML-aware category slug
if (function_exists('icl_object_id')) {
$lang = apply_filters('wpml_current_language', null);
$translated_id = apply_filters('wpml_object_id', $terms[0]->term_id ?? null, 'programma_category', true, $lang);
$translated_term = get_term($translated_id, 'programma_category');

if ($translated_term && !is_wp_error($translated_term)) {
$category_slug = $translated_term->slug;
}
}

// ✅ Replace only the placeholder in the generated link
$post_link = str_replace('%programma_category%', $category_slug, $post_link);
}

return $post_link;
}

// Register CPT and taxonomy
add_action('init', 'my_custom_post_type_programma');
add_action('init', 'my_custom_taxonomy_programma_category');
add_filter('post_type_link', 'my_custom_project_permalink', 10, 2);

May 20, 2025 at 3:16 am #17050761

yilmazA-2

The key fix was this snippet added to your functions.php file:

add_filter('request', function($vars) {
if (!is_admin() && isset($vars['name']) && !isset($vars['post_type'])) {
global $wpdb;

// Zoek of de slug voorkomt in de 'programma' CPT
$slug = sanitize_title($vars['name']);
$query = $wpdb->prepare(
"SELECT ID FROM $wpdb->posts
WHERE post_name = %s AND post_type = %s AND post_status = 'publish' LIMIT 1",
$slug, 'programma'
);
$post_id = $wpdb->get_var($query);

if ($post_id) {
$vars['post_type'] = ['programma']; // Forceer 'programma' CPT
}
}

return $vars;
});

✅ What it does:
This code intercepts WordPress’s query variables ($vars) before WordPress resolves the request and:

Checks if there's a name (post slug), but no post_type.

Searches the programma post type for a matching slug.

If it finds one, it forces the post_type to programma.

💡 Why it's needed:
WordPress normally matches slugs like /hans-dulfer/ based on the first post type it finds. In your case, the slug hans-dulfer also exists in artiesten, so it was resolving to that CPT instead of programma.

WPML and permalinks made it worse by translating URLs, but not changing how WordPress resolves which CPT the slug belongs to. This fix ensures that if a slug exists in programma, WordPress explicitly uses that post type, avoiding false matches.