نوع نوشتهٔ سفارشی (Custom Post Type) یکی از قابلیت‌های کلیدی وردپرس است که بسیاری از پروژه‌ها بدون آن قابل تصور نیستند. یک سایت شرکتی که «نمونه‌کار» دارد، یک آموزشگاه که «دوره» دارد، یک رستوران که «منو» دارد — همهٔ این‌ها با CPT مدیریت می‌شوند. تجربه‌ام این است که بیشتر پروژه‌هایی که با CPT کار نکرده‌اند، در پایان با دیتابیس آشفته یا محتوای غیرقابل‌فهم سر و کله می‌زنند. خبر خوب: CPT در وردپرس، ساختاری تمیز و مستند دارد. این مقاله، گام‌به‌گام ساخت CPT را از ثبت تا نمایش مرور می‌کند. اگر با مفاهیم پایه آشنا نیستید، وردپرس چیست و توسعهٔ افزونه از صفر را پیش از ادامه ببینید.

CPT چیست و چه زمانی لازم است؟

CPT، نوع محتوایی مستقل از نوشته و برگه است که با ساختار و ظاهر خودش مدیریت می‌شود. سه نشانهٔ نیاز: یک — محتوایی دارید که به‌طور ساختاری از نوشته و برگه متفاوت است (مثلاً نمونه‌کار با فیلد «سال پروژه»). دو — می‌خواهید رابط کاربری مدیریت، جدا از نوشته‌ها باشد. سه — می‌خواهید URL اختصاصی داشته باشد (مثلاً /product/... یا /portfolio/...). تجربه‌ام: در پروژه‌های محتوایی، استفاده از CPT به‌جای «دسته‌بندی کردن نوشته‌ها»، تفاوت بین یک پنل مدیریت تمیز و یک پنل آشفته است. مفاهیم کلی در توابع وردپرس برای داده‌های نوشته و افزونه وردپرس چیست آمده است.

CPT، ساختار دادهٔ اختصاصی سایت شماست. مثل یک جدول در دیتابیس که خودتان طراحی کرده‌اید، ولی با تمام امکانات وردپرس.

ثبت CPT با register_post_type

اسکلت پایهٔ ثبت CPT:

function my_plugin_register_portfolio() {
    $labels = array(
        'name'               => 'نمونه‌کارها',
        'singular_name'      => 'نمونه‌کار',
        'add_new'            => 'افزودن نمونه‌کار جدید',
        'add_new_item'       => 'افزودن نمونه‌کار',
        'edit_item'          => 'ویرایش نمونه‌کار',
        'new_item'           => 'نمونه‌کار جدید',
        'view_item'          => 'مشاهدهٔ نمونه‌کار',
        'all_items'          => 'همهٔ نمونه‌کارها',
        'search_items'       => 'جستجوی نمونه‌کارها',
        'not_found'          => 'نمونه‌کاری یافت نشد',
    );

    $args = array(
        'labels'       => $labels,
        'public'       => true,
        'has_archive'  => true,
        'menu_icon'    => 'dashicons-portfolio',
        'supports'     => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
        'rewrite'      => array( 'slug' => 'portfolio' ),
        'show_in_rest' => true,
    );

    register_post_type( 'portfolio', $args );
}
add_action( 'init', 'my_plugin_register_portfolio' );

سه نکتهٔ کلیدی: یک — hook init: همیشه روی init ثبت کنید، نه روی after_setup_theme (که گاهی خیلی دیر است) و نه زودتر از آن. دو — نام CPT: حداکثر ۲۰ کاراکتر، فقط حروف کوچک لاتین، بدون خط تیره یا space. سه — slug با نام متفاوت: اگر نام CPT طولانی است، slug را کوتاه‌تر بگذارید (مثلاً book_2026 با slug book). راهنمای دقیق در ساختار فایل‌های افزونهٔ استاندارد و هوک‌های وردپرس.

Labels و برچسب‌های فارسی

Labels، تمام متن‌هایی است که در پیشخوان نمایش داده می‌شوند. تجربه‌ام: در پروژه‌های فارسی، این برچسب‌ها بیشترین اثر را روی تجربهٔ کاربر غیرفنی دارند. اگر برچسب‌ها انگلیسی بمانند، کاربر فارسی‌زبان احساس غریبی می‌کند. جدول کامل labels:

کلیدکاربرد
nameنام جمع در منو
singular_nameنام مفرد
add_newمتن دکمهٔ افزودن
edit_itemعنوان صفحهٔ ویرایش
all_itemsعنوان لیست
search_itemsمتن جستجو
not_foundمتن نبود نتیجه

در نسخهٔ حرفه‌ای، از تابع __() با text domain استفاده کنید تا قابل ترجمه باشد:

$labels = array(
    'name' => _x( 'نمونه‌کارها', 'Post type general name', 'my-plugin' ),
    'singular_name' => _x( 'نمونه‌کار', 'Post type singular name', 'my-plugin' ),
    // ...
);

راهنمای ترجمه در آماده‌سازی برای فارسی.

پارامترهای مهم args

پارامترهای کلیدی register_post_type: public: اگر false، CPT از front-end مخفی می‌شود (برای داده‌های داخلی). has_archive: اگر true، صفحهٔ آرشیو CPT ساخته می‌شود. menu_icon: آیکون در پیشخوان (dashicons یا URL). supports: کدام قابلیت‌های ویرایشگر فعال باشند (title، editor، thumbnail، excerpt، custom-fields، comments، revisions). rewrite: تنظیمات URL. show_in_rest: برای دسترسی از طریق REST API و گوتنبرگ. hierarchical: اگر true، مثل برگه سلسله‌مراتبی می‌شود. تجربه‌ام: انتخاب اشتباه در has_archive و hierarchical، بیشترین درد را در ادامهٔ پروژه می‌سازد. پیش از شروع، یک بار مستندات رسمی را مرور کنید.

rewrite و slug فارسی

برای URL خوانا، rewrite را تنظیم کنید:

'rewrite' => array(
    'slug'       => 'portfolio',
    'with_front' => false,
    'pages'      => true,
    'feeds'      => true,
),

دو نکته: یک — slug فارسی: به‌جای slug لاتین، می‌توانید از slug فارسی استفاده کنید، ولی به شرطی که در PHP URL-encode شده باشد. توصیهٔ من: برای SEO و سازگاری، slug لاتین بگذارید و در has_archive از عنوان فارسی استفاده کنید. دو — flush_rewrite_rules: پس از تغییر rewrite، یک بار flush کنید (یا از تنظیمات → پیوندهای یکتا → ذخیره استفاده کنید). راهنمای کامل در ساختار URL و سئو.

capability و سطوح دسترسی

برای کنترل دقیق دسترسی، capability_type و map_meta_cap را تنظیم کنید:

'capability_type' => 'portfolio',
'map_meta_cap'    => true,

سپس در فعال‌سازی افزونه، این capabilityها را به نقش‌های موردنظر اضافه کنید. تجربه‌ام: در پروژه‌های شرکتی، این تنظیم تفاوت بین «همهٔ ادمین‌ها دسترسی دارند» و «فقط نقش مدیر نمونه‌کار دسترسی دارد» را می‌سازد. مدیریت نقش‌ها در افزونه‌های مدیریت کاربران و توابع نقش و دسترسی.

اتصال به تاکسونومی سفارشی

برای CPT «نمونه‌کار»، معمولاً یک تاکسونومی «دستهٔ نمونه‌کار» لازم است:

register_taxonomy( 'portfolio_cat', 'portfolio', array(
    'labels' => array(
        'name' => 'دسته‌های نمونه‌کار',
        'singular_name' => 'دستهٔ نمونه‌کار',
    ),
    'hierarchical' => true,
    'show_in_rest' => true,
    'rewrite' => array( 'slug' => 'portfolio-category' ),
) );

الگوی کامل در ساخت تاکسونومی سفارشی. نکته: تاکسونومی و CPT، مکمل یکدیگرند؛ یکی بدون دیگری، مثل یک سیستم برچسب بدون محتوا است.

نمایش CPT در قالب

پس از ثبت CPT، وردپرس به‌طور خودکار فایل‌های template زیر را می‌جوید:

single-portfolio.php       # تک‌نمونه‌کار
archive-portfolio.php      # آرشیو نمونه‌کارها
taxonomy-portfolio_cat.php # آرشیو دستهٔ نمونه‌کار

اگر نباشند، از single.php، archive.php و taxonomy.php استفاده می‌شود. برای نمایش سفارشی، فایل‌های اختصاصی بسازید. راهنمای template hierarchy در ساختار فایل‌های قالب استاندارد. در حلقه، برای دسترسی به متادیتای CPT از get_post_meta استفاده کنید. الگوی کامل در توابع داده‌های نوشته.

CPT در افزونه یا قالب؟

سؤال همیشگی. پاسخ کوتاه: در افزونه. دلیل: اگر CPT در قالب ثبت شود، روز تغییر قالب، CPT از پیشخوان ناپدید می‌شود (داده در دیتابیس می‌ماند، ولی رابط کاربری مدیریت از بین می‌رود). تجربه‌ام: در چند پروژه، انتقال CPT از قالب به افزونه، نجات‌دهنده بود. حتی برای سایت شخصی، CPT را در mu-plugins/ یا یک افزونهٔ کوچک اختصاصی نگه دارید. تفکیک دقیق در افزودن قابلیت به وردپرس.

دید مهندسی: CPT به‌عنوان معماری داده

برای توسعه‌دهنده‌های سطح بالا، CPT فراتر از یک قابلیت مدیریتی است؛ یک تصمیم معماری داده است. سه الگوی پیشرفته: یک — CPT relational. اگر دو CPT با هم رابطه دارند (مثلاً «پروژه» و «عضو تیم»)، از متادیتای ارتباطی یا p2p استفاده کنید. دو — Headless CPT. با show_in_rest = true، CPT به‌طور خودکار REST endpoint می‌گیرد. الگو در استفاده از REST API. سه — CPT as Storage. در پروژه‌هایی که داده‌های حجیم دارند، CPT می‌تواند جایگزین جدول اختصاصی دیتابیس شود، با این مزیت که از تمام ابزارهای وردپرس بهره می‌برد. تجربه‌ام: در پروژه‌ای با ۵۰٬۰۰۰ محصول (CPT)، عملکرد با ایندکس‌گذاری مناسب و cache، تفاوتی با جدول اختصاصی نداشت. یک نکته: در CPTهای سنگین، بخشی از کوئری‌های پیش‌فرض را با فیلتر pre_get_posts محدود کنید و در صورت نیاز، از transients برای کش نتایج استفاده کنید — الگو در ترنزینت‌ها در وردپرس.

اشتباهات رایج

  • ثبت CPT روی hook اشتباه: باید init باشد. هوک‌ها.
  • نام CPT با حروف بزرگ یا خط تیره: تعارض و خطا. فقط حروف کوچک لاتین.
  • فراموش کردن flush_rewrite_rules: صفحه‌های CPT 404 می‌شوند.
  • نادیده‌گرفتن show_in_rest: گوتنبرگ کار نمی‌کند.
  • ثبت CPT در قالب: با تغییر قالب از دست می‌رود. باید افزونه باشد.
  • تاکسونومی با CPT اشتباه: تاکسونومی به CPT اشتباه وصل شود، در پیشخوان ظاهر نمی‌شود.
  • فراموش کردن labels فارسی: تجربهٔ کاربری غیرفنی ضعیف.
  • نادیده‌گرفتن capability: دسترسی همهٔ ادمین‌ها به CPTهای حساس.
  • حذف CPT بدون پاک‌سازی داده: ردیف‌های بی‌استفاده در دیتابیس. الگوی پاک‌سازی در پاک‌سازی دیتابیس.

جمع‌بندی

ساخت CPT در وردپرس، پنج گام دارد: ثبت با register_post_type، تعریف labels فارسی، تنظیم args، اتصال به تاکسونومی، و نمایش در قالب. قاعدهٔ طلایی: CPT در افزونه، نه در قالب. اگر امروز فقط یک کار می‌کنید: به پروژهٔ فعلی خود نگاه کنید و ببینید آیا محتوایی دارید که به‌طور ساختاری از نوشته/برگه متفاوت است — همان، کاندیدای CPT است. تجربهٔ خودتان از ساخت CPT، در دیدگاه‌ها ارزشمند است. 📚