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!
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);
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.