D
P
0

WordPress

Dua add_rewrite_rule dengan Regex Sama Tidak Saling Mengisi — yang Kedua Menimpa yang Pertama

15 Juni 2026·3 menit baca
Dua add_rewrite_rule dengan Regex Sama Tidak Saling Mengisi — yang Kedua Menimpa yang Pertama

Di sebuah situs WordPress yang saya rawat, saya butuh sebuah URL prefix tunggal — misalnya /style/... — yang bisa melayani dua tipe konten: sebuah custom post type lebih dulu, dan kalau tidak ada, fallback ke post biasa. Idenya sederhana di kepala: daftarkan dua rewrite rule dengan regex yang sama, satu untuk CPT, satu untuk post, biarkan WordPress mencoba yang pertama lalu jatuh ke yang kedua.

Hasilnya: semua slug CPT 404. Hanya rule yang terakhir didaftarkan yang bertahan. Tidak ada chaining, tidak ada fallback.

Akar masalah: rewrite rule di-key berdasarkan regex

Inilah bagian yang menjebak. WordPress menyimpan rewrite rules sebagai sebuah array yang di-key oleh string regex-nya. Jadi kalau Anda mendaftarkan dua rule dengan regex identik tapi query string berbeda, yang kedua menimpa yang pertama — persis seperti $arr[$key] = $a; $arr[$key] = $b;. Hanya satu rule per regex unik yang bisa hidup.

Kode yang saya kira benar:

// SALAH — rule kedua menimpa rule pertama, tidak ada fallthrough
add_rewrite_rule( '^style/([^/]+)/?$', 'index.php?post_type=article&name=$matches[1]', 'top' );
add_rewrite_rule( '^style/([^/]+)/?$', 'index.php?name=$matches[1]', 'top' ); // ini yang menang

Karena kedua regex identik (^style/([^/]+)/?$), entri kedua menumpuk yang pertama. Yang tersisa hanyalah rule post. Slug yang sebenarnya milik CPT article tidak punya rule yang cocok lagi → 404.

WordPress memang tidak punya konsep "coba rule ini, kalau tidak match coba berikutnya" untuk regex yang sama. Setiap regex hanya boleh muncul sekali.

Solusi: satu rule + private query var + filter request

Karena hanya satu rule per regex yang bisa hidup, jangan lawan itu — pakai satu rule saja, tandai dengan private query var, lalu mekarkan tanda itu di sebuah filter request menjadi array post_type.

// 1. SATU rule, dengan penanda query var sendiri
add_action( 'init', function () {
    add_rewrite_rule(
        '^style/([^/]+)/?$',
        'index.php?name=$matches[1]&my_multitype=1',
        'top'
    );
} );
 
// 2. Daftarkan query var penanda agar WP mengenalinya
add_filter( 'query_vars', function ( $vars ) {
    $vars[] = 'my_multitype';
    return $vars;
} );
 
// 3. Mekarkan penanda menjadi array post_type di filter request
add_filter( 'request', function ( $qv ) {
    if ( ! empty( $qv['my_multitype'] ) ) {
        $qv['post_type'] = array( 'post', 'article' );
        unset( $qv['my_multitype'] );
    }
    return $qv;
} );

Kuncinya ada di langkah 3: dengan $qv['post_type'] = array( 'post', 'article' ), WordPress mencari slug name di kedua tipe konten dalam satu query — inilah "fallback" yang sebenarnya kita inginkan, tanpa perlu dua rewrite rule. Penanda my_multitype di-unset supaya tidak bocor ke query final.

Auto-flush rewrite rule setelah deploy

Rewrite rule baru tidak aktif sampai di-flush. Daripada manual menyimpan permalink settings, saya pasang auto-flush yang dipatok ke versi, dijalankan di init setelah rule didaftarkan:

add_action( 'init', function () {
    $version = '1.0.1'; // naikkan setiap kali rule berubah
    if ( get_option( 'my_rewrite_version' ) !== $version ) {
        flush_rewrite_rules();
        update_option( 'my_rewrite_version', $version );
    }
}, 99 ); // prioritas akhir, setelah add_rewrite_rule terdaftar

flush_rewrite_rules() itu mahal, jadi jangan dipanggil setiap request. Pola version-pin ini hanya flush sekali per deploy — saat versinya naik — lalu menyimpannya di wp_options.

Catatan penutup

  • Rewrite rule di-key oleh regex. Mendaftarkan regex yang sama dua kali akan menimpa, bukan chaining. Hanya satu yang hidup.
  • Jangan harap fallthrough antar dua rule beregex sama. WordPress tidak punya mekanisme itu.
  • Pakai satu rule + private query var, lalu mekarkan di filter request. Untuk multi-tipe, set post_type jadi array — itu fallback yang sesungguhnya.
  • Jangan lupa unset penanda query var. Kalau dibiarkan, ia bisa mengacaukan query final.
  • Auto-flush yang dipatok versi. flush_rewrite_rules() mahal; jalankan sekali per deploy lewat opsi versi, jangan tiap request.

Pelajaran intinya: rewrite rule itu sebuah map yang di-key oleh regex, bukan daftar berurut yang dicoba satu per satu. Begitu Anda memikirkannya sebagai map, solusinya jelas — satu key, satu rule, dan biarkan filter request yang mengurus percabangan.