WordPress

Panduan Lengkap Struktur Plugin WordPress: File dan Folder Wajib, Opsional, dan Custom

A
Andi
Author
31 Desember 2025
52 min read
📝

Panduan Lengkap Struktur Plugin WordPress: File dan Folder Wajib, Opsional, dan Custom

Membangun plugin WordPress yang profesional dan mudah dirawat dimulai dari struktur file dan folder yang benar. Struktur yang baik tidak hanya membuat kode lebih terorganisir, tetapi juga memudahkan kolaborasi, maintenance, dan scaling plugin di masa depan.

Dalam panduan lengkap ini, kita akan membahas semua aspek struktur plugin WordPress, mulai dari file wajib minimal hingga struktur kompleks untuk plugin modern yang menggunakan React dan Vite.

Problem Statement: Mengapa Struktur Plugin Sering Menjadi Masalah?

Bayangkan skenario ini: Anda telah menghabiskan berjam-jam mengembangkan plugin WordPress yang luar biasa. Fitur-fitur sudah lengkap, kode sudah diuji, dan Anda siap untuk mempublikasikannya ke WordPress.org repository. Namun, saat submit, plugin Anda ditolak dengan alasan "struktur plugin tidak sesuai standar" atau "file keamanan tidak lengkap".

Atau mungkin skenario lain: Plugin Anda berfungsi dengan baik di development environment, tetapi saat di-deploy ke production, ada celah keamanan yang memungkinkan hacker mengakses file PHP secara langsung karena lupa menambahkan index.php di folder-folder penting.

Common Mistakes yang Sering Terjadi

Berikut adalah kesalahan umum yang sering dilakukan developer saat membangun plugin WordPress:

❌ Before (Struktur Buruk):

text
/my-plugin/
└── plugin.php  # Semua kode di satu file

✅ After (Struktur Baik):

text
/my-plugin/
├── plugin.php
├── index.php
├── /includes/
│   ├── index.php
│   └── class-plugin.php
├── /admin/
│   ├── index.php
│   └── class-admin.php
└── /public/
    ├── index.php
    └── class-public.php

Kesalahan Umum:

  1. Semua kode di satu file - Membuat maintenance menjadi nightmare
  2. Lupa menambahkan index.php - Membuka celah keamanan untuk directory listing
  3. Tidak ada ABSPATH check - File bisa diakses langsung melalui URL
  4. Struktur folder tidak konsisten - Sulit untuk kolaborasi tim
  5. Tidak ada separation of concerns - Admin dan public logic tercampur
  6. Lupa readme.txt - Plugin ditolak saat submit ke WordPress.org

Cost of Poor Structure

Struktur plugin yang buruk memiliki dampak yang signifikan:

  • Technical Debt: Semakin lama plugin dikembangkan, semakin sulit untuk dirawat. Refactoring menjadi lebih mahal dan berisiko.
  • Maintenance Time: Developer menghabiskan lebih banyak waktu untuk mencari file dan memahami kode yang tidak terorganisir.
  • Security Vulnerabilities: Struktur yang tidak aman membuka celah untuk eksploitasi, seperti directory listing, direct file access, dan SQL injection.
  • Collaboration Issues: Tim sulit berkolaborasi karena tidak ada standar struktur yang jelas.
  • WordPress.org Rejection: Plugin dengan struktur tidak standar akan ditolak saat submit ke repository resmi.
  • Performance Issues: Struktur yang buruk sering kali menyebabkan loading file yang tidak efisien.

Specific Use Cases

Struktur plugin yang baik penting untuk berbagai skenario:

  • Client Work: Plugin untuk klien membutuhkan struktur yang mudah dirawat dan di-extend
  • Commercial Distribution: Plugin komersial membutuhkan struktur profesional yang mengikuti best practices
  • WordPress.org Submission: Repository resmi memiliki standar ketat untuk struktur plugin
  • Team Development: Struktur yang baik memudahkan multiple developer bekerja bersama
  • Long-term Maintenance: Plugin yang akan digunakan dalam jangka panjang membutuhkan fondasi struktur yang solid

Learning Objectives

Setelah menyelesaikan panduan ini, Anda akan mampu:

  1. Membuat plugin WordPress minimal dengan plugin header yang benar dan struktur file dasar
  2. Mengimplementasikan security best practices termasuk index.php di setiap folder dan ABSPATH checks untuk mencegah direct access
  3. Mengorganisir plugin dengan struktur folder yang direkomendasikan menggunakan /includes/, /admin/, dan /public/ untuk separation of concerns
  4. Menyiapkan build process React+Vite untuk plugin modern dengan integrasi WordPress yang benar
  5. Mempersiapkan plugin untuk WordPress.org submission dengan readme.txt yang sesuai standar dan struktur yang benar
  6. Menerapkan teknik optimasi performa seperti lazy loading assets, caching, dan conditional enqueue
  7. Mengimplementasikan internationalization (i18n) dengan text domain dan translation files yang benar

Prerequisites

Sebelum memulai panduan ini, pastikan Anda memiliki:

Required Knowledge:

  • ✅ Dasar-dasar PHP (variables, functions, classes, arrays)
  • ✅ Pemahaman dasar WordPress (hooks, filters, actions)
  • ✅ Familiar dengan WordPress file structure (wp-content/plugins/)

Optional but Helpful:

  • ✅ Pengalaman dengan npm/Node.js (untuk section React+Vite)
  • ✅ Basic knowledge tentang React (untuk section modern plugin development)
  • ✅ Familiar dengan command line/terminal

Estimated Time:

  • ⏱️ Basic Structure: 30 menit (Section 1-2.4)
  • ⏱️ Recommended Structure: 1 jam (Section 2.5-2.7)
  • ⏱️ React+Vite Setup: 2 jam (Section 3)
  • ⏱️ Hands-On Tutorial: 2-3 jam (Contact Form Plugin)
  • ⏱️ Complete Guide: 4-6 jam (membaca semua sections)

Choose Your Learning Path

Panduan ini dirancang untuk berbagai use cases dan skill levels. Gunakan decision tree berikut untuk memilih path yang sesuai dengan kebutuhan Anda:

Path 1: Building Your First Plugin

Target: Pemula yang baru mulai dengan WordPress plugin development

Focus Areas:

  • ✅ Section 1: File dan Folder Wajib (Minimal Structure)
  • ✅ Section 2.1-2.4: Recommended Best Practices (Dokumentasi, Security, Lifecycle, i18n)
  • ✅ Hands-On Tutorial: Contact Form Plugin (Step 1-3)

Skip: Section 3 (React+Vite), Section 2.5-2.7 (Advanced Structure)

Estimated Time: 2-3 jam

Path 2: Modernizing Existing Plugin

Target: Developer yang sudah punya plugin dan ingin improve structure

Focus Areas:

  • ✅ Section 2: File dan Folder Opsional (Best Practices)
  • ✅ Section 6: Best Practices & Tips (Security, Performance, Organization)
  • ✅ Hands-On Tutorial: Refactoring existing code

Skip: Section 1 (Basic), Section 3 (React+Vite) jika tidak diperlukan

Estimated Time: 3-4 jam

Path 3: Adding React to Plugin

Target: Developer yang ingin menambahkan React frontend ke plugin WordPress

Focus Areas:

  • ✅ Section 3: Struktur Custom untuk React + Vite (Lengkap)
  • ✅ Section 5: Workflow Development dengan React + Vite
  • ✅ Prerequisites: Pastikan sudah familiar dengan React basics

Skip: Section 1-2 jika sudah paham basic structure

Estimated Time: 2-3 jam

Path 4: Preparing for WordPress.org

Target: Developer yang ingin submit plugin ke WordPress.org repository

Focus Areas:

  • ✅ Section 2.1: File Dokumentasi (readme.txt format)
  • ✅ Section 2.2: File Keamanan (Security best practices)
  • ✅ Section 6: Best Practices (Semua aspek)
  • ✅ Troubleshooting: Common rejection reasons

Skip: Section 3 (React+Vite) jika tidak digunakan

Estimated Time: 4-5 jam

Path 5: Complete Guide

Target: Developer yang ingin memahami semua aspek plugin structure

Focus Areas:

  • ✅ Semua sections secara sequential
  • ✅ Hands-On Tutorial lengkap
  • ✅ Best Practices & Tips
  • ✅ Troubleshooting

Estimated Time: 6-8 jam

Glossary: Technical Terms & Concepts

Berikut adalah definisi istilah-istilah teknis yang digunakan dalam panduan ini:

ABSPATH

Constant WordPress yang mendefinisikan absolute path ke WordPress installation. Digunakan untuk security check:

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

Autoloading (PSR-4)

Mekanisme untuk automatically load class files tanpa perlu manual require_once. PSR-4 adalah standar untuk autoloading di PHP:

hljs php
spl_autoload_register(function($class) {
    // Automatically load class files
});

Hooks vs Filters

  • Hooks (Actions): Titik di WordPress lifecycle di mana plugin bisa mengeksekusi code. Tidak mengembalikan nilai.
  • Filters: Titik di WordPress lifecycle di mana plugin bisa memodifikasi data. Harus mengembalikan nilai yang dimodifikasi.

Nonces

"Number used once" - Security token untuk mencegah CSRF (Cross-Site Request Forgery) attacks:

hljs php
wp_nonce_field('action_name', 'nonce_name');
wp_verify_nonce($_POST['nonce_name'], 'action_name');

Transients

WordPress caching mechanism untuk menyimpan data sementara dengan expiration time:

hljs php
set_transient('key', $data, 3600); // Cache for 1 hour
get_transient('key');

Semantic Versioning

Sistem versioning dengan format MAJOR.MINOR.PATCH:

  • MAJOR: Breaking changes (2.0.0)
  • MINOR: New features, backward compatible (1.1.0)
  • PATCH: Bug fixes (1.0.1)

Text Domain

Identifier unik untuk translation strings dalam plugin. Biasanya sama dengan plugin slug:

hljs php
__('Hello World', 'plugin-name'); // 'plugin-name' adalah text domain

Plugin Header

Metadata di bagian atas file plugin utama yang memberitahu WordPress tentang plugin:

hljs php
/**
 * Plugin Name: My Plugin
 * Version: 1.0.0
 * ...
 */

Separation of Concerns

Prinsip desain yang memisahkan kode berdasarkan tanggung jawabnya (admin, public, shared logic).

Enqueue

WordPress function untuk properly load CSS dan JavaScript files:

hljs php
wp_enqueue_script('handle', $src, $deps, $version, $in_footer);
wp_enqueue_style('handle', $src, $deps, $version);

What You'll Build

Dalam panduan ini, kita akan membangun pemahaman tentang berbagai level struktur plugin WordPress:

1. Struktur Minimal (Section 1)

text
/my-plugin/
└── plugin-name.php  # Hanya 1 file dengan plugin header

2. Struktur Recommended (Section 2)

text
/my-plugin/
├── plugin-name.php
├── index.php
├── readme.txt
├── /includes/
├── /admin/
├── /public/
└── /languages/

3. Struktur Modern dengan React+Vite (Section 3)

text
/my-plugin/
├── [semua file dari struktur recommended]
├── package.json
├── vite.config.js
├── /src/          # React source code
└── /dist/         # Build output

Hands-On Tutorial: Contact Form Plugin

Kita akan membangun plugin Contact Form lengkap yang mencakup:

  • ✅ Basic plugin structure dengan security
  • ✅ Admin interface untuk settings
  • ✅ Public shortcode untuk form
  • ✅ Form submission handler dengan security
  • ✅ Internationalization support
  • ✅ Best practices implementation

WordPress Plugin Architecture 101

Sebelum masuk ke detail struktur file dan folder, penting untuk memahami bagaimana WordPress memuat dan menjalankan plugin. Pemahaman ini akan membantu Anda membuat keputusan yang tepat tentang struktur plugin.

WordPress Execution Lifecycle

Berikut adalah alur eksekusi WordPress saat memuat plugin:

text
1. WordPress Bootstrap
   └──> Load core WordPress files
   
2. Load Active Plugins
   └──> Scan /wp-content/plugins/ directory
   └──> Read plugin header dari setiap plugin
   └──> Load plugin files secara sequential
   
3. Plugin Initialization
   └──> Execute code di plugin-name.php
   └──> Register hooks (actions & filters)
   └──> Load dependencies (includes, admin, public)
   
4. WordPress Core Hooks
   └──> plugins_loaded (semua plugin sudah dimuat)
   └──> init (WordPress fully initialized)
   └──> admin_init (admin area initialized)
   └──> wp_loaded (frontend ready)
   
5. Request Processing
   └──> Parse request (admin atau frontend)
   └──> Execute registered hooks
   └──> Render output

Hooks and Filters Explained

WordPress menggunakan sistem hooks untuk memungkinkan plugin memodifikasi atau menambahkan functionality tanpa mengubah core WordPress. Ada dua jenis hooks:

Actions - Lakukan sesuatu pada titik tertentu:

hljs php
// Action: Lakukan sesuatu saat plugin diaktifkan
register_activation_hook(__FILE__, 'my_plugin_activate');

// Action: Tambahkan menu admin
add_action('admin_menu', 'my_plugin_add_menu');

// Action: Enqueue scripts
add_action('wp_enqueue_scripts', 'my_plugin_enqueue_scripts');

Filters - Modifikasi data sebelum ditampilkan:

hljs php
// Filter: Modifikasi post title
add_filter('the_title', 'my_plugin_modify_title');

// Filter: Modifikasi content
add_filter('the_content', 'my_plugin_modify_content');

// Filter: Modifikasi excerpt
add_filter('get_the_excerpt', 'my_plugin_modify_excerpt');

Perbedaan Utama:

  • Actions: "Lakukan sesuatu" - tidak mengembalikan nilai
  • Filters: "Ubah sesuatu" - harus mengembalikan nilai yang dimodifikasi

Plugin Loading Process

WordPress memuat plugin dalam urutan tertentu:

  1. Plugin Discovery: WordPress scan folder /wp-content/plugins/ untuk mencari file PHP dengan plugin header
  2. Header Parsing: WordPress membaca plugin header untuk mendapatkan metadata (name, version, dll)
  3. Active Plugin Check: WordPress hanya memuat plugin yang aktif (checked di database)
  4. Sequential Loading: Plugin dimuat secara berurutan (bisa dikontrol dengan plugin priority)
  5. Hook Registration: Plugin mendaftarkan hooks mereka ke WordPress
  6. Execution: WordPress menjalankan hooks sesuai dengan request (admin atau frontend)

Separation of Concerns Principle

Separation of Concerns adalah prinsip penting dalam software development yang berarti setiap bagian kode harus memiliki tanggung jawab yang jelas dan terpisah. Dalam konteks plugin WordPress:

  • Core Logic (/includes/) - Business logic yang digunakan bersama oleh admin dan public
  • Admin Logic (/admin/) - Functionality khusus untuk WordPress admin area
  • Public Logic (/public/) - Functionality khusus untuk frontend website
  • Assets (/admin/css/, /public/js/) - Stylesheets dan JavaScript files

Manfaat Separation of Concerns:

  • ✅ Kode lebih mudah dirawat dan di-debug
  • ✅ Lebih mudah untuk testing
  • ✅ Memungkinkan multiple developer bekerja parallel
  • ✅ Mengurangi coupling antara components
  • ✅ Memudahkan scaling dan adding features

OOP vs Procedural Approaches

WordPress plugin bisa dikembangkan menggunakan dua pendekatan:

Procedural Approach (Traditional):

hljs php
// plugin-name.php
function my_plugin_init() {
    // Initialize plugin
}
add_action('init', 'my_plugin_init');

function my_plugin_add_menu() {
    // Add admin menu
}
add_action('admin_menu', 'my_plugin_add_menu');

Pros:

  • ✅ Lebih sederhana untuk plugin kecil
  • ✅ Lebih mudah dipahami untuk pemula
  • ✅ Tidak perlu memahami OOP concepts

Cons:

  • ❌ Sulit di-scale untuk plugin besar
  • ❌ Risk of function name collision
  • ❌ Sulit untuk code reuse

OOP Approach (Modern):

hljs php
// plugin-name.php
class My_Plugin {
    public function __construct() {
        add_action('init', array($this, 'init'));
        add_action('admin_menu', array($this, 'add_menu'));
    }
    
    public function init() {
        // Initialize plugin
    }
    
    public function add_menu() {
        // Add admin menu
    }
}

new My_Plugin();

Pros:

  • ✅ Lebih terorganisir untuk plugin besar
  • ✅ Mengurangi risk of naming collision
  • ✅ Memudahkan code reuse dan inheritance
  • ✅ Lebih mudah untuk testing
  • ✅ Better encapsulation

Cons:

  • ❌ Lebih kompleks untuk pemula
  • ❌ Membutuhkan pemahaman OOP

Rekomendasi:

  • Gunakan Procedural untuk plugin sederhana (< 500 lines)
  • Gunakan OOP untuk plugin kompleks atau yang akan di-scale

Mengapa Struktur Plugin Penting?

Sebelum masuk ke detail, mari kita pahami mengapa struktur plugin yang baik sangat penting:

  • Maintainability: Kode yang terorganisir lebih mudah dirawat dan di-debug
  • Scalability: Struktur yang baik memudahkan penambahan fitur baru
  • Security: Struktur yang benar membantu mencegah security issues
  • Performance: Organisasi file yang tepat dapat meningkatkan performa
  • Best Practices: Mengikuti standar WordPress membuat plugin lebih profesional
  • Team Collaboration: Struktur yang jelas memudahkan kolaborasi tim

1. File dan Folder Wajib [Beginner]

Sebenarnya, WordPress hanya membutuhkan satu file untuk mengenali sebuah plugin. Namun, dalam praktiknya, struktur yang lebih lengkap sangat direkomendasikan. Pelajari lebih lanjut di Plugin Basics - WordPress Plugin Handbook.

File Wajib Minimal

File/FolderLokasiDeskripsi
plugin-name.phpRootFile utama plugin dengan plugin header yang berisi metadata plugin

Catatan Penting: Hanya file plugin-name.php yang benar-benar mandatory. WordPress hanya membutuhkan 1 file PHP dengan valid plugin header untuk mengenali plugin tersebut.

Plugin Header yang Benar

Setiap file plugin utama harus memiliki plugin header di bagian atas file. Plugin header adalah metadata yang memberitahu WordPress tentang plugin Anda. Berikut adalah contoh plugin header yang lengkap:

hljs php
<?php
/**
 * Plugin Name: Nama Plugin Anda
 * Plugin URI: https://example.com/plugin-name
 * Description: Deskripsi singkat tentang apa yang dilakukan plugin ini
 * Version: 1.0.0
 * Author: Nama Anda
 * Author URI: https://example.com
 * License: GPL-2.0+
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 * Text Domain: plugin-name
 * Domain Path: /languages
 */

Penjelasan Field Plugin Header:

  • Plugin Name: Nama plugin yang akan muncul di dashboard WordPress (wajib) - Pelajari lebih lanjut
  • Plugin URI: URL website plugin (opsional)
  • Description: Deskripsi singkat plugin (sangat direkomendasikan)
  • Version: Versi plugin saat ini (wajib untuk update mechanism)
  • Author: Nama pembuat plugin
  • Author URI: URL website author
  • License: Lisensi plugin (biasanya GPL-2.0+ untuk WordPress) - WordPress License
  • Text Domain: Domain untuk translation (penting untuk i18n)
  • Domain Path: Path ke folder translation files

💡 Tip Performance: Gunakan versi number yang konsisten (semantic versioning) untuk memastikan browser cache bekerja dengan baik saat plugin di-update.

Meskipun tidak wajib, file dan folder berikut sangat direkomendasikan untuk plugin yang profesional dan mengikuti best practices WordPress.

2.1. File Dokumentasi dan Lisensi

File-file dokumentasi dan lisensi membantu menjelaskan plugin Anda kepada pengguna dan developer lain. Meskipun tidak wajib secara teknis, file-file ini sangat penting untuk profesionalitas dan distribusi plugin.

File/FolderLokasiDeskripsiKapan Digunakan
readme.txtRootInformasi plugin untuk WordPress.org directory (description, installation, FAQ, changelog, screenshots)Wajib jika submit ke WordPress.org repository
LICENSE atau license.txtRootFile lisensi (biasanya GPL 2.0 seperti WordPress)Jika distribusi open source
CHANGELOG.mdRootDokumentasi perubahan versi pluginRecommended untuk tracking changes

Referensi:

Contoh readme.txt untuk WordPress.org:

Format readme.txt harus mengikuti standar WordPress.org. Berikut adalah contoh lengkap:

text
=== Plugin Name ===
Contributors: yourusername
Tags: plugin, tag1, tag2
Requires at least: 5.0
Tested up to: 6.4
Stable tag: 1.0.0
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html

== Description ==

Deskripsi lengkap plugin Anda di sini.

== Installation ==

1. Upload plugin ke folder `/wp-content/plugins/`
2. Aktifkan plugin melalui menu 'Plugins' di WordPress
3. Konfigurasi plugin sesuai kebutuhan

== Changelog ==

= 1.0.0 =
* Initial release

💡 Tip: Pastikan format readme.txt mengikuti standar WordPress.org dengan benar. Format yang salah akan menyebabkan plugin ditolak saat submit ke repository.

2.2. File Keamanan

Keamanan adalah aspek penting dalam pengembangan plugin WordPress. File-file berikut membantu melindungi plugin Anda dari akses tidak sah dan eksploitasi keamanan. Pelajari lebih lanjut di Plugin Security - WordPress Plugin Handbook.

File/FolderLokasiDeskripsiKapan Digunakan
index.phpRoot & semua folderFile blank untuk mencegah directory listingSecurity best practice - wajib di setiap folder

Contoh index.php untuk security:

hljs php
<?php
// Silence is golden.

File ini harus diletakkan di setiap folder plugin untuk mencegah user mengakses direktori secara langsung melalui browser.

🔒 Security Tip: Selain index.php, selalu gunakan ABSPATH check di awal setiap file PHP untuk mencegah direct access:

hljs php
if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly
}

Ini mencegah file diakses langsung melalui URL dan hanya bisa diakses melalui WordPress.

2.3. File Lifecycle (Activation/Deactivation/Uninstall)

Plugin WordPress memiliki lifecycle yang jelas: activation, deactivation, dan uninstall. File-file berikut membantu mengelola setiap tahap lifecycle dengan benar dan membersihkan data yang tidak diperlukan. Pelajari lebih lanjut di Plugin Lifecycle - WordPress Plugin Handbook.

File/FolderLokasiDeskripsiKapan Digunakan
uninstall.phpRootCleanup data saat plugin di-uninstall (hapus options, tables, transients)Jika plugin menyimpan data di database

Contoh uninstall.php:

hljs php
<?php
// Jika file ini dipanggil langsung, keluar
if (!defined('WP_UNINSTALL_PLUGIN')) {
    exit;
}

// Hapus options
delete_option('plugin_name_option');

// Hapus custom tables
global $wpdb;
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}plugin_name_table");

// Hapus transients
delete_transient('plugin_name_transient');

Referensi:

🔒 Security Tip: Selalu cek WP_UNINSTALL_PLUGIN constant sebelum melakukan cleanup. Jangan hapus data user atau konten penting tanpa konfirmasi.

⚡ Performance Tip: Gunakan delete_option() dan delete_transient() daripada query langsung untuk menghapus data. WordPress akan menangani cleanup dengan lebih efisien.

2.4. Folder Internationalization (i18n)

Internationalization (i18n) memungkinkan plugin Anda mendukung berbagai bahasa. Folder ini berisi file-file translation yang memungkinkan plugin digunakan oleh pengguna dari berbagai negara. Pelajari lebih lanjut di Internationalization - WordPress Plugin Handbook.

File/FolderLokasiDeskripsiKapan Digunakan
/languages/RootFolder untuk internationalization files (.pot, .po, .mo)Jika plugin support multi-bahasa
plugin-name.pot/languages/Translation template fileBase file untuk translator
plugin-name-id_ID.po/languages/Translation file untuk bahasa tertentuSetelah diterjemahkan
plugin-name-id_ID.mo/languages/Compiled translation fileGenerated dari .po file

Cara menggunakan translation:

hljs php
// Di dalam kode PHP
echo __('Hello World', 'plugin-name');
echo _e('Hello World', 'plugin-name'); // echo langsung

// Dengan placeholder
printf(__('Hello %s', 'plugin-name'), $name);

// Plural forms
echo _n('One item', '%d items', $count, 'plugin-name');

Referensi:

💡 Tip: Selalu gunakan text domain yang konsisten di seluruh plugin. Text domain biasanya sama dengan plugin slug untuk memudahkan maintenance.

Referensi:

⚡ Performance Tip: Load text domain hanya saat diperlukan. Gunakan load_plugin_textdomain() di hook yang tepat (biasanya plugins_loaded) untuk menghindari overhead yang tidak perlu.

2.5. Folder Includes (Core Functionality) [Intermediate]

Folder includes berisi core functionality yang digunakan bersama oleh admin dan public area. Ini adalah tempat untuk meletakkan logika bisnis utama plugin yang tidak spesifik untuk admin atau frontend. Struktur ini mengikuti WordPress Plugin Best Practices.

Separation of Concerns Principle: Folder includes adalah implementasi dari prinsip separation of concerns yang memisahkan kode berdasarkan tanggung jawabnya:

  • Shared Logic/includes/ - Digunakan oleh admin dan public
  • Admin Logic/admin/ - Hanya untuk WordPress admin area
  • Public Logic/public/ - Hanya untuk frontend website

Dengan struktur ini, setiap bagian kode memiliki tanggung jawab yang jelas, membuat plugin lebih mudah dirawat, di-test, dan di-scale.

File/FolderLokasiDeskripsiKapan Digunakan
/includes/RootCore functionality shared antara admin & publicPlugin dengan kompleksitas medium-large
class-plugin-name.php/includes/Main plugin class dengan initializationOOP architecture
class-plugin-name-loader.php/includes/Class untuk register hooks dengan WordPressBoilerplate pattern (opsional)
class-plugin-name-activator.php/includes/Code yang run saat activation (create tables, add options)Setup requirements saat activation
class-plugin-name-deactivator.php/includes/Code yang run saat deactivation (cleanup temporary data)Cleanup saat deactivation
class-plugin-name-i18n.php/includes/Load internationalization filesHandle translations

Referensi:

Contoh struktur class:

hljs php
<?php
// includes/class-plugin-name.php

if (!defined('ABSPATH')) {
    exit;
}

class Plugin_Name {
    
    protected $loader;
    
    public function __construct() {
        $this->load_dependencies();
        $this->define_admin_hooks();
        $this->define_public_hooks();
    }
    
    private function load_dependencies() {
        require_once plugin_dir_path(__FILE__) . 'class-plugin-name-loader.php';
        $this->loader = new Plugin_Name_Loader();
    }
    
    private function define_admin_hooks() {
        // Admin hooks
    }
    
    private function define_public_hooks() {
        // Public hooks
    }
    
    public function run() {
        $this->loader->run();
    }
}

🔒 Security Tip: Selalu gunakan ABSPATH check di awal setiap file PHP. Ini mencegah file diakses langsung dan memastikan hanya WordPress yang bisa memanggil file tersebut.

⚡ Performance Tip: Gunakan lazy loading untuk dependencies. Jangan load semua class sekaligus, tapi load hanya saat diperlukan. Ini mengurangi memory footprint dan meningkatkan performa.

2.6. Folder Admin (Backend) [Intermediate]

Folder admin berisi semua file yang khusus untuk WordPress admin area. Ini termasuk class untuk menangani admin functionality, partials untuk UI, serta assets seperti CSS dan JavaScript khusus admin. Pelajari lebih lanjut di Admin Menus - WordPress Plugin Handbook.

Admin Hooks dan Enqueue Mechanism: WordPress menyediakan hooks khusus untuk admin area yang memungkinkan plugin menambahkan functionality:

  • admin_menu - Hook untuk menambahkan menu dan submenu di admin
  • admin_init - Hook yang dijalankan saat admin area di-initialize
  • admin_enqueue_scripts - Hook untuk enqueue CSS dan JavaScript di admin area

Enqueue mechanism adalah cara WordPress untuk properly load assets (CSS/JS) dengan dependency management, version control, dan conditional loading. Ini lebih baik daripada langsung include file karena WordPress akan menangani:

  • Dependency resolution (jQuery, React, dll)
  • Cache busting dengan version numbers
  • Conditional loading (hanya di halaman yang diperlukan)
  • Conflict prevention dengan plugin lain
File/FolderLokasiDeskripsiKapan Digunakan
/admin/RootSemua file khusus admin areaJika plugin punya admin interface
class-plugin-name-admin.php/admin/Main admin class dengan hooks dan functionalityAdmin-specific logic
/admin/partials//admin/File PHP dengan HTML output untuk admin UIPisahkan view dari logic
/admin/js//admin/JavaScript files khusus adminAdmin scripts
/admin/css//admin/CSS files khusus adminAdmin styles
/admin/images//admin/Image assets khusus adminAdmin UI graphics

Referensi:

Contoh enqueue admin assets:

Gunakan wp_enqueue_style() dan wp_enqueue_script() untuk enqueue assets dengan benar:

hljs php
// admin/class-plugin-name-admin.php

public function enqueue_styles() {
    wp_enqueue_style(
        'plugin-name-admin',
        plugin_dir_url(__FILE__) . 'css/plugin-name-admin.css',
        array(),
        $this->version,
        'all'
    );
}

public function enqueue_scripts() {
    wp_enqueue_script(
        'plugin-name-admin',
        plugin_dir_url(__FILE__) . 'js/plugin-name-admin.js',
        array('jquery'),
        $this->version,
        false
    );
    
    // Localize script untuk pass data ke JavaScript
    wp_localize_script('plugin-name-admin', 'pluginNameAdmin', array(
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'nonce' => wp_create_nonce('plugin_name_admin_nonce'),
    ));
}

Referensi:

🔒 Security Tip: Selalu gunakan wp_verify_nonce() untuk memverifikasi AJAX requests dan form submissions di admin area. Jangan pernah trust user input tanpa validasi.

⚡ Performance Tip: Enqueue scripts dan styles hanya di halaman yang membutuhkan. Gunakan conditional checks seperti get_current_screen() untuk memastikan assets hanya di-load saat diperlukan. Ini mengurangi load time dan memory usage.

2.7. Folder Public (Frontend) [Intermediate]

Folder public berisi semua file yang digunakan di frontend website. Jika plugin Anda menampilkan konten di halaman publik, file-file tersebut sebaiknya ditempatkan di folder ini. Pelajari lebih lanjut di Frontend Development - WordPress Plugin Handbook.

Frontend Hooks dan Output Escaping: WordPress menyediakan hooks khusus untuk frontend yang memungkinkan plugin menambahkan functionality:

  • wp_enqueue_scripts - Hook untuk enqueue CSS dan JavaScript di frontend
  • wp_head - Hook untuk menambahkan code di <head> section
  • wp_footer - Hook untuk menambahkan code sebelum closing </body> tag
  • the_content - Filter untuk memodifikasi post content
  • shortcode - System untuk membuat shortcodes yang bisa digunakan di content

Output Escaping adalah praktik keamanan penting untuk mencegah XSS (Cross-Site Scripting) attacks. Setiap data yang ditampilkan ke frontend harus di-escape sesuai dengan konteksnya:

  • esc_html() - Untuk HTML content
  • esc_attr() - Untuk HTML attributes
  • esc_url() - Untuk URLs
  • wp_kses_post() - Untuk HTML dengan allowed tags
  • wp_json_encode() - Untuk JavaScript data

Selalu escape output sebelum menampilkan data user atau dari database ke frontend.

File/FolderLokasiDeskripsiKapan Digunakan
/public/RootSemua file khusus frontendJika plugin render output di frontend
class-plugin-name-public.php/public/Main public class dengan hooks dan functionalityFrontend-specific logic
/public/partials//public/File PHP dengan HTML output untuk frontendFrontend view templates
/public/js//public/JavaScript files khusus frontendPublic scripts
/public/css//public/CSS files khusus frontendPublic styles
/public/images//public/Image assets khusus frontendPublic graphics

Referensi:

🔒 Security Tip: Selalu escape output sebelum menampilkan data ke frontend. Gunakan esc_html(), esc_attr(), esc_url(), atau wp_kses() untuk mencegah XSS attacks.

⚡ Performance Tip: Gunakan conditional enqueue untuk frontend assets. Jangan load CSS/JS di semua halaman jika tidak diperlukan. Gunakan is_single(), is_page(), atau conditional tags lainnya untuk optimize loading.

3. Struktur Custom untuk React + Vite [Advanced]

Untuk plugin modern yang menggunakan React dengan Vite sebagai build tool, ada struktur tambahan yang diperlukan. Ini memungkinkan Anda menggunakan teknologi frontend modern sambil tetap terintegrasi dengan WordPress.

Build Process dan WordPress Integration: Proses build dengan Vite mengubah source code React (JSX, modern JavaScript, CSS modules) menjadi production-ready files yang bisa di-enqueue ke WordPress:

  1. Development: Tulis code di /src/ dengan React components, modern JavaScript, dan CSS
  2. Build Process: Vite compile dan bundle semua files menjadi optimized JavaScript dan CSS di /dist/
  3. WordPress Integration: Enqueue compiled files dari /dist/ menggunakan wp_enqueue_script() dan wp_enqueue_style()
  4. Runtime: React app di-mount ke target element di WordPress page

Keuntungan Build Process:

  • ✅ Code splitting untuk mengurangi bundle size
  • ✅ Minification dan optimization untuk production
  • ✅ Hot Module Replacement (HMR) untuk development
  • ✅ Tree shaking untuk menghapus unused code
  • ✅ Modern JavaScript features (ES6+) dengan backward compatibility

3.1. File Konfigurasi Build

File-file konfigurasi build diperlukan untuk mengatur build process dengan Vite. File-file ini menentukan bagaimana source code React di-compile menjadi production-ready files yang siap digunakan di WordPress.

File/FolderLokasiDeskripsiWajib untuk React+Vite?
package.jsonRootNPM dependencies dan scripts (dev, build, watch)Ya
package-lock.jsonRootLock file untuk dependenciesAuto-generated
vite.config.jsRootKonfigurasi Vite untuk build process (plugins, output, rollup options)Ya
.gitignoreRootExclude node_modules, dist, dll dari version controlSangat recommended
.eslintrc atau eslint.config.jsRootESLint configuration untuk code qualityRecommended
tsconfig.jsonRootTypeScript configuration (jika pakai TS)Jika pakai TypeScript

3.2. Folder Source Code

Folder src berisi semua source code React sebelum di-build. Ini adalah tempat Anda menulis komponen React, hooks, services, dan assets yang akan di-compile oleh Vite menjadi production files.

File/FolderLokasiDeskripsiWajib untuk React+Vite?
/src/RootSource code React components sebelum di-buildYa
main.jsx atau index.jsx/src/Entry point untuk React app (ReactDOM.render)Ya
App.jsx/src/Main React componentYa (bisa nama lain)
ComponentName.jsx/src/React componentsSesuai kebutuhan
ComponentName.css/src/CSS untuk component specificOpsional
/src/components//src/Reusable React componentsRecommended untuk organisasi
/src/hooks//src/Custom React hooksJika ada custom hooks
/src/services//src/API calls dan utilitiesUntuk external API integration
/src/assets//src/Images, fonts, icons untuk React appJika ada static assets
/src/styles//src/Global styles atau shared CSSUntuk global styling

3.3. Folder Build Output

Folder dist adalah output dari build process Vite. File-file di sini adalah hasil kompilasi dari source code di folder src dan siap untuk di-enqueue ke WordPress. Folder ini biasanya di-ignore dari version control.

File/FolderLokasiDeskripsiWajib untuk React+Vite?
/dist/RootOutput folder dari Vite build (compiled production files)Auto-generated (jangan edit manual)
/dist/assets//dist/Compiled JS dan CSS files dengan hashAuto-generated oleh Vite
main.js/dist/assets/Compiled JavaScript bundleDi-enqueue ke WordPress
index.css/dist/assets/Compiled CSS bundleDi-enqueue ke WordPress
/node_modules/RootDependencies dari npmAuto-generated (add ke .gitignore)

3.4. Package.json Scripts untuk React + Vite

Tambahkan scripts berikut di package.json:

hljs json
{
  "name": "plugin-name",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "watch": "vite build --watch",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^4.2.1",
    "vite": "^5.0.0"
  }
}

Penjelasan Scripts:

  • dev: Development server dengan hot reload (untuk testing standalone)
  • build: Build production files ke folder /dist/
  • watch: Auto-rebuild saat file berubah (untuk development WordPress)
  • preview: Preview build production

3.5. Vite Config untuk WordPress Plugin

Contoh vite.config.js yang dioptimasi untuk WordPress:

hljs javascript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  build: {
    outDir: 'dist',
    emptyOutDir: true,
    rollupOptions: {
      input: {
        main: 'src/main.jsx'
      },
      output: {
        entryFileNames: 'assets/[name].js',
        chunkFileNames: 'assets/[name].[hash].js',
        assetFileNames: (assetInfo) => {
          if (/\.(css)$/.test(assetInfo.name)) {
            return 'assets/index.css';
          }
          return 'assets/[name].[hash].[ext]';
        }
      }
    }
  }
});

Penjelasan Konfigurasi:

  • outDir: 'dist': Output folder untuk build files
  • emptyOutDir: true: Hapus folder dist sebelum build baru
  • entryFileNames: Nama file untuk entry point (main.js)
  • chunkFileNames: Nama file untuk code splitting chunks
  • assetFileNames: Nama file untuk assets (CSS, images, dll)

3.6. Contoh Entry Point React

hljs jsx
// src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './styles/index.css';

// Target element di WordPress
const rootElement = document.getElementById('plugin-name-root');

if (rootElement) {
  const root = ReactDOM.createRoot(rootElement);
  root.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
}

3.7. Enqueue Assets di WordPress

Setelah build, enqueue assets di file plugin utama. Pastikan menggunakan wp_enqueue_script() dan wp_enqueue_style() dengan benar:

hljs php
// plugin-name.php

public function enqueue_react_assets() {
    // Enqueue React CSS
    wp_enqueue_style(
        'plugin-name-react',
        plugin_dir_url(__FILE__) . 'dist/assets/index.css',
        array(),
        $this->version
    );
    
    // Enqueue React JS
    wp_enqueue_script(
        'plugin-name-react',
        plugin_dir_url(__FILE__) . 'dist/assets/main.js',
        array(),
        $this->version,
        true // Load di footer
    );
    
    // Pass data dari PHP ke React (opsional)
    wp_localize_script('plugin-name-react', 'pluginNameData', array(
        'apiUrl' => rest_url('plugin-name/v1/'),
        'nonce' => wp_create_nonce('wp_rest'),
    ));
}

Referensi:

🔒 Security Tip: Selalu gunakan wp_create_nonce() untuk REST API requests dari React. Verifikasi nonce di server-side untuk mencegah unauthorized access.

⚡ Performance Tip: Gunakan conditional enqueue untuk React assets. Jangan load React di semua halaman jika tidak diperlukan. Juga pertimbangkan code splitting untuk mengurangi bundle size.

4. Contoh Struktur Lengkap Plugin dengan React + Vite

Berikut adalah contoh struktur lengkap untuk plugin WordPress modern yang menggunakan React + Vite:

text
/my-react-plugin/
├── plugin-name.php           # WAJIB - Main plugin file
├── readme.txt                # Wajib untuk WordPress.org
├── uninstall.php             # Opsional - Cleanup saat uninstall
├── index.php                 # Security - Prevent directory listing
├── LICENSE                   # Opsional
├── CHANGELOG.md              # Recommended
├── .gitignore                # Recommended
│
├── /languages/               # Opsional - i18n
│   ├── index.php
│   ├── plugin-name.pot
│   └── plugin-name-id_ID.po
│
├── /includes/                # Opsional - Shared PHP logic
│   ├── index.php
│   ├── class-plugin-name.php
│   ├── class-plugin-name-loader.php
│   ├── class-plugin-name-activator.php
│   └── class-plugin-name-deactivator.php
│
├── /admin/                   # Opsional - Admin area
│   ├── index.php
│   ├── class-plugin-name-admin.php
│   ├── /partials/
│   │   └── plugin-name-admin-display.php
│   ├── /js/
│   │   └── plugin-name-admin.js
│   └── /css/
│       └── plugin-name-admin.css
│
├── /public/                  # Opsional - Frontend
│   ├── index.php
│   ├── class-plugin-name-public.php
│   ├── /partials/
│   │   └── plugin-name-public-display.php
│   ├── /js/
│   │   └── plugin-name-public.js
│   └── /css/
│       └── plugin-name-public.css
│
├── package.json              # React+Vite - NPM config
├── package-lock.json         # React+Vite - Auto-generated
├── vite.config.js            # React+Vite - Vite config
├── .eslintrc.js              # Recommended
│
├── /node_modules/            # React+Vite - Auto-generated (gitignore)
│
├── /src/                     # React+Vite - Source code
│   ├── main.jsx              # Entry point
│   ├── App.jsx
│   ├── App.css
│   ├── /components/
│   │   ├── Header.jsx
│   │   ├── Footer.jsx
│   │   └── Button.jsx
│   ├── /hooks/
│   │   └── useWordPressData.js
│   ├── /services/
│   │   └── api.js
│   ├── /assets/
│   │   └── logo.svg
│   └── /styles/
│       └── index.css
│
└── /dist/                    # React+Vite - Build output (gitignore)
    └── /assets/
        ├── main.js           # Di-enqueue ke WordPress
        └── index.css         # Di-enqueue ke WordPress

5. Hands-On Tutorial: Building a Contact Form Plugin

Dalam tutorial ini, kita akan membangun plugin Contact Form lengkap dari awal dengan mengikuti best practices yang telah kita pelajari. Tutorial ini akan memandu Anda melalui 6 langkah untuk membuat plugin yang profesional dan aman.

Step 1: Setup Basic Structure

Tujuan: Membuat plugin minimal yang terdeteksi oleh WordPress dengan struktur dasar yang aman.

Langkah-langkah:

  1. Buat folder plugin:

    hljs bash
    mkdir contact-form-plugin
    cd contact-form-plugin
    
  2. Buat file utama contact-form-plugin.php:

    hljs php
    <?php
    /**
     * Plugin Name: Contact Form Plugin
     * Plugin URI: https://example.com/contact-form-plugin
     * Description: A simple and secure contact form plugin for WordPress
     * Version: 1.0.0
     * Author: Your Name
     * Author URI: https://example.com
     * License: GPL-2.0+
     * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
     * Text Domain: contact-form-plugin
     * Domain Path: /languages
     */
    
    // If this file is called directly, abort.
    if (!defined('ABSPATH')) {
        exit;
    }
    
    // Define plugin constants
    define('CFP_VERSION', '1.0.0');
    define('CFP_PLUGIN_DIR', plugin_dir_path(__FILE__));
    define('CFP_PLUGIN_URL', plugin_dir_url(__FILE__));
    
  3. Buat file index.php untuk security:

    hljs php
    <?php
    // Silence is golden.
    
  4. Aktifkan plugin di WordPress admin:

    • Buka WordPress Admin → Plugins
    • Cari "Contact Form Plugin"
    • Klik "Activate"

✅ Checkpoint:

  • Plugin muncul di WordPress Admin → Plugins page
  • Plugin bisa diaktifkan tanpa error
  • File index.php ada di root folder

Troubleshooting:

  • Jika plugin tidak muncul, pastikan plugin header format benar
  • Jika ada error, cek PHP syntax dengan php -l contact-form-plugin.php

Step 2: Create Includes Structure

Tujuan: Membuat struktur folder /includes/ dengan main plugin class dan activation hook.

Langkah-langkah:

  1. Buat folder dan file:

    hljs bash
    mkdir includes
    touch includes/index.php
    touch includes/class-contact-form-plugin.php
    touch includes/class-contact-form-plugin-activator.php
    
  2. Buat includes/index.php:

    hljs php
    <?php
    // Silence is golden.
    
  3. Buat includes/class-contact-form-plugin-activator.php:

    hljs php
    <?php
    if (!defined('ABSPATH')) {
        exit;
    }
    
    class Contact_Form_Plugin_Activator {
        public static function activate() {
            // Create default options
            if (false === get_option('cfp_settings')) {
                add_option('cfp_settings', array(
                    'email_to' => get_option('admin_email'),
                    'email_subject' => 'New Contact Form Submission',
                ));
            }
        }
    }
    
  4. Update contact-form-plugin.php untuk include class:

    hljs php
    // ... existing code ...
    
    // Include activator class
    require_once CFP_PLUGIN_DIR . 'includes/class-contact-form-plugin-activator.php';
    
    // Register activation hook
    register_activation_hook(__FILE__, array('Contact_Form_Plugin_Activator', 'activate'));
    
    // Include main plugin class
    require_once CFP_PLUGIN_DIR . 'includes/class-contact-form-plugin.php';
    
    // Initialize plugin
    function run_contact_form_plugin() {
        $plugin = new Contact_Form_Plugin();
        $plugin->run();
    }
    run_contact_form_plugin();
    
  5. Buat includes/class-contact-form-plugin.php:

    hljs php
    <?php
    if (!defined('ABSPATH')) {
        exit;
    }
    
    class Contact_Form_Plugin {
        public function __construct() {
            $this->load_dependencies();
        }
        
        private function load_dependencies() {
            // Load dependencies here
        }
        
        public function run() {
            // Initialize plugin
        }
    }
    

✅ Checkpoint:

  • Plugin activates tanpa errors
  • Default options dibuat di database
  • Struktur folder /includes/ lengkap dengan index.php

Troubleshooting:

  • Jika activation error, cek class name dan file paths
  • Pastikan semua file memiliki ABSPATH check

Step 3: Add Admin Interface

Tujuan: Membuat admin menu dan settings page untuk plugin.

Langkah-langkah:

  1. Buat folder admin:

    hljs bash
    mkdir admin
    mkdir admin/partials
    touch admin/index.php
    touch admin/class-contact-form-plugin-admin.php
    touch admin/partials/contact-form-plugin-admin-display.php
    
  2. Buat admin/index.php:

    hljs php
    <?php
    // Silence is golden.
    
  3. Buat admin/class-contact-form-plugin-admin.php:

    hljs php
    <?php
    if (!defined('ABSPATH')) {
        exit;
    }
    
    class Contact_Form_Plugin_Admin {
        private $plugin_name;
        private $version;
        
        public function __construct($plugin_name, $version) {
            $this->plugin_name = $plugin_name;
            $this->version = $version;
        }
        
        public function enqueue_styles() {
            wp_enqueue_style(
                $this->plugin_name,
                CFP_PLUGIN_URL . 'admin/css/contact-form-plugin-admin.css',
                array(),
                $this->version,
                'all'
            );
        }
        
        public function enqueue_scripts() {
            wp_enqueue_script(
                $this->plugin_name,
                CFP_PLUGIN_URL . 'admin/js/contact-form-plugin-admin.js',
                array('jquery'),
                $this->version,
                false
            );
        }
        
        public function add_admin_menu() {
            add_menu_page(
                'Contact Form Settings',
                'Contact Form',
                'manage_options',
                'contact-form-plugin',
                array($this, 'display_admin_page'),
                'dashicons-email-alt',
                30
            );
        }
        
        public function display_admin_page() {
            if (!current_user_can('manage_options')) {
                wp_die('You do not have sufficient permissions');
            }
            
            // Handle form submission
            if (isset($_POST['cfp_save_settings']) && check_admin_referer('cfp_save_settings')) {
                $settings = array(
                    'email_to' => sanitize_email($_POST['email_to']),
                    'email_subject' => sanitize_text_field($_POST['email_subject']),
                );
                update_option('cfp_settings', $settings);
                echo '<div class="notice notice-success"><p>Settings saved!</p></div>';
            }
            
            $settings = get_option('cfp_settings', array());
            include_once CFP_PLUGIN_DIR . 'admin/partials/contact-form-plugin-admin-display.php';
        }
    }
    
  4. Buat admin/partials/contact-form-plugin-admin-display.php:

    hljs php
    <?php
    if (!defined('ABSPATH')) {
        exit;
    }
    ?>
    <div class="wrap">
        <h1>Contact Form Plugin Settings</h1>
        <form method="post" action="">
            <?php wp_nonce_field('cfp_save_settings'); ?>
            <table class="form-table">
                <tr>
                    <th scope="row">
                        <label for="email_to">Email To</label>
                    </th>
                    <td>
                        <input type="email" 
                               id="email_to" 
                               name="email_to" 
                               value="<?php echo esc_attr($settings['email_to'] ?? ''); ?>" 
                               class="regular-text" />
                    </td>
                </tr>
                <tr>
                    <th scope="row">
                        <label for="email_subject">Email Subject</label>
                    </th>
                    <td>
                        <input type="text" 
                               id="email_subject" 
                               name="email_subject" 
                               value="<?php echo esc_attr($settings['email_subject'] ?? ''); ?>" 
                               class="regular-text" />
                    </td>
                </tr>
            </table>
            <?php submit_button('Save Settings', 'primary', 'cfp_save_settings'); ?>
        </form>
    </div>
    
  5. Update includes/class-contact-form-plugin.php:

    hljs php
    // ... existing code ...
    
    private function load_dependencies() {
        require_once CFP_PLUGIN_DIR . 'admin/class-contact-form-plugin-admin.php';
    }
    
    private function define_admin_hooks() {
        $plugin_admin = new Contact_Form_Plugin_Admin('contact-form-plugin', CFP_VERSION);
        
        add_action('admin_enqueue_scripts', array($plugin_admin, 'enqueue_styles'));
        add_action('admin_enqueue_scripts', array($plugin_admin, 'enqueue_scripts'));
        add_action('admin_menu', array($plugin_admin, 'add_admin_menu'));
    }
    
    public function run() {
        $this->define_admin_hooks();
    }
    

✅ Checkpoint:

  • Menu "Contact Form" muncul di WordPress admin sidebar
  • Settings page bisa diakses
  • Form submission berfungsi dan menyimpan settings
  • Nonce verification bekerja

Troubleshooting:

  • Jika menu tidak muncul, cek capability manage_options
  • Jika form tidak save, cek nonce verification
  • Pastikan semua input di-sanitize

Step 4: Implement Contact Form Logic

Tujuan: Membuat shortcode untuk contact form dan handler untuk form submission.

Langkah-langkah:

  1. Buat folder public:

    hljs bash
    mkdir public
    mkdir public/partials
    touch public/index.php
    touch public/class-contact-form-plugin-public.php
    touch public/partials/contact-form-display.php
    
  2. Buat public/index.php:

    hljs php
    <?php
    // Silence is golden.
    
  3. Buat public/class-contact-form-plugin-public.php:

    hljs php
    <?php
    if (!defined('ABSPATH')) {
        exit;
    }
    
    class Contact_Form_Plugin_Public {
        private $plugin_name;
        private $version;
        
        public function __construct($plugin_name, $version) {
            $this->plugin_name = $plugin_name;
            $this->version = $version;
        }
        
        public function enqueue_styles() {
            wp_enqueue_style(
                $this->plugin_name,
                CFP_PLUGIN_URL . 'public/css/contact-form-plugin-public.css',
                array(),
                $this->version,
                'all'
            );
        }
        
        public function enqueue_scripts() {
            wp_enqueue_script(
                $this->plugin_name,
                CFP_PLUGIN_URL . 'public/js/contact-form-plugin-public.js',
                array('jquery'),
                $this->version,
                true
            );
            
            wp_localize_script($this->plugin_name, 'cfpAjax', array(
                'ajaxurl' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce('cfp_ajax_nonce'),
            ));
        }
        
        public function register_shortcode() {
            add_shortcode('contact_form', array($this, 'display_contact_form'));
        }
        
        public function display_contact_form($atts) {
            $atts = shortcode_atts(array(
                'title' => 'Contact Us',
            ), $atts);
            
            ob_start();
            include CFP_PLUGIN_DIR . 'public/partials/contact-form-display.php';
            return ob_get_clean();
        }
        
        public function handle_form_submission() {
            // Verify nonce
            if (!isset($_POST['cfp_nonce']) || 
                !wp_verify_nonce($_POST['cfp_nonce'], 'cfp_form_submit')) {
                wp_send_json_error(array('message' => 'Security check failed'));
            }
            
            // Sanitize input
            $name = sanitize_text_field($_POST['name']);
            $email = sanitize_email($_POST['email']);
            $message = sanitize_textarea_field($_POST['message']);
            
            // Validate
            if (empty($name) || empty($email) || empty($message)) {
                wp_send_json_error(array('message' => 'All fields are required'));
            }
            
            if (!is_email($email)) {
                wp_send_json_error(array('message' => 'Invalid email address'));
            }
            
            // Get settings
            $settings = get_option('cfp_settings', array());
            $to = $settings['email_to'] ?? get_option('admin_email');
            $subject = $settings['email_subject'] ?? 'New Contact Form Submission';
            
            // Prepare email
            $email_message = "Name: $name\n";
            $email_message .= "Email: $email\n\n";
            $email_message .= "Message:\n$message";
            
            $headers = array(
                'From: ' . $name . ' <' . $email . '>',
                'Reply-To: ' . $email,
            );
            
            // Send email
            $sent = wp_mail($to, $subject, $email_message, $headers);
            
            if ($sent) {
                wp_send_json_success(array('message' => 'Thank you! Your message has been sent.'));
            } else {
                wp_send_json_error(array('message' => 'Failed to send email. Please try again.'));
            }
        }
    }
    
  4. Buat public/partials/contact-form-display.php:

    hljs php
    <?php
    if (!defined('ABSPATH')) {
        exit;
    }
    ?>
    <div class="cfp-contact-form">
        <h3><?php echo esc_html($atts['title']); ?></h3>
        <form id="cfp-contact-form" method="post">
            <?php wp_nonce_field('cfp_form_submit', 'cfp_nonce'); ?>
            <p>
                <label for="cfp-name">Name *</label>
                <input type="text" id="cfp-name" name="name" required />
            </p>
            <p>
                <label for="cfp-email">Email *</label>
                <input type="email" id="cfp-email" name="email" required />
            </p>
            <p>
                <label for="cfp-message">Message *</label>
                <textarea id="cfp-message" name="message" rows="5" required></textarea>
            </p>
            <p>
                <button type="submit">Send Message</button>
            </p>
            <div id="cfp-message-response"></div>
        </form>
    </div>
    
  5. Update includes/class-contact-form-plugin.php:

    hljs php
    // ... existing code ...
    
    private function load_dependencies() {
        require_once CFP_PLUGIN_DIR . 'admin/class-contact-form-plugin-admin.php';
        require_once CFP_PLUGIN_DIR . 'public/class-contact-form-plugin-public.php';
    }
    
    private function define_public_hooks() {
        $plugin_public = new Contact_Form_Plugin_Public('contact-form-plugin', CFP_VERSION);
        
        add_action('wp_enqueue_scripts', array($plugin_public, 'enqueue_styles'));
        add_action('wp_enqueue_scripts', array($plugin_public, 'enqueue_scripts'));
        add_action('init', array($plugin_public, 'register_shortcode'));
        add_action('wp_ajax_cfp_submit_form', array($plugin_public, 'handle_form_submission'));
        add_action('wp_ajax_nopriv_cfp_submit_form', array($plugin_public, 'handle_form_submission'));
    }
    
    public function run() {
        $this->define_admin_hooks();
        $this->define_public_hooks();
    }
    

✅ Checkpoint:

  • Shortcode [contact_form] bisa digunakan di post/page
  • Form tampil dengan benar
  • Form submission berfungsi dengan AJAX
  • Email terkirim dengan benar
  • Input validation bekerja
  • Nonce verification bekerja

Troubleshooting:

  • Jika shortcode tidak muncul, pastikan hook init digunakan
  • Jika AJAX tidak bekerja, cek wp_localize_script dan action name
  • Pastikan semua input di-sanitize dan di-validate

Step 5: Add Security & Best Practices

Tujuan: Menambahkan security checks dan best practices ke semua file.

Langkah-langkah:

  1. Pastikan semua file memiliki ABSPATH check:

    • ✅ Sudah ada di semua file yang kita buat
  2. Tambahkan capability checks di admin:

    hljs php
    // Sudah ada di display_admin_page()
    if (!current_user_can('manage_options')) {
        wp_die('You do not have sufficient permissions');
    }
    
  3. Pastikan semua output di-escape:

    hljs php
    // Di admin display
    echo esc_attr($settings['email_to']);
    echo esc_html($atts['title']);
    
  4. Tambahkan prepared statements jika menggunakan database:

    hljs php
    // Contoh jika perlu query database
    global $wpdb;
    $results = $wpdb->get_results($wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}table WHERE id = %d",
        $id
    ));
    
  5. Validasi file upload (jika ada):

    hljs php
    // Contoh validasi file upload
    $allowed_types = array('image/jpeg', 'image/png');
    if (!in_array($_FILES['file']['type'], $allowed_types)) {
        wp_die('Invalid file type');
    }
    

✅ Checkpoint:

  • Semua file memiliki ABSPATH check
  • Semua output di-escape dengan fungsi yang tepat
  • Capability checks ada di semua admin functions
  • Nonce verification ada di semua form submissions
  • Input validation dan sanitization lengkap

Troubleshooting:

  • Gunakan WordPress debug mode untuk menemukan security issues
  • Test dengan security plugins seperti Wordfence
  • Review code dengan security checklist

Step 6: Add Internationalization

Tujuan: Menambahkan support untuk multiple languages.

Langkah-langkah:

  1. Buat folder languages:

    hljs bash
    mkdir languages
    touch languages/index.php
    
  2. Buat languages/index.php:

    hljs php
    <?php
    // Silence is golden.
    
  3. Update semua strings dengan translation functions:

    hljs php
    // Di class-contact-form-plugin-admin.php
    add_menu_page(
        __('Contact Form Settings', 'contact-form-plugin'),
        __('Contact Form', 'contact-form-plugin'),
        'manage_options',
        'contact-form-plugin',
        array($this, 'display_admin_page'),
        'dashicons-email-alt',
        30
    );
    
    // Di contact-form-plugin-admin-display.php
    <h1><?php echo esc_html(__('Contact Form Plugin Settings', 'contact-form-plugin')); ?></h1>
    <label for="email_to"><?php echo esc_html(__('Email To', 'contact-form-plugin')); ?></label>
    
    // Di contact-form-display.php
    <h3><?php echo esc_html(__($atts['title'], 'contact-form-plugin')); ?></h3>
    <label for="cfp-name"><?php echo esc_html(__('Name', 'contact-form-plugin')); ?> *</label>
    
  4. Load text domain di main plugin file:

    hljs php
    // Di contact-form-plugin.php
    function load_contact_form_plugin_textdomain() {
        load_plugin_textdomain(
            'contact-form-plugin',
            false,
            dirname(plugin_basename(__FILE__)) . '/languages'
        );
    }
    add_action('plugins_loaded', 'load_contact_form_plugin_textdomain');
    
  5. Generate .pot file (template translation):

    hljs bash
    # Install WP-CLI jika belum ada
    wp i18n make-pot . languages/contact-form-plugin.pot --domain=contact-form-plugin
    

✅ Checkpoint:

  • Semua user-facing strings menggunakan translation functions
  • Text domain konsisten di seluruh plugin
  • .pot file berhasil di-generate
  • Translation functions menggunakan text domain yang benar

Troubleshooting:

  • Pastikan text domain sama di semua translation functions
  • Gunakan WP-CLI untuk generate .pot file
  • Test dengan mengubah WordPress language

Selamat! Anda telah berhasil membangun plugin Contact Form yang lengkap dengan struktur yang profesional, aman, dan mengikuti best practices WordPress! 🎉

6. Workflow Development dengan React + Vite

Berikut adalah workflow yang direkomendasikan untuk development plugin WordPress dengan React + Vite:

5.1. Development Mode

  1. Jalankan watch mode:

    hljs bash
    npm run watch
    

    Ini akan auto-rebuild saat file di folder /src/ berubah.

  2. Aktifkan plugin di WordPress dan refresh halaman untuk melihat perubahan.

  3. Development dengan Hot Reload (opsional): Jika ingin menggunakan Vite dev server untuk testing standalone:

    hljs bash
    npm run dev
    

5.2. Production Build

Sebelum release, build production files:

hljs bash
npm run build

Ini akan generate optimized files ke folder /dist/ yang siap untuk production.

5.3. WordPress Enqueue

Pastikan file plugin utama meng-enqueue assets dari /dist/assets/ menggunakan wp_enqueue_script() dan wp_enqueue_style():

hljs php
// Enqueue di admin atau public sesuai kebutuhan
wp_enqueue_script('plugin-name-react', plugin_dir_url(__FILE__) . 'dist/assets/main.js');
wp_enqueue_style('plugin-name-react', plugin_dir_url(__FILE__) . 'dist/assets/index.css');

⚡ Performance Tip: Gunakan version number untuk cache busting. Setiap kali Anda update plugin, increment version number agar browser load file baru:

hljs php
wp_enqueue_script('plugin-name-react', ..., array(), $this->version);

🔒 Security Tip: Selalu verifikasi nonce untuk AJAX requests dari React. Gunakan wp_verify_nonce() di server-side sebelum memproses request.

5.4. Git Configuration

Pastikan .gitignore meng-exclude folder yang auto-generated:

hljs gitignore
# Dependencies
node_modules/

# Build output
dist/

# Environment
.env
.env.local

# IDE
.vscode/
.idea/

6. Best Practices & Tips [All Levels]

6.1. Security Best Practices

Keamanan adalah prioritas utama dalam pengembangan plugin WordPress. Berikut adalah praktik keamanan yang harus selalu diterapkan. Pelajari lebih lanjut di Plugin Security - WordPress Plugin Handbook.

  1. Gunakan ABSPATH check:

    hljs php
    if (!defined('ABSPATH')) {
        exit;
    }
    

    Mengapa penting: Mencegah akses langsung ke file PHP melalui URL. Pelajari lebih lanjut.

  2. Sanitize dan validate input:

    hljs php
    // Sanitize input
    $value = sanitize_text_field($_POST['value']);
    $email = sanitize_email($_POST['email']);
    $url = esc_url_raw($_POST['url']);
    $html = wp_kses_post($_POST['html']);
    
    // Validate input
    if (!is_email($email)) {
        wp_die('Invalid email address');
    }
    

    Mengapa penting: Mencegah SQL injection dan XSS attacks. Pelajari lebih lanjut.

  3. Nonce untuk forms dan AJAX:

    hljs php
    // Di form
    wp_nonce_field('plugin_name_action', 'plugin_name_nonce');
    
    // Di handler
    if (!isset($_POST['plugin_name_nonce']) || 
        !wp_verify_nonce($_POST['plugin_name_nonce'], 'plugin_name_action')) {
        wp_die('Security check failed');
    }
    
    // Untuk AJAX
    check_ajax_referer('plugin_name_ajax', 'nonce');
    

    Mengapa penting: Mencegah CSRF (Cross-Site Request Forgery) attacks. Pelajari lebih lanjut.

  4. Capability checks:

    hljs php
    if (!current_user_can('manage_options')) {
        wp_die('You do not have sufficient permissions');
    }
    
    // Atau untuk specific capability
    if (!current_user_can('edit_posts')) {
        return;
    }
    

    Mengapa penting: Memastikan hanya user dengan permission yang tepat yang bisa mengakses fungsi tertentu. Pelajari lebih lanjut.

  5. Escape output:

    hljs php
    // Untuk HTML
    echo esc_html($user_input);
    
    // Untuk attributes
    echo '<div class="' . esc_attr($class_name) . '">';
    
    // Untuk URLs
    echo '<a href="' . esc_url($url) . '">Link</a>';
    
    // Untuk JavaScript
    echo '<script>var data = ' . wp_json_encode($data) . ';</script>';
    

    Mengapa penting: Mencegah XSS (Cross-Site Scripting) attacks saat menampilkan data. Pelajari lebih lanjut.

  6. Prepared statements untuk database:

    hljs php
    // JANGAN lakukan ini (vulnerable to SQL injection)
    $wpdb->query("SELECT * FROM {$wpdb->prefix}table WHERE id = " . $_POST['id']);
    
    // LAKUKAN ini (secure)
    $wpdb->prepare("SELECT * FROM {$wpdb->prefix}table WHERE id = %d", $_POST['id']);
    

    Mengapa penting: Mencegah SQL injection attacks. Pelajari lebih lanjut.

  7. Secure file uploads:

    hljs php
    // Validasi file type
    $allowed_types = array('image/jpeg', 'image/png');
    if (!in_array($_FILES['file']['type'], $allowed_types)) {
        wp_die('Invalid file type');
    }
    
    // Validasi file size
    if ($_FILES['file']['size'] > 5000000) {
        wp_die('File too large');
    }
    
    // Gunakan wp_handle_upload()
    $upload = wp_handle_upload($_FILES['file'], array('test_form' => false));
    

    Mengapa penting: Mencegah upload file berbahaya. Pelajari lebih lanjut.

6.2. Performance Tips

Performance adalah faktor penting untuk user experience dan SEO. Berikut adalah tips untuk mengoptimalkan performa plugin Anda.

  1. Lazy load assets:

    hljs php
    // Enqueue hanya di halaman yang membutuhkan
    public function enqueue_scripts() {
        // Hanya load di admin pages plugin
        $screen = get_current_screen();
        if ($screen->id !== 'toplevel_page_plugin-name') {
            return;
        }
        
        wp_enqueue_script('plugin-name-admin', ...);
    }
    
    // Atau untuk frontend
    public function enqueue_public_scripts() {
        // Hanya load di single post
        if (!is_single()) {
            return;
        }
        wp_enqueue_script('plugin-name-public', ...);
    }
    

    Manfaat: Mengurangi HTTP requests dan memory usage. Pelajari lebih lanjut.

  2. Minify dan optimize assets:

    hljs php
    // Gunakan version number untuk cache busting
    wp_enqueue_script(
        'plugin-name',
        plugin_dir_url(__FILE__) . 'js/script.min.js',
        array(),
        $this->version, // Version number
        true // Load in footer
    );
    

    Manfaat: File lebih kecil = load time lebih cepat. Gunakan build tools seperti Vite, Webpack, atau Gulp untuk minify.

  3. Cache API calls dengan transients:

    hljs php
    // Get cached data
    $data = get_transient('plugin_name_api_data');
    
    if (false === $data) {
        // Fetch dari API
        $data = wp_remote_get('https://api.example.com/data');
        $data = json_decode(wp_remote_retrieve_body($data));
        
        // Cache untuk 1 jam
        set_transient('plugin_name_api_data', $data, HOUR_IN_SECONDS);
    }
    
    return $data;
    

    Manfaat: Mengurangi API calls dan meningkatkan response time. Pelajari lebih lanjut.

  4. Optimize database queries:

    hljs php
    // JANGAN lakukan multiple queries dalam loop (N+1 problem)
    foreach ($posts as $post) {
        $meta = get_post_meta($post->ID, 'key', true); // BAD - Multiple queries
    }
    
    // LAKUKAN batch query
    $post_ids = wp_list_pluck($posts, 'ID');
    $meta = get_post_meta($post_ids, 'key', true); // GOOD - Single query
    
    // Gunakan WP_Query dengan benar
    $query = new WP_Query(array(
        'post_type' => 'custom_post',
        'posts_per_page' => 10,
        'no_found_rows' => true, // Skip pagination count jika tidak perlu
        'update_post_meta_cache' => false, // Skip meta cache jika tidak perlu
        'update_post_term_cache' => false, // Skip term cache jika tidak perlu
    ));
    
    // Gunakan wp_cache untuk expensive queries
    $cache_key = 'plugin_name_query_' . md5(serialize($args));
    $results = wp_cache_get($cache_key);
    if (false === $results) {
        $results = new WP_Query($args);
        wp_cache_set($cache_key, $results, '', 3600);
    }
    

    Manfaat: Mengurangi database load dan meningkatkan query speed. Pelajari lebih lanjut.

    ⚡ Performance Tip: Hindari N+1 query problem. Selalu gunakan batch queries atau preload data yang diperlukan. Gunakan update_post_meta_cache dan update_post_term_cache dengan bijak untuk mengurangi queries.

  5. Use object caching:

    hljs php
    // Gunakan wp_cache untuk expensive operations
    $result = wp_cache_get('plugin_name_expensive_operation');
    
    if (false === $result) {
        $result = expensive_operation();
        wp_cache_set('plugin_name_expensive_operation', $result, '', 3600);
    }
    

    Manfaat: Mengurangi computation time untuk operations yang mahal.

  6. Defer JavaScript loading:

    hljs php
    wp_enqueue_script(
        'plugin-name',
        plugin_dir_url(__FILE__) . 'js/script.js',
        array(),
        $this->version,
        true // Load in footer
    );
    
    // Atau add defer attribute
    add_filter('script_loader_tag', function($tag, $handle) {
        if ('plugin-name' === $handle) {
            return str_replace(' src', ' defer src', $tag);
        }
        return $tag;
    }, 10, 2);
    

    Manfaat: Tidak blocking page rendering, meningkatkan perceived performance.

  7. Optimize images dan assets:

    • Gunakan WebP format untuk images
    • Lazy load images yang tidak critical
    • Compress CSS dan JavaScript
    • Use CDN untuk static assets jika memungkinkan

6.3. Naming Conventions

Mengikuti naming conventions yang konsisten membantu mencegah konflik dengan plugin lain dan membuat kode lebih mudah dibaca. Ikuti WordPress Coding Standards untuk konsistensi.

  1. Plugin name: Gunakan lowercase dengan hyphens: my-awesome-plugin

    • Mengapa: Mencegah konflik dengan plugin lain dan sesuai dengan WordPress standards
    • Contoh: woocommerce, contact-form-7, yoast-seo
  2. Class names: PascalCase dengan prefix: My_Awesome_Plugin

    • Mengapa: Mencegah class name collision
    • Contoh: WC_Product, CF7_Contact_Form
  3. Function names: snake_case dengan prefix: my_awesome_plugin_function()

    • Mengapa: Mencegah function name collision
    • Contoh: wc_get_product(), cf7_save_form()
  4. Constants: UPPERCASE dengan prefix: MY_AWESOME_PLUGIN_VERSION

    • Mengapa: Mudah diidentifikasi sebagai constant
    • Contoh: WC_VERSION, WP_DEBUG
  5. Variables: snake_case dengan prefix: $my_plugin_option

    • Mengapa: Konsisten dengan WordPress coding standards

Referensi:

6.4. Organization Tips

Organisasi kode yang baik membuat plugin lebih mudah dirawat dan dikembangkan. Berikut adalah tips untuk mengorganisir kode plugin Anda.

  1. Separate concerns: Pisahkan admin, public, dan shared logic

    • Admin logic/admin/ folder
    • Public logic/public/ folder
    • Shared logic/includes/ folder
    • Manfaat: Mudah di-maintain dan test
  2. Use autoloading: Gunakan PSR-4 atau WordPress autoloader

    hljs php
    // Autoloader sederhana
    spl_autoload_register(function($class) {
        $prefix = 'Plugin_Name\\';
        $base_dir = plugin_dir_path(__FILE__) . 'includes/';
        
        $len = strlen($prefix);
        if (strncmp($prefix, $class, $len) !== 0) {
            return;
        }
        
        $relative_class = substr($class, $len);
        $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
        
        if (file_exists($file)) {
            require $file;
        }
    });
    

    Manfaat: Tidak perlu manual require_once untuk setiap class

  3. Documentation: Tambahkan PHPDoc untuk semua functions dan classes

    hljs php
    /**
     * Get plugin option value
     *
     * @param string $option_name Option name
     * @param mixed  $default     Default value if option doesn't exist
     * @return mixed Option value or default
     * @since 1.0.0
     */
    public function get_option($option_name, $default = false) {
        return get_option('plugin_name_' . $option_name, $default);
    }
    

    Manfaat: IDE autocomplete dan dokumentasi otomatis

  4. Version control: Gunakan semantic versioning (1.0.0, 1.1.0, 2.0.0)

    • Major (2.0.0): Breaking changes
    • Minor (1.1.0): New features, backward compatible
    • Patch (1.0.1): Bug fixes, backward compatible
    • Manfaat: User tahu apa yang berubah dan apakah update aman

Referensi:

6.5. Testing & Debugging

Testing dan debugging adalah bagian penting dari development process. Berikut adalah praktik yang direkomendasikan.

  1. Enable WP_DEBUG: Aktifkan di wp-config.php untuk development

    hljs php
    define('WP_DEBUG', true);
    define('WP_DEBUG_LOG', true);
    define('WP_DEBUG_DISPLAY', false);
    

    Manfaat: Menampilkan errors dan warnings untuk debugging

  2. Use WordPress coding standards: Install PHPCS dengan WordPress standards

    hljs bash
    composer require --dev wp-coding-standards/wpcs
    phpcs --standard=WordPress plugin-name.php
    

    Manfaat: Memastikan kode mengikuti WordPress standards

  3. Test di berbagai environment:

    • PHP versions: 7.4, 8.0, 8.1, 8.2, 8.3
    • WordPress versions: Latest dan beberapa versi sebelumnya
    • Different themes: Default themes dan popular themes
    • Manfaat: Memastikan kompatibilitas luas
  4. Browser compatibility: Test di berbagai browser

    • Chrome, Firefox, Safari, Edge
    • Mobile browsers (iOS Safari, Chrome Mobile)
    • Manfaat: Memastikan UI bekerja di semua browser
  5. Unit testing: Gunakan PHPUnit untuk automated testing

    hljs php
    class Plugin_Test extends WP_UnitTestCase {
        public function test_plugin_activation() {
            $this->assertTrue(is_plugin_active('plugin-name/plugin-name.php'));
        }
    }
    

    Manfaat: Automated testing mengurangi bugs

Referensi:

7. Troubleshooting: Common Errors dan Solutions

Dalam development plugin WordPress, Anda mungkin menghadapi berbagai error dan masalah. Berikut adalah common errors dan solusinya:

Plugin Tidak Muncul di Admin

Gejala: Plugin tidak muncul di WordPress Admin → Plugins page

Penyebab:

  • Plugin header format salah atau tidak lengkap
  • Syntax error di file PHP
  • Plugin folder name tidak sesuai dengan main file name

Solusi:

hljs php
// Pastikan plugin header lengkap dan benar
/**
 * Plugin Name: Your Plugin Name  // WAJIB - harus ada
 * Version: 1.0.0                 // WAJIB untuk update mechanism
 * Text Domain: your-plugin-name  // Penting untuk i18n
 */

Cek syntax error:

hljs bash
php -l your-plugin-file.php

Validasi:

  • Pastikan Plugin Name ada di header
  • Pastikan tidak ada syntax error
  • Pastikan file PHP valid

Activation Errors

Gejala: Error saat mengaktifkan plugin (fatal error, white screen)

Penyebab:

  • Class atau function name collision dengan plugin lain
  • Missing dependencies atau files
  • PHP version incompatibility
  • Fatal error di activation hook

Solusi:

hljs php
// Gunakan unique prefix untuk class names
class Your_Unique_Prefix_Plugin {
    // ...
}

// Cek file exists sebelum require
if (file_exists(CFP_PLUGIN_DIR . 'includes/class-plugin.php')) {
    require_once CFP_PLUGIN_DIR . 'includes/class-plugin.php';
}

// Handle errors di activation hook
register_activation_hook(__FILE__, function() {
    try {
        // Activation code
    } catch (Exception $e) {
        deactivate_plugins(plugin_basename(__FILE__));
        wp_die('Activation failed: ' . $e->getMessage());
    }
});

Debug:

  • Enable WP_DEBUG di wp-config.php
  • Cek error log di /wp-content/debug.log
  • Test activation di clean WordPress installation

Vite Build Errors

Gejala: Error saat build dengan Vite (npm run build fails)

Penyebab:

  • Missing dependencies di package.json
  • Vite config salah
  • Node.js version incompatibility
  • Path issues di vite.config.js

Solusi:

hljs bash
# Reinstall dependencies
rm -rf node_modules package-lock.json
npm install

# Check Node.js version (requires 16+)
node --version

# Clear cache
npm cache clean --force

Vite config check:

hljs javascript
// Pastikan paths benar
export default defineConfig({
  build: {
    outDir: 'dist',  // Pastikan folder ini bisa di-write
    // ...
  }
});

Troubleshooting:

  • Pastikan node_modules dan dist di .gitignore
  • Cek file permissions untuk folder dist
  • Test dengan npm run dev terlebih dahulu

Permission Errors

Gejala: Cannot write files, permission denied errors

Penyebab:

  • File/folder permissions tidak benar
  • WordPress tidak punya write access
  • Server security restrictions

Solusi:

hljs bash
# Set correct permissions (Linux/Mac)
chmod 755 wp-content/plugins/your-plugin/
chmod 644 wp-content/plugins/your-plugin/*.php

# Untuk folder yang perlu write access
chmod 775 wp-content/plugins/your-plugin/dist/

WordPress way:

hljs php
// Gunakan WordPress functions untuk file operations
$upload_dir = wp_upload_dir();
$file_path = $upload_dir['basedir'] . '/your-file.txt';

// WordPress akan handle permissions
file_put_contents($file_path, $content);

Translation Not Loading

Gejala: Translation strings tidak muncul dalam bahasa yang dipilih

Penyebab:

  • Text domain tidak konsisten
  • Translation file tidak ada atau salah lokasi
  • Text domain tidak di-load dengan benar
  • .mo file tidak di-generate dari .po file

Solusi:

hljs php
// Pastikan text domain konsisten
__('String', 'your-plugin-name');  // 'your-plugin-name' harus sama di semua tempat

// Load text domain dengan benar
load_plugin_textdomain(
    'your-plugin-name',
    false,
    dirname(plugin_basename(__FILE__)) . '/languages'
);

// Pastikan hook benar
add_action('plugins_loaded', 'load_textdomain_function');

Generate translation files:

hljs bash
# Generate .pot file
wp i18n make-pot . languages/your-plugin-name.pot --domain=your-plugin-name

# Compile .po to .mo
msgfmt languages/your-plugin-name-id_ID.po -o languages/your-plugin-name-id_ID.mo

Validasi:

  • Cek text domain sama di semua translation functions
  • Pastikan .mo file ada dan di-compile dengan benar
  • Test dengan mengubah WordPress language

AJAX Requests Not Working

Gejala: AJAX requests dari frontend tidak berfungsi (404, 403, atau tidak ada response)

Penyebab:

  • Action name tidak terdaftar dengan benar
  • Nonce verification failed
  • wp_localize_script tidak dipanggil
  • URL AJAX salah

Solusi:

hljs php
// Register AJAX action (untuk logged-in users)
add_action('wp_ajax_my_action', 'my_ajax_handler');

// Register AJAX action (untuk non-logged-in users)
add_action('wp_ajax_nopriv_my_action', 'my_ajax_handler');

// Handler function
function my_ajax_handler() {
    // Verify nonce
    check_ajax_referer('my_nonce', 'nonce');
    
    // Process request
    wp_send_json_success(array('message' => 'Success'));
}

// Localize script dengan benar
wp_localize_script('my-script', 'myAjax', array(
    'ajaxurl' => admin_url('admin-ajax.php'),
    'nonce' => wp_create_nonce('my_nonce'),
));

JavaScript side:

hljs javascript
jQuery.ajax({
    url: myAjax.ajaxurl,
    type: 'POST',
    data: {
        action: 'my_action',
        nonce: myAjax.nonce,
        // other data
    },
    success: function(response) {
        console.log(response);
    }
});

Debug:

  • Cek browser console untuk error messages
  • Cek Network tab untuk melihat request/response
  • Pastikan action name match antara PHP dan JavaScript

Security Issues

Gejala: Plugin vulnerable to attacks, security warnings

Penyebab:

  • Missing ABSPATH checks
  • Input tidak di-sanitize
  • Output tidak di-escape
  • Missing nonce verification
  • Missing capability checks

Solusi:

hljs php
// 1. ABSPATH check di setiap file
if (!defined('ABSPATH')) {
    exit;
}

// 2. Sanitize input
$value = sanitize_text_field($_POST['value']);
$email = sanitize_email($_POST['email']);
$url = esc_url_raw($_POST['url']);

// 3. Escape output
echo esc_html($user_input);
echo esc_attr($attribute);
echo esc_url($url);

// 4. Nonce verification
if (!wp_verify_nonce($_POST['nonce'], 'action_name')) {
    wp_die('Security check failed');
}

// 5. Capability checks
if (!current_user_can('manage_options')) {
    wp_die('Insufficient permissions');
}

Validation Checklist:

  • Semua file memiliki ABSPATH check
  • Semua user input di-sanitize
  • Semua output di-escape
  • Semua forms memiliki nonce
  • Semua admin functions memiliki capability checks
  • Database queries menggunakan prepared statements

Debugging Tips

Enable WordPress Debug Mode:

hljs php
// wp-config.php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);  // Jangan tampilkan di frontend

Check Error Logs:

  • WordPress: /wp-content/debug.log
  • Server: /var/log/apache2/error.log atau /var/log/nginx/error.log
  • PHP: Check php.ini untuk error_log location

Use WordPress Debug Functions:

hljs php
// Log debug messages
error_log('Debug message: ' . print_r($data, true));

// WordPress debug functions
if (defined('WP_DEBUG') && WP_DEBUG) {
    error_log('Plugin debug: ' . $message);
}

Browser Developer Tools:

  • Console tab untuk JavaScript errors
  • Network tab untuk AJAX requests
  • Elements tab untuk HTML structure

Validation Checklist: Sebelum release plugin, pastikan:

  • Plugin activates tanpa errors
  • Semua features berfungsi
  • Tidak ada PHP warnings/notices
  • Tidak ada JavaScript errors
  • Security checks lengkap
  • Translation strings wrapped dengan benar
  • Code mengikuti WordPress coding standards
  • Tested di berbagai WordPress versions
  • Tested di berbagai PHP versions

8. Kesimpulan

Struktur plugin WordPress yang baik adalah fondasi untuk plugin yang profesional, mudah dirawat, dan scalable. Mulai dari struktur minimal dengan satu file PHP, hingga struktur kompleks dengan React + Vite, semuanya memiliki tempatnya masing-masing tergantung kebutuhan project.

Key Takeaways:

  1. Minimal requirement: Hanya butuh 1 file PHP dengan plugin header
  2. Best practices: Struktur lengkap dengan /includes/, /admin/, /public/ untuk plugin kompleks - Pelajari lebih lanjut
  3. Modern stack: React + Vite memungkinkan development modern sambil tetap terintegrasi dengan WordPress
  4. Security first: Selalu gunakan index.php di setiap folder dan sanitize input - Security Best Practices
  5. Documentation: Dokumentasi yang baik memudahkan maintenance dan kolaborasi - Gunakan readme.txt format untuk WordPress.org
  6. Performance: Optimize assets loading dan gunakan caching untuk meningkatkan performa
  7. Internationalization: Siapkan plugin untuk multi-bahasa sejak awal dengan i18n

Next Steps:

Referensi Lengkap WordPress Documentation

Untuk mempelajari lebih dalam tentang setiap aspek pengembangan plugin WordPress, berikut adalah referensi lengkap:

Dasar-dasar Plugin

Keamanan

Internationalization

Admin Development

Plugin Lifecycle

WordPress.org Repository

Coding Standards

Dengan mengikuti panduan ini dan referensi di atas, Anda sudah siap untuk membangun plugin WordPress yang profesional, aman, dan mengikuti best practices. Selamat coding! 🚀

WordPressPlugin DevelopmentBest PracticesTutorialReactVite