Custom Post Type ile WordPress İçerik Türü Oluşturma
WordPress ile ciddi proje geliştiren herkes eninde sonunda şu soruyla yüzleşiyor: “Benim içeriğim ne gönderiye benziyor ne sayfaya, bunun için ne yapayım?” İşte tam bu noktada Custom Post Type (Özel İçerik Türü) devreye giriyor. Yıllardır WordPress projelerinde çalışırken, müşterilerden gelen “portföy eklentisi yükledim ama ağır geliyor” ya da “şu eklenti tema ile çakışıyor” şikayetlerinin neredeyse tamamının aynı kökten beslendiğini gördüm: hazır eklentiye bağımlılık. Oysa WordPress’in register_post_type() fonksiyonu, ihtiyacınıza birebir uyan, hafif ve kontrol sizde olan bir yapı kurmanıza yetiyor.
Custom Post Type Nedir ve Neden Kendiniz Yazmalısınız?
WordPress çekirdek kurulumunda post, page, attachment, revision ve nav_menu_item olmak üzere beş yerleşik post type bulunur. Bunların dışında ihtiyaç duyduğunuz her tür içerik kategorisi için kendi post type’ınızı tanımlayabilirsiniz.
Bir portföy sitesi yapıyorsanız projeler, bir restoran sitesinde menü kalemleri, bir etkinlik platformunda etkinlikler, bir hukuk bürosunda davalar veya bir emlak sitesinde mülkler gibi kavramların hepsinin ayrı post type’larla temsil edilmesi çok daha temiz bir mimari sağlar.
Peki neden hazır eklenti yerine kendiniz yazasınız?
- Performans: Çoğu CPT eklentisi sizin kullanmayacağınız onlarca özellikle geliyor. Her özellik bir miktar yük demek.
- Kontrol: Eklenti güncellendikten sonra davranışı değişirse ne yapacaksınız?
- Öğrenme eğrisi: Bunu bir kez anladığınızda WordPress geliştirme anlayışınız kökten değişiyor.
- Müşteri teslimi: Müşteriye “bu eklentiye bağlı” demek yerine “bu tamamen sizin temanızın parçası” diyebilmek çok daha profesyonel.
Eklenti mi, functions.php mi?
Bu tartışma hala devam ediyor. Kısa cevap: tema değişse bile içerik kaybolmaması gerekiyorsa, eklenti olarak yazın.
Bir düşünün: portföy projelerinizi functions.php’ye yazdınız. Tema değiştirdiniz. Bütün portföy verileri veritabanında duruyor ama WordPress artık o post type’ı tanımıyor. Admin panelinde göremezsiniz, sorgulayamazsınız. İçerik gitmedi ama erişilmez hale geldi.
Bu yüzden ben her zaman minimalist bir eklenti yapısı tercih ediyorum:
wp-content/
└── plugins/
└── my-custom-types/
├── my-custom-types.php
└── includes/
├── post-types.php
└── taxonomies.php
Ana eklenti dosyası şu kadar basit olabilir:
<?php
/**
* Plugin Name: My Custom Types
* Description: Projeye özel içerik türleri
* Version: 1.0.0
* Author: Sizin Adınız
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
require_once plugin_dir_path( __FILE__ ) . 'includes/post-types.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/taxonomies.php';
register_post_type() ile İlk CPT’nizi Yazın
Gelin gerçek bir senaryo üzerinden gidelim. Bir mimarlık bürosu için site yapıyorsunuz ve “Projeler” adında bir içerik türü lazım. Her projenin başlığı, açıklaması, fotoğrafları ve proje türü (konut, ticari, kamu) gibi kategorileri olacak.
<?php
// includes/post-types.php
function mct_register_project_post_type() {
$labels = array(
'name' => 'Projeler',
'singular_name' => 'Proje',
'menu_name' => 'Projeler',
'add_new' => 'Yeni Proje Ekle',
'add_new_item' => 'Yeni Proje Ekle',
'edit_item' => 'Projeyi Düzenle',
'new_item' => 'Yeni Proje',
'view_item' => 'Projeyi Görüntüle',
'search_items' => 'Projelerde Ara',
'not_found' => 'Proje bulunamadı',
'not_found_in_trash' => 'Çöp kutusunda proje bulunamadı',
'all_items' => 'Tüm Projeler',
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'projeler' ),
'capability_type' => 'post',
'has_archive' => true,
'hierarchical' => false,
'menu_position' => 5,
'menu_icon' => 'dashicons-building',
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields' ),
'show_in_rest' => true,
);
register_post_type( 'proje', $args );
}
add_action( 'init', 'mct_register_project_post_type' );
Buradaki önemli parametreleri açıklayayım:
- public: false yaparsanız bu CPT admin dışında hiçbir yerde görünmez, back-office yönetimi için kullanışlı
- has_archive: true yapınca yoursite.com/projeler/ URL’inde arşiv sayfası çalışır
- hierarchical: true yaparsanız sayfa gibi parent-child ilişkisi kurabilirsiniz, false olursa gönderi gibi davranır
- show_in_rest: Gutenberg editörünün ve REST API’nin bu CPT’yi tanıması için true olmalı, false bırakırsanız klasik editörde açılır
- rewrite: URL slug’ını buradan belirliyorsunuz, Türkçe karakterden kaçının
- supports: Hangi WordPress özelliklerinin bu CPT için aktif olacağını söylüyor
Taxonomy (Sınıflandırma) Eklemek
Post type’ınıza özel bir taksonomi olmadan gerçek anlamda kullanışlı bir yapı kurmak zor. Mimarlık bürosu örneğimize proje türleri için bir taksonomi ekleyelim:
<?php
// includes/taxonomies.php
function mct_register_project_type_taxonomy() {
$labels = array(
'name' => 'Proje Türleri',
'singular_name' => 'Proje Türü',
'search_items' => 'Proje Türü Ara',
'all_items' => 'Tüm Proje Türleri',
'parent_item' => 'Üst Proje Türü',
'parent_item_colon' => 'Üst Proje Türü:',
'edit_item' => 'Proje Türünü Düzenle',
'update_item' => 'Proje Türünü Güncelle',
'add_new_item' => 'Yeni Proje Türü Ekle',
'new_item_name' => 'Yeni Proje Türü Adı',
'menu_name' => 'Proje Türleri',
);
$args = array(
'hierarchical' => true,
'labels' => $labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'proje-turu' ),
'show_in_rest' => true,
);
register_taxonomy( 'proje_turu', array( 'proje' ), $args );
}
add_action( 'init', 'mct_register_project_type_taxonomy' );
hierarchical: true yapmak bu taksonomiye kategori benzeri davranış kazandırır (parent-child ilişkisi). false yaparsanız tag benzeri, hiyerarşisiz çalışır.
Permalink Sorununu Çözmek
CPT’nizi kaydettiğinizde “Sayfa bulunamadı” hatasıyla karşılaşırsanız panik yapmayın. WordPress’in yeniden yazma kurallarını yenilemesi gerekiyor. Bunu eklenti aktifleştirildiğinde otomatik yapmak en temiz yol:
<?php
// my-custom-types.php içine ekleyin
register_activation_hook( __FILE__, 'mct_flush_rewrite_rules' );
register_deactivation_hook( __FILE__, 'mct_flush_rewrite_on_deactivation' );
function mct_flush_rewrite_rules() {
mct_register_project_post_type();
flush_rewrite_rules();
}
function mct_flush_rewrite_on_deactivation() {
flush_rewrite_rules();
}
Geliştirme ortamında sık sık post type’ı değiştiriyorsanız her seferinde Ayarlar > Kalıcı Bağlantılar sayfasına gidip kaydet tuşuna basmak da aynı işi görür, ancak bunu müşteriye söylemek zorunda kalmamak için activation hook kullanın.
Meta Box ile Özel Alan Eklemek
Her projenin bir “Tamamlanma Yılı” ve “İnşaat Alanı (m²)” alanı olsun istiyorsunuz diyelim. Advanced Custom Fields kullanmadan bunu nasıl yaparsınız?
<?php
// Meta box eklemek için
function mct_add_project_meta_boxes() {
add_meta_box(
'proje_detaylari',
'Proje Detayları',
'mct_project_details_callback',
'proje',
'normal',
'high'
);
}
add_action( 'add_meta_boxes', 'mct_add_project_meta_boxes' );
function mct_project_details_callback( $post ) {
wp_nonce_field( 'mct_save_project_details', 'mct_project_nonce' );
$tamamlanma_yili = get_post_meta( $post->ID, '_proje_tamamlanma_yili', true );
$insaat_alani = get_post_meta( $post->ID, '_proje_insaat_alani', true );
?>
<p>
<label for="proje_tamamlanma_yili"><strong>Tamamlanma Yılı:</strong></label><br>
<input type="number"
id="proje_tamamlanma_yili"
name="proje_tamamlanma_yili"
value="<?php echo esc_attr( $tamamlanma_yili ); ?>"
min="1900"
max="2099"
style="width:100%;">
</p>
<p>
<label for="proje_insaat_alani"><strong>İnşaat Alanı (m²):</strong></label><br>
<input type="number"
id="proje_insaat_alani"
name="proje_insaat_alani"
value="<?php echo esc_attr( $insaat_alani ); ?>"
style="width:100%;">
</p>
<?php
}
function mct_save_project_details( $post_id ) {
if ( ! isset( $_POST['mct_project_nonce'] ) ) {
return;
}
if ( ! wp_verify_nonce( $_POST['mct_project_nonce'], 'mct_save_project_details' ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
if ( isset( $_POST['proje_tamamlanma_yili'] ) ) {
update_post_meta(
$post_id,
'_proje_tamamlanma_yili',
absint( $_POST['proje_tamamlanma_yili'] )
);
}
if ( isset( $_POST['proje_insaat_alani'] ) ) {
update_post_meta(
$post_id,
'_proje_insaat_alani',
absint( $_POST['proje_insaat_alani'] )
);
}
}
add_action( 'save_post_proje', 'mct_save_project_details' );
Burada güvenlik konusunda iki şeyi asla atlamayın: nonce doğrulaması ve capability kontrolü. Bir de dikkat edin, save_post_{post_type} hook’unu kullandım. Bu sayede sadece “proje” türündeki kayıtlar kaydedilirken bu fonksiyon tetikleniyor, gereksiz yere diğer post type’larda çalışmıyor.
WP_Query ile CPT Sorgulama
Front-end’de projelerinizi listelemek için WP_Query kullanacaksınız. İşte birkaç pratik örnek:
<?php
// Belirli bir türdeki son 6 projeyi getir
$proje_sorgu = new WP_Query( array(
'post_type' => 'proje',
'posts_per_page' => 6,
'orderby' => 'date',
'order' => 'DESC',
'tax_query' => array(
array(
'taxonomy' => 'proje_turu',
'field' => 'slug',
'terms' => 'konut',
),
),
'meta_query' => array(
array(
'key' => '_proje_tamamlanma_yili',
'value' => 2020,
'compare' => '>=',
'type' => 'NUMERIC',
),
),
) );
if ( $proje_sorgu->have_posts() ) {
while ( $proje_sorgu->have_posts() ) {
$proje_sorgu->the_post();
$yil = get_post_meta( get_the_ID(), '_proje_tamamlanma_yili', true );
$alan = get_post_meta( get_the_ID(), '_proje_insaat_alani', true );
echo '<div class="proje-karti">';
echo '<h2>' . get_the_title() . '</h2>';
echo '<p>Yıl: ' . esc_html( $yil ) . ' | Alan: ' . esc_html( $alan ) . ' m²</p>';
echo '</div>';
}
wp_reset_postdata();
}
wp_reset_postdata() çağrısını unutmayın. Özellikle ana döngüyle iç içe sorgular yaptığınızda bunu atlamak garip görünüm bozukluklarına yol açıyor. Bunu bir kez atladım, müşteriden “sidebar’da yanlış içerik çıkıyor” diye mail aldım, bir saatim gitti.
Tema Dosyalarını Doğru Yapılandırmak
WordPress, CPT için şu hiyerarşide tema dosyalarını arar:
- Tekil görünüm için:
single-proje.php>single.php>singular.php>index.php - Arşiv görünümü için:
archive-proje.php>archive.php>index.php - Taksonomi için:
taxonomy-proje_turu.php>taxonomy-proje_turu-{term}.php>taxonomy.php>archive.php
Yani temanızda single-proje.php ve archive-proje.php dosyalarını oluşturmanız yeterli. Child theme kullanıyorsanız bu dosyaları child theme dizinine atın, parent theme’yi dokunmadan bırakın.
Gerçek Dünya İpuçları
Yıllarca proje geliştirirken şu hatalarla ve çözümleriyle karşılaştım:
- Post type slug çakışması: “type”, “page”, “post” gibi rezerve kelimelerden uzak durun. Bunları slug olarak kullanmaya çalışırsanız sessiz sedasız çalışmaz.
proje,etkinlik,urungibi açık Türkçe sluglar kullanmak hem çakışmaları önlüyor hem de SEO açısından tutarlı.
- Çok fazla CPT: Her şeyi ayrı bir post type’a koyma eğilimi var yeni başlayanlarda. Bazen bir taksonomi ile ayrıştırabileceğiniz içerikleri ayrı CPT’ye almak veritabanını gereksiz karmaşıklaştırıyor.
- Gutenberg uyumluluğu:
show_in_rest => truekoymayı unutursanız Gutenberg o post type için çalışmaz, klasik editöre düşersiniz. Sonradan eklemek istemediğiniz bir durum.
- Admin kolonları: Özel alanlarınızı admin listesinde kolon olarak göstermek için
manage_proje_posts_columnsvemanage_proje_posts_custom_columnhookları kullanın, zira yüzlerce proje arasında gezinirken yılı veya alanı görmek can kurtarır.
Sonuç
Custom Post Type yazmak, WordPress ile ciddiye alınmaya değer projeler geliştirmenin temel taşı. register_post_type() fonksiyonunu bir kez anladıktan sonra “acaba hangi eklentiyi kullansam” sorusu yerine “bunu ben yazarım” demeye başlıyorsunuz. Bu hem özgürlük hem de sorumluluk.
Meta box’lardan taksonomilere, WP_Query sorgularından tema hiyerarşisine kadar anlattığım yapı, production ortamında çalışan gerçek projelerde test edilmiş yaklaşımlar. Özellikle ajans ortamında birden fazla müşteri projesi yönetiyorsanız bu minimalist eklenti yaklaşımını bir starter template haline getirip her projede tekrar kullanabilirsiniz.
ACF veya CMB2 gibi kütüphaneler meta box yazımını kolaylaştırıyor, bunu reddetmiyorum. Ancak meta box’ı sıfırdan bir kez kendiniz yazıp nonce kontrolü, sanitizasyon ve capability denetiminin nasıl çalıştığını öğrenmeden bu kütüphaneleri kullanmak, bir şeylerin neden bozulduğunu anlamayı güçleştiriyor. Temeli öğrenin, sonra araçları seçin.
