اولین افزونه‌ای که نوشتم، یک قطعه کد ۱۲ خطی بود که در functions.php قالب گذاشتم و قرار بود قیمت محصولات ووکامرس را با یک فرمول خاص حساب کند. سه ماه بعد، وقتی قالب را آپدیت کردم، همه‌چیز پاک شد. آن روز فهمیدم مرز بین «یک قطعه کد» و «یک افزونه» دقیقاً همان‌جایی است که کد شما باید مستقل از قالب زندگی کند — با ساختار، با هدر، با مدیریت نسخه. این تجربه، نقطه‌ی شروع مسیر من در توسعه افزونه بود. اگر شما هم از جایی شبیه این شروع کرده‌اید و حالا می‌خواهید یک افزونه‌ی واقعی بسازید، این راهنما برای شماست.

پیش از ورود به جزئیات، پیشنهاد می‌کنم اگر تازه با وردپرس آشنا می‌شوید، اول افزونه وردپرس چیست و چگونه افزونه مناسب انتخاب کنیم و توسعه وردپرس چیست و از کجا باید شروع کنیم را بخوانید. این مقاله، لایه‌ی عملی و فنی همان دو مقاله است.

چرا ساخت افزونه، پرش به توسعه‌ی حرفه‌ای وردپرس است؟

در پروژه‌هایی که با توسعه‌دهنده‌های وردپرس کار کرده‌ام، یک الگوی تکراری دیده‌ام: تا زمانی که کد در functions.php قالب است، فرد یک «کاربر پیشرفته» است. لحظه‌ای که اولین افزونه‌ی مستقل را می‌سازد، به «توسعه‌دهنده» تبدیل می‌شود. تفاوت در دو چیز است:

  • استقلال از قالب: افزونه، مستقل از پوسته زندگی می‌کند. اگر قالب عوض شود، افزونه سرِ جایش می‌ماند.
  • قابلیت بازاستفاده: افزونه می‌تواند روی چند سایت نصب شود. کد در functions.php فقط برای همان سایت است.

این تفاوت، از نظر حرفه‌ای مهم است — چون بازارِ افزونه‌سازی، بزرگ‌تر از بازار قالب‌سازی است. اما از نظر فنی هم مهم است، چون ساخت افزونه شما را با مفاهیمی رو‌به‌رو می‌کند که در قالب‌ها فقط سطحی‌شان را می‌بینید: معماری کلاس، مدیریت نسخه، internationalization، امنیت، و چرخه‌ی انتشار.

اگر پیش از این با ساختار قالب‌ها و فایل‌هایشان آشنا شده‌اید (مسیر در ساختار فایل‌های یک قالب استاندارد وردپرس)، ساخت افزونه را می‌توانید نسخه‌ی مکعبی‌شده‌ی همان مفاهیم بدانید.

افزونه، مثل ماژول است: هر بخش سایت را می‌توان به ماژول مستقل تبدیل کرد. قالب، بستر رندر است؛ افزونه، منطق.

پیش‌نیازها و ابزارها

ساخت افزونه، نیاز به سه لایه دانش دارد:

  1. PHP در سطح متوسط: کلاس، متد، آرایه، حلقه، توابع.
  2. مدل ذهنی هوک‌ها: تفاوت action و filter، ترتیب اجرا و priority. مسیر در هوک‌های وردپرس چیستند و چگونه کار می‌کنند.
  3. محیط لوکال: هرگز افزونه را روی سایت زنده توسعه ندهید. مسیر در توسعه وردپرس با محیط لوکال.

ابزارها ساده‌اند: یک ویرایشگر کد (VS Code یا PHPStorm)، یک محیط لوکال، و یک مخزن گیت برای کنترل نسخه. تفاوت دقیق ابزارها در بهترین ابزار توسعه وردپرس: مقایسه آمده است.

اسکلت اولیه: سه فایل که باید بسازید

هر افزونه، از یک پوشه شروع می‌شود. حداقلِ چیزی که وردپرس برای شناسایی یک افزونه لازم دارد، یک فایل PHP با هدر مشخص است. ولی یک افزونه‌ی حرفه‌ای، از سه فایل شروع می‌کند:

my-plugin/
├── my-plugin.php        ← فایل اصلی با هدر
├── uninstall.php        ← پاک‌سازی هنگام حذف
└── readme.txt           ← اطلاعات برای کاربر و مخزن

سه فایل، سه نقش:

  • فایل اصلی (my-plugin.php): نقطه‌ی ورود افزونه. وردپرس این فایل را برای خواندن هدر باز می‌کند.
  • فایل uninstall.php: کدی که هنگام حذف افزونه اجرا می‌شود. این فایل اختیاری است، ولی اگر افزونه شما در دیتابیس چیزی ذخیره می‌کند، حتماً لازم است.
  • فایل readme.txt: برای کاربران و برای ثبت در مخزن رسمی وردپرس. حتی اگر افزونه را منتشر نمی‌کنید، داشتنش عادت خوبی است.

ساختار کامل‌تر افزونه — با پوشه‌های includes، admin، public، languages — در ساختار فایل‌های یک افزونه استاندارد وردپرس آمده است. برای شروع، سه فایل بالا کافی است.

هدر افزونه، مهم‌ترین بخش فایل اصلی است — چون تنها چیزی است که وردپرس از همان ابتدا می‌خواند. اگر هدر نباشد، افزونه در فهرست پیشخوان ظاهر نمی‌شود:

<?php
/**
 * Plugin Name:       My Custom Plugin
 * Plugin URI:        https://example.com/my-plugin
 * Description:       A lightweight plugin that does one thing well.
 * Version:           1.0.0
 * Requires at least: 6.0
 * Requires PHP:      7.4
 * Author:            Your Name
 * Author URI:        https://example.com
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       my-plugin
 * Domain Path:       /languages
 */

// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

سه فیلد از این هدر، حیاتی هستند:

  1. Version: وردپرس از این نسخه برای مقایسه با نسخه‌های بعدی استفاده می‌کند. بدون آن، آپدیت‌های افزونه شناسایی نمی‌شوند.
  2. Requires PHP: اگر این را ننویسید، کاربر با PHP قدیمی، افزونه را نصب می‌کند و با خطای سفید صفحه رو‌به‌رو می‌شود.
  3. Text Domain: برای ترجمه. اگر با نام پوشه یکسان نباشد، ترجمه‌ها بی‌صدا از کار می‌افتند.

و یک نکته‌ی امنیتی که در ابتدای هر فایل PHP افزونه باید باشد: if ( ! defined( 'ABSPATH' ) ) exit;. این خط، جلوی دسترسی مستقیم به فایل از URL را می‌گیرد. هیچ افزونه‌ی حرفه‌ای بدون آن نیست.

هوک‌ها: قلب تپنده‌ی افزونه

افزونه‌ی وردپرس، از طریق هوک‌ها به هسته وصل می‌شود. هوک یعنی «در چه لحظه‌ای از اجرای وردپرس، کد من اجرا شود؟». دو نوع دارد:

نوعکارمثال
actionانجام کار در نقطه‌ای مشخصافزودن چیزی به header صفحه
filterتغییر داده‌ی موجودتغییر متن عنوان نوشته

مثال عملی — افزودن یک پیام به فوتر همه‌ی صفحات:

add_action( 'wp_footer', function() {
    echo '<!-- Powered by My Custom Plugin -->';
} );

مثال filter — افزودن یک متن به انتهای هر نوشته:

add_filter( 'the_content', function( $content ) {
    if ( is_single() ) {
        $content .= '<p>' . esc_html__( 'Thanks for reading.', 'my-plugin' ) . '</p>';
    }
    return $content;
} );

ترتیب اجرا، priority و پارامترها را در نحوه استفاده از add_action در وردپرس و نحوه استفاده از add_filter در وردپرس به‌تفصیل توضیح داده‌ام. اگر هم می‌خواهید هوک اختصاصی بسازید، مسیر در چگونه یک Action سفارشی در وردپرس بسازیم و چگونه یک Filter سفارشی در وردپرس بسازیم آمده است.

هوک‌ها، همان چیزی هستند که وردپرس را از یک سیستم مدیریت محتوای بسته، به یک پلتفرم باز تبدیل می‌کنند. هر افزونه‌ی حرفه‌ای، در واقع مجموعه‌ای از هوک‌هاست.

صفحه تنظیمات: از Options API تا Settings API

اکثر افزونه‌ها نیاز دارند کاربر چیزی را تنظیم کند: رنگ، متن، فعال/غیرفعال بودن یک قابلیت. برای این کار، دو مسیر وجود دارد:

Options API (ساده): با توابع get_option() و update_option()، داده را در جدول wp_options ذخیره می‌کنید و یک فرم ساده در پیشخوان می‌سازید:

add_action( 'admin_menu', function() {
    add_options_page(
        'My Plugin Settings',
        'My Plugin',
        'manage_options',
        'my-plugin',
        function() {
            $value = get_option( 'my_plugin_value', '' );
            ?>
            <form method="post" action="options.php">
                <input type="text" name="my_plugin_value" value="<?php echo esc_attr( $value ); ?>" />
                <?php submit_button(); ?>
            </form>
            <?php
        }
    );
} );

Settings API (حرفه‌ای): رویکرد ساختاریافته‌تر که خودِ وردپرس، فرم، ذخیره‌سازی و sanitization را مدیریت می‌کند. مسیر کامل در ساخت صفحه تنظیمات اختصاصی در وردپرس آمده است.

در پروژه‌های جدی، همیشه Settings API را انتخاب می‌کنم — چون کد کمتری می‌نویسید و امنیت به‌طور پیش‌فرض لحاظ می‌شود.

امنیت: nonce، sanitization، escape

این بخش، همان‌جایی است که اکثر افزونه‌های تازه‌کار شکست می‌خورند. سه اصل امنیتی که در هر افزونه رعایت می‌کنم:

  1. Sanitize هر چیزی که ذخیره می‌کنید: قبل از update_option، داده را با sanitize_text_field، sanitize_email یا تابع مناسب پاک کنید.
  2. Escape هر چیزی که نمایش می‌دهید: قبل از echo، داده را با esc_html، esc_attr یا esc_url پاک کنید.
  3. Nonce برای هر عمل کاربري: قبل از انجام هر عملی که کاربر درخواست می‌کند (ذخیره تنظیمات، حذف یک آیتم، ارسال فرم)، nonce را بررسی کنید:
if ( ! isset( $_POST['_my_plugin_nonce'] ) || ! wp_verify_nonce( $_POST['_my_plugin_nonce'], 'my_plugin_action' ) ) {
    wp_die( 'Invalid request.' );
}

مفهوم nonce و نقشش در امنیت فرم‌ها در نانس وردپرس و نقش آن در امنیت فرم‌ها و مسیر عملی در پیاده‌سازی نانس در فرم‌های سفارشی آمده است.

یک تجربه‌ی واقعی: در یک افزونه‌ی داخلی که برای یک شرکت نوشته بودم، یک فرم ساده‌ی تنظیمات بدون nonce گذاشته بودم. یک هفته بعد، یکی از کارمندان با دسترسی محدود، از طریق یک لینک ساده، توانست تنظیمات مدیریتی را ذخیره کند. آن روز یاد گرفتم که حتی در افزونه‌های داخلی، امنیت اختیاری نیست.

بارگذاری دارایی‌ها با enqueue

اگر افزونه‌ی شما CSS یا JS دارد، هرگز نباید آن‌ها را مستقیم در HTML لینک کنید. باید از wp_enqueue_style و wp_enqueue_script استفاده کنید — فقط در صفحاتی که لازم است:

add_action( 'admin_enqueue_scripts', function( $hook ) {
    if ( 'settings_page_my-plugin' !== $hook ) {
        return;
    }
    wp_enqueue_style(
        'my-plugin-admin',
        plugin_dir_url( __FILE__ ) . 'assets/admin.css',
        array(),
        '1.0.0'
    );
} );

بارگذاری مشروط، مهم‌ترین اصل در enqueue است. افزونه‌ای که فایل‌هایش را در همه‌ی صفحات پیشخوان بار کند، سایت را کند می‌کند. اثر این عادت بد روی سرعت در افزونه‌های وردپرس چگونه روی سرعت سایت اثر می‌گذارند به‌تفصیل آمده است.

بین‌المللی‌سازی و ترجمه

اگر افزونه‌ی شما قرار است توسط کاربران غیرفارسی هم استفاده شود، باید بین‌المللی‌سازی (i18n) شود. سه نکته:

  • هر رشته‌ی نمایشی را با __() یا _e() بنویسید و Text Domain را به‌عنوان پارامتر دوم بدهید.
  • فایل .pot بسازید — فایل قالب ترجمه که مترجم‌ها از آن استفاده می‌کنند.
  • پوشه‌ی languages داشته باشید و Domain Path را در هدر ست کنید.

مثال:

echo esc_html__( 'Hello, world!', 'my-plugin' );

نکته‌ی مهم: برای سایت‌های فارسی، این لایه خیلی مهم است. اگر افزونه قرار است به فارسی هم ترجمه شود، از ابتدا این ساختار را رعایت کنید تا بعداً بازنویسی لازم نشود.

تست، دیباگ و انتشار

پیش از انتشار، این چک‌لیست را در محیط لوکال اجرا کنید:

  1. فعال‌سازی WP_DEBUG در wp-config.php و پاک‌سازی همه‌ی notice و warningها.
  2. تست با PHP 8.x — اکثر هاست‌های مدرن روی این نسخه هستند.
  3. تست نصب روی نصب تمیز وردپرس — بدون هیچ افزونه‌ی دیگری.
  4. تست نصب روی نصب وردپرس با چند افزونه‌ی متداول — تا تضادها را پیدا کنید.
  5. تست سناریوی حذف — بعد از حذف، همه‌ی ردی‌های دیتابیس پاک شوند؟

برای انتشار در مخزن رسمی وردپرس، مسیر رسمی (SVN) را دنبال کنید. برای انتشار در بازار خودتان، فقط یک فایل zip بسازید و در سایت خود بفروشید. در هر دو حالت، readme.txt حرفه‌ای و یک تصویر بنر، بخشی از تجربه‌ی کاربر است.

نگهداری افزونه بعد از انتشار

این بخش، همان‌جایی است که اکثر افزونه‌ها می‌میرند. بعد از انتشار، سه تعهد دارید:

  • سازگاری با آپدیت‌های وردپرس: هر نسخه‌ی جدید وردپرس، ممکن است رفتار افزونه را تغییر دهد. باید افزونه را با نسخه‌های جدید تست کنید.
  • آپدیت‌های امنیتی: اگر آسیب‌پذیری در افزونه‌ی شما گزارش شود، باید در اسرع وقت patch منتشر کنید.
  • پاسخ به پشتیبانی: حتی اگر افزونه رایگان است، پاسخ‌گویی به سؤالات در انجمن یا از طریق ایمیل، بخشی از تعهد است.

یک تجربه‌ی تلخ: یک افزونه‌ی محبوب که چند سال آپدیت نشده بود، ناگهان تبدیل شد به یک درِ باز امنیتی. حملات، از طریق آسیب‌پذیری شناخته‌شده‌ی آن افزونه، به هزاران سایت نفوذ کردند. درس: افزونه، تعهدی بلندمدت است، نه یک پروژه‌ی یک‌باره. نشانه‌های افزونه‌های رهاشده در علائم هک و بدافزار در وردپرس آمده است.

اشتباهاتی که افزونه را نیمه‌کاره رها می‌کنند

در تجربه‌ی خودم، این هفت اشتباه را بیشتر از همه دیده‌ام:

اشتباهپیامد
نوشتن همه‌چیز در یک فایل PHPفایل ۲۰۰۰ خطی که شش ماه بعد خودتان هم نمی‌فهمید
نبود nonce و sanitizationآسیب‌پذیری امنیتی جدی
بارگذاری دارایی‌ها در همه‌ی صفحاتکندی سایت
نداشتن فایل uninstall.phpباقی‌ماندن داده‌های بی‌استفاده در دیتابیس
نبود کنترل نسخه (گیت)در روز بحران، برگشتی نیست
نداشتن readme.txtکاربر نمی‌داند افزونه چه کار می‌کند
رهاکردن بعد از انتشارافزونه‌ی امنیتی، به یک ریسک امنیتی تبدیل می‌شود

یک نکته‌ی مهم: هیچ افزونه‌ای نباید «فقط برای یک سایت» باشد. حتی اگر الان فقط برای سایت خودتان است، از ابتدا آن را طوری بنویسید که بتواند روی چند سایت نصب شود. این نگاه، شما را از یک «کاربر پیشرفته» به یک «توسعه‌دهنده» تبدیل می‌کند.

اگر تجربه‌ای از افزونه‌ی نال و کرک‌شده دارید و می‌خواهید ببینید این نوع افزونه‌ها چه ریسکی دارند، چگونه یک افزونه وردپرس مطمئن دانلود کنیم را بخوانید. و اگر به‌دنبال فهم تفاوت افزونه‌های ضروری و غیرضروری هستید، بهترین افزونه‌های ضروری وردپرس برای هر سایت نقشه‌ی کاملی می‌دهد.

نگاهی از سطح معماری: افزونه به‌مثابه یک قطعه‌ی نرم‌افزاری مستقل

برای کسی که سال‌ها روی معماری نرم‌افزار کار کرده، افزونه‌ی وردپرس در نگاه اول یک «قطعه کد اضافه» است. اما اگر عمیق‌تر نگاه کنید، افزونه یک قطعه‌ی نرم‌افزاری مستقل است که سه اصل معماری را در خود جای می‌دهد:

  • اصل «مرزهای روشن»: افزونه باید مرزهای مشخصی با هسته و بقیه‌ی افزونه‌ها داشته باشد. یعنی از توابع و کلاس‌هایی استفاده کند که با پیشوند یکتا تعریف شده‌اند تا با بقیه تضاد نکنند. این همان چیزی است که در معماری نرم‌افزار به آن «namespace» می‌گویند — و در وردپرس، با پیشوندهای اختصاصی پیاده می‌شود.
  • اصل «چرخه‌ی حیات کامل»: افزونه‌ی حرفه‌ای، چرخه‌ی حیات کاملی دارد: نصب، فعال‌سازی، اجرا، به‌روزرسانی، غیرفعال‌سازی، حذف. در هر مرحله از این چرخه، افزونه باید رفتار مشخصی داشته باشد — نه فقط در مرحله‌ی اجرا. کدهای register_activation_hook، register_deactivation_hook و uninstall.php دقیقاً برای پوشش این مراحل هستند.
  • اصل «سازگاری با سیستم میزبان»: افزونه باید با هر نسخه‌ی سازگاری که اعلام کرده کار کند. این یعنی نه از توابعی که در نسخه‌های قدیمی وردپرس نیستند استفاده کند، نه از ساختارهایی که ممکن است در نسخه‌های جدید از بین بروند. تفاوت یک افزونه‌ی حرفه‌ای با یک قطعه کد، در همین سازگاری بلندمدت است.

در چارچوب‌های جدی مهندسی نرم‌افزار، این سه اصل را با مفهوم «Modular Design» می‌شناسند: طراحی که هر بخش، مرزهای روشن، چرخه‌ی حیات کامل و سازگاری با محیط دارد. اگر افزونه‌ی وردپرسی خود را با این نگاه بنویسید، از یک «قطعه کد» به یک «محصول نرم‌افزاری» تبدیل می‌شود.

سه سؤال که در ساخت هر افزونه، از خودم می‌پرسم:

  1. آیا این افزونه، مرز مشخصی با هسته و افزونه‌های دیگر دارد؟
  2. آیا برای هر مرحله از چرخه‌ی حیاتش، کد مشخصی دارم؟
  3. آیا سه سال بعد، هنوز قابل نگهداری خواهد بود؟

اگر پاسخ این سه، روشن باشد، افزونه‌ای حرفه‌ای ساخته‌اید. برای درک عمیق‌تر این نگاه، ساختار هسته وردپرس چگونه کار می‌کند، اصول کدنویسی تمیز در پروژه‌های وردپرس و توسعه وردپرس چیست و از کجا باید شروع کنیم را در کنار این بحث بخوانید.

یک نکته‌ی مهم دیگر که در پروژه‌های بلندمدت یاد گرفته‌ام: افزونه، امضای فنی یک توسعه‌دهنده است. کدی که در افزونه‌ی خودتان می‌نویسید، همان چیزی است که سال‌ها بعد خودتان یا توسعه‌دهنده‌ی بعدی می‌بیند. اگر با ساختار تمیز، با کامنت کافی، با نام‌گذاری معنادار و با انضباط در چرخه‌ی حیات بنویسید، افزونه‌ای می‌سازید که سال‌ها باقی می‌ماند. اگر با عجله و بدون ساختار بنویسید، همان کدی است که شش ماه بعد، خودتان هم جرئت نمی‌کنید بازش کنید. این تفاوت، همان چیزی است که بین یک «افزونه‌ساز» و یک «توسعه‌دهنده‌ی نرم‌افزار» تفاوت می‌سازد.

حرف آخر: افزونه، تعهد بلندمدت است

خلاصه‌ی این راهنما در یک جمله: افزونه‌ی وردپرس، یک قطعه کد نیست؛ یک محصول نرم‌افزاری با مرز، چرخه‌ی حیات و تعهد نگهداری است. سه فایل شروع، یک هدر دقیق، هوک‌های درست، صفحه‌ی تنظیمات ساختاریافته، امنیت جدی، بارگذاری مشروط، و پشتیبانی مداوم — این هفت جزء، تفاوت بین یک افزونه‌ی حرفه‌ای و یک قطعه کد رهاشده است.

قدم عملی امشب‌تان: اگر ایده‌ی یک افزونه‌ی کوچک در ذهن دارید، همین امشب با ساخت اسکلت اولیه شروع کنید. فایل اصلی با هدر کامل، پوشه‌ی includes برای تفکیک منطق، و یک مخزن گیت. سه ساعت کار، شما را از «کاربر پیشرفته» به «توسعه‌دهنده‌ی افزونه» تبدیل می‌کند.

اگر تجربه‌ای از ساخت افزونه دارید — چه با موفقیت، چه با شکست در انتشار — برای من جذاب است بدانم کدام بخش، بیشترین چالش را داشت. تجربه‌تان را در دیدگاه‌ها بنویسید؛ مخصوصاً اگر اشتباهی هست که در این فهرست نبوده ولی در پروژه‌ی شما گران تمام شد، آن هم داده‌ای است که برای نفر بعدی، ساعت‌ها وقت ذخیره می‌کند. 🧩