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):
/my-plugin/
└── plugin.php # Semua kode di satu file
✅ After (Struktur Baik):
/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:
- Semua kode di satu file - Membuat maintenance menjadi nightmare
- Lupa menambahkan
index.php- Membuka celah keamanan untuk directory listing - Tidak ada
ABSPATHcheck - File bisa diakses langsung melalui URL - Struktur folder tidak konsisten - Sulit untuk kolaborasi tim
- Tidak ada separation of concerns - Admin dan public logic tercampur
- 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:
- ✅ Membuat plugin WordPress minimal dengan plugin header yang benar dan struktur file dasar
- ✅ Mengimplementasikan security best practices termasuk
index.phpdi setiap folder danABSPATHchecks untuk mencegah direct access - ✅ Mengorganisir plugin dengan struktur folder yang direkomendasikan menggunakan
/includes/,/admin/, dan/public/untuk separation of concerns - ✅ Menyiapkan build process React+Vite untuk plugin modern dengan integrasi WordPress yang benar
- ✅ Mempersiapkan plugin untuk WordPress.org submission dengan
readme.txtyang sesuai standar dan struktur yang benar - ✅ Menerapkan teknik optimasi performa seperti lazy loading assets, caching, dan conditional enqueue
- ✅ 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.txtformat) - ✅ 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:
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:
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:
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:
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:
__('Hello World', 'plugin-name'); // 'plugin-name' adalah text domain
Plugin Header
Metadata di bagian atas file plugin utama yang memberitahu WordPress tentang plugin:
/**
* 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:
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)
/my-plugin/
└── plugin-name.php # Hanya 1 file dengan plugin header
2. Struktur Recommended (Section 2)
/my-plugin/
├── plugin-name.php
├── index.php
├── readme.txt
├── /includes/
├── /admin/
├── /public/
└── /languages/
3. Struktur Modern dengan React+Vite (Section 3)
/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:
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:
// 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:
// 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:
- Plugin Discovery: WordPress scan folder
/wp-content/plugins/untuk mencari file PHP dengan plugin header - Header Parsing: WordPress membaca plugin header untuk mendapatkan metadata (name, version, dll)
- Active Plugin Check: WordPress hanya memuat plugin yang aktif (checked di database)
- Sequential Loading: Plugin dimuat secara berurutan (bisa dikontrol dengan plugin priority)
- Hook Registration: Plugin mendaftarkan hooks mereka ke WordPress
- 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):
// 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):
// 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/Folder | Lokasi | Deskripsi |
|---|---|---|
plugin-name.php | Root | File 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:
<?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.
2. File dan Folder Opsional (Recommended Best Practice) [Beginner - Intermediate]
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/Folder | Lokasi | Deskripsi | Kapan Digunakan |
|---|---|---|---|
readme.txt | Root | Informasi plugin untuk WordPress.org directory (description, installation, FAQ, changelog, screenshots) | Wajib jika submit ke WordPress.org repository |
LICENSE atau license.txt | Root | File lisensi (biasanya GPL 2.0 seperti WordPress) | Jika distribusi open source |
CHANGELOG.md | Root | Dokumentasi perubahan versi plugin | Recommended untuk tracking changes |
Referensi:
readme.txtformat - Format standar untuk WordPress.org repository- WordPress License - GPL v2 atau lebih baru
Contoh readme.txt untuk WordPress.org:
Format readme.txt harus mengikuti standar WordPress.org. Berikut adalah contoh lengkap:
=== 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.txtmengikuti 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/Folder | Lokasi | Deskripsi | Kapan Digunakan |
|---|---|---|---|
index.php | Root & semua folder | File blank untuk mencegah directory listing | Security best practice - wajib di setiap folder |
Contoh index.php untuk security:
<?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 gunakanABSPATHcheck di awal setiap file PHP untuk mencegah direct access:hljs phpif (!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/Folder | Lokasi | Deskripsi | Kapan Digunakan |
|---|---|---|---|
uninstall.php | Root | Cleanup data saat plugin di-uninstall (hapus options, tables, transients) | Jika plugin menyimpan data di database |
Contoh uninstall.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:
- Uninstall Hook Documentation - Cara yang benar untuk cleanup saat uninstall
- Activation/Deactivation Hooks - Mengelola plugin lifecycle
🔒 Security Tip: Selalu cek
WP_UNINSTALL_PLUGINconstant sebelum melakukan cleanup. Jangan hapus data user atau konten penting tanpa konfirmasi.⚡ Performance Tip: Gunakan
delete_option()dandelete_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/Folder | Lokasi | Deskripsi | Kapan Digunakan |
|---|---|---|---|
/languages/ | Root | Folder untuk internationalization files (.pot, .po, .mo) | Jika plugin support multi-bahasa |
plugin-name.pot | /languages/ | Translation template file | Base file untuk translator |
plugin-name-id_ID.po | /languages/ | Translation file untuk bahasa tertentu | Setelah diterjemahkan |
plugin-name-id_ID.mo | /languages/ | Compiled translation file | Generated dari .po file |
Cara menggunakan translation:
// 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:
- Translation Functions - Fungsi translation yang tersedia
- Plural Forms - Menangani plural forms
💡 Tip: Selalu gunakan text domain yang konsisten di seluruh plugin. Text domain biasanya sama dengan plugin slug untuk memudahkan maintenance.
Referensi:
- Internationalization Best Practices - Panduan lengkap i18n
- Text Domain - Menggunakan text domain dengan benar
- Loading Text Domain - Load translation files
⚡ Performance Tip: Load text domain hanya saat diperlukan. Gunakan
load_plugin_textdomain()di hook yang tepat (biasanyaplugins_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/Folder | Lokasi | Deskripsi | Kapan Digunakan |
|---|---|---|---|
/includes/ | Root | Core functionality shared antara admin & public | Plugin dengan kompleksitas medium-large |
class-plugin-name.php | /includes/ | Main plugin class dengan initialization | OOP architecture |
class-plugin-name-loader.php | /includes/ | Class untuk register hooks dengan WordPress | Boilerplate 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 files | Handle translations |
Referensi:
- Plugin Best Practices - Struktur dan organisasi plugin
- Hooks and Filters - Menggunakan WordPress hooks dengan benar
Contoh struktur class:
<?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
ABSPATHcheck 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 adminadmin_init- Hook yang dijalankan saat admin area di-initializeadmin_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/Folder | Lokasi | Deskripsi | Kapan Digunakan |
|---|---|---|---|
/admin/ | Root | Semua file khusus admin area | Jika plugin punya admin interface |
class-plugin-name-admin.php | /admin/ | Main admin class dengan hooks dan functionality | Admin-specific logic |
/admin/partials/ | /admin/ | File PHP dengan HTML output untuk admin UI | Pisahkan view dari logic |
/admin/js/ | /admin/ | JavaScript files khusus admin | Admin scripts |
/admin/css/ | /admin/ | CSS files khusus admin | Admin styles |
/admin/images/ | /admin/ | Image assets khusus admin | Admin UI graphics |
Referensi:
- Admin Menus - Membuat menu admin
- Enqueue Scripts and Styles - Cara yang benar untuk enqueue assets
Contoh enqueue admin assets:
Gunakan wp_enqueue_style() dan wp_enqueue_script() untuk enqueue assets dengan benar:
// 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:
- Including Assets - Panduan lengkap enqueue assets
- wp_localize_script() - Pass data dari PHP ke JavaScript
🔒 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 frontendwp_head- Hook untuk menambahkan code di<head>sectionwp_footer- Hook untuk menambahkan code sebelum closing</body>tagthe_content- Filter untuk memodifikasi post contentshortcode- 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 contentesc_attr()- Untuk HTML attributesesc_url()- Untuk URLswp_kses_post()- Untuk HTML dengan allowed tagswp_json_encode()- Untuk JavaScript data
Selalu escape output sebelum menampilkan data user atau dari database ke frontend.
| File/Folder | Lokasi | Deskripsi | Kapan Digunakan |
|---|---|---|---|
/public/ | Root | Semua file khusus frontend | Jika plugin render output di frontend |
class-plugin-name-public.php | /public/ | Main public class dengan hooks dan functionality | Frontend-specific logic |
/public/partials/ | /public/ | File PHP dengan HTML output untuk frontend | Frontend view templates |
/public/js/ | /public/ | JavaScript files khusus frontend | Public scripts |
/public/css/ | /public/ | CSS files khusus frontend | Public styles |
/public/images/ | /public/ | Image assets khusus frontend | Public graphics |
Referensi:
- Enqueue Scripts and Styles - Enqueue assets untuk frontend
- Frontend Development - Best practices untuk frontend
🔒 Security Tip: Selalu escape output sebelum menampilkan data ke frontend. Gunakan
esc_html(),esc_attr(),esc_url(), atauwp_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:
- Development: Tulis code di
/src/dengan React components, modern JavaScript, dan CSS - Build Process: Vite compile dan bundle semua files menjadi optimized JavaScript dan CSS di
/dist/ - WordPress Integration: Enqueue compiled files dari
/dist/menggunakanwp_enqueue_script()danwp_enqueue_style() - 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/Folder | Lokasi | Deskripsi | Wajib untuk React+Vite? |
|---|---|---|---|
package.json | Root | NPM dependencies dan scripts (dev, build, watch) | Ya |
package-lock.json | Root | Lock file untuk dependencies | Auto-generated |
vite.config.js | Root | Konfigurasi Vite untuk build process (plugins, output, rollup options) | Ya |
.gitignore | Root | Exclude node_modules, dist, dll dari version control | Sangat recommended |
.eslintrc atau eslint.config.js | Root | ESLint configuration untuk code quality | Recommended |
tsconfig.json | Root | TypeScript 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/Folder | Lokasi | Deskripsi | Wajib untuk React+Vite? |
|---|---|---|---|
/src/ | Root | Source code React components sebelum di-build | Ya |
main.jsx atau index.jsx | /src/ | Entry point untuk React app (ReactDOM.render) | Ya |
App.jsx | /src/ | Main React component | Ya (bisa nama lain) |
ComponentName.jsx | /src/ | React components | Sesuai kebutuhan |
ComponentName.css | /src/ | CSS untuk component specific | Opsional |
/src/components/ | /src/ | Reusable React components | Recommended untuk organisasi |
/src/hooks/ | /src/ | Custom React hooks | Jika ada custom hooks |
/src/services/ | /src/ | API calls dan utilities | Untuk external API integration |
/src/assets/ | /src/ | Images, fonts, icons untuk React app | Jika ada static assets |
/src/styles/ | /src/ | Global styles atau shared CSS | Untuk 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/Folder | Lokasi | Deskripsi | Wajib untuk React+Vite? |
|---|---|---|---|
/dist/ | Root | Output folder dari Vite build (compiled production files) | Auto-generated (jangan edit manual) |
/dist/assets/ | /dist/ | Compiled JS dan CSS files dengan hash | Auto-generated oleh Vite |
main.js | /dist/assets/ | Compiled JavaScript bundle | Di-enqueue ke WordPress |
index.css | /dist/assets/ | Compiled CSS bundle | Di-enqueue ke WordPress |
/node_modules/ | Root | Dependencies dari npm | Auto-generated (add ke .gitignore) |
3.4. Package.json Scripts untuk React + Vite
Tambahkan scripts berikut di package.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:
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 filesemptyOutDir: true: Hapus folder dist sebelum build baruentryFileNames: Nama file untuk entry point (main.js)chunkFileNames: Nama file untuk code splitting chunksassetFileNames: Nama file untuk assets (CSS, images, dll)
3.6. Contoh Entry Point React
// 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:
// 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:
- Enqueue Scripts and Styles - Cara yang benar untuk enqueue assets
- wp_localize_script() - Pass data dari PHP ke JavaScript
🔒 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:
/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:
-
Buat folder plugin:
hljs bashmkdir contact-form-plugin cd contact-form-plugin -
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__)); -
Buat file
index.phpuntuk security:hljs php<?php // Silence is golden. -
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.phpada 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:
-
Buat folder dan file:
hljs bashmkdir includes touch includes/index.php touch includes/class-contact-form-plugin.php touch includes/class-contact-form-plugin-activator.php -
Buat
includes/index.php:hljs php<?php // Silence is golden. -
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', )); } } } -
Update
contact-form-plugin.phpuntuk 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(); -
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 denganindex.php
Troubleshooting:
- Jika activation error, cek class name dan file paths
- Pastikan semua file memiliki
ABSPATHcheck
Step 3: Add Admin Interface
Tujuan: Membuat admin menu dan settings page untuk plugin.
Langkah-langkah:
-
Buat folder admin:
hljs bashmkdir 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 -
Buat
admin/index.php:hljs php<?php // Silence is golden. -
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'; } } -
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> -
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:
-
Buat folder public:
hljs bashmkdir public mkdir public/partials touch public/index.php touch public/class-contact-form-plugin-public.php touch public/partials/contact-form-display.php -
Buat
public/index.php:hljs php<?php // Silence is golden. -
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.')); } } } -
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> -
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
initdigunakan - Jika AJAX tidak bekerja, cek
wp_localize_scriptdan 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:
-
Pastikan semua file memiliki
ABSPATHcheck:- ✅ Sudah ada di semua file yang kita buat
-
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'); } -
Pastikan semua output di-escape:
hljs php// Di admin display echo esc_attr($settings['email_to']); echo esc_html($atts['title']); -
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 )); -
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
ABSPATHcheck - 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:
-
Buat folder languages:
hljs bashmkdir languages touch languages/index.php -
Buat
languages/index.php:hljs php<?php // Silence is golden. -
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> -
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'); -
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
-
.potfile 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
-
Jalankan watch mode:
hljs bashnpm run watchIni akan auto-rebuild saat file di folder
/src/berubah. -
Aktifkan plugin di WordPress dan refresh halaman untuk melihat perubahan.
-
Development dengan Hot Reload (opsional): Jika ingin menggunakan Vite dev server untuk testing standalone:
hljs bashnpm run dev
5.2. Production Build
Sebelum release, build production files:
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():
// 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 phpwp_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:
# 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.
-
Gunakan
ABSPATHcheck:hljs phpif (!defined('ABSPATH')) { exit; }Mengapa penting: Mencegah akses langsung ke file PHP melalui URL. Pelajari lebih lanjut.
-
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.
-
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.
-
Capability checks:
hljs phpif (!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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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_cachedanupdate_post_term_cachedengan bijak untuk mengurangi queries. -
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.
-
Defer JavaScript loading:
hljs phpwp_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.
-
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.
-
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
-
Class names: PascalCase dengan prefix:
My_Awesome_Plugin- Mengapa: Mencegah class name collision
- Contoh:
WC_Product,CF7_Contact_Form
-
Function names: snake_case dengan prefix:
my_awesome_plugin_function()- Mengapa: Mencegah function name collision
- Contoh:
wc_get_product(),cf7_save_form()
-
Constants: UPPERCASE dengan prefix:
MY_AWESOME_PLUGIN_VERSION- Mengapa: Mudah diidentifikasi sebagai constant
- Contoh:
WC_VERSION,WP_DEBUG
-
Variables: snake_case dengan prefix:
$my_plugin_option- Mengapa: Konsisten dengan WordPress coding standards
Referensi:
- WordPress Coding Standards - Standar penulisan kode WordPress
- Naming Conventions - Detail tentang naming conventions
6.4. Organization Tips
Organisasi kode yang baik membuat plugin lebih mudah dirawat dan dikembangkan. Berikut adalah tips untuk mengorganisir kode plugin Anda.
-
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
- Admin logic →
-
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_onceuntuk setiap class -
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
-
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:
- Best Practices - Organisasi dan struktur plugin
6.5. Testing & Debugging
Testing dan debugging adalah bagian penting dari development process. Berikut adalah praktik yang direkomendasikan.
-
Enable WP_DEBUG: Aktifkan di
wp-config.phpuntuk developmenthljs phpdefine('WP_DEBUG', true); define('WP_DEBUG_LOG', true); define('WP_DEBUG_DISPLAY', false);Manfaat: Menampilkan errors dan warnings untuk debugging
-
Use WordPress coding standards: Install PHPCS dengan WordPress standards
hljs bashcomposer require --dev wp-coding-standards/wpcs phpcs --standard=WordPress plugin-name.phpManfaat: Memastikan kode mengikuti WordPress standards
-
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
-
Browser compatibility: Test di berbagai browser
- Chrome, Firefox, Safari, Edge
- Mobile browsers (iOS Safari, Chrome Mobile)
- Manfaat: Memastikan UI bekerja di semua browser
-
Unit testing: Gunakan PHPUnit untuk automated testing
hljs phpclass 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:
- Debugging in WordPress - Debugging best practices
- WordPress Coding Standards - Coding standards
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:
// 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:
php -l your-plugin-file.php
Validasi:
- Pastikan
Plugin Nameada 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:
// 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_DEBUGdiwp-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:
# 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:
// Pastikan paths benar
export default defineConfig({
build: {
outDir: 'dist', // Pastikan folder ini bisa di-write
// ...
}
});
Troubleshooting:
- Pastikan
node_modulesdandistdi.gitignore - Cek file permissions untuk folder
dist - Test dengan
npm run devterlebih 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:
# 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:
// 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
.mofile tidak di-generate dari.pofile
Solusi:
// 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:
# 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
.mofile 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_scripttidak dipanggil- URL AJAX salah
Solusi:
// 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:
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
ABSPATHchecks - Input tidak di-sanitize
- Output tidak di-escape
- Missing nonce verification
- Missing capability checks
Solusi:
// 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
ABSPATHcheck - 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:
// 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.logatau/var/log/nginx/error.log - PHP: Check
php.iniuntukerror_loglocation
Use WordPress Debug Functions:
// 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:
- Minimal requirement: Hanya butuh 1 file PHP dengan plugin header
- Best practices: Struktur lengkap dengan
/includes/,/admin/,/public/untuk plugin kompleks - Pelajari lebih lanjut - Modern stack: React + Vite memungkinkan development modern sambil tetap terintegrasi dengan WordPress
- Security first: Selalu gunakan
index.phpdi setiap folder dan sanitize input - Security Best Practices - Documentation: Dokumentasi yang baik memudahkan maintenance dan kolaborasi - Gunakan readme.txt format untuk WordPress.org
- Performance: Optimize assets loading dan gunakan caching untuk meningkatkan performa
- Internationalization: Siapkan plugin untuk multi-bahasa sejak awal dengan i18n
Next Steps:
- Pelajari lebih lanjut di WordPress Plugin Handbook
- Explore WordPress Coding Standards
- Practice dengan membuat plugin sederhana terlebih dahulu
- Gunakan WordPress Plugin Boilerplate sebagai starting point
- Baca Plugin Development Guide untuk detail lebih lanjut
Referensi Lengkap WordPress Documentation
Untuk mempelajari lebih dalam tentang setiap aspek pengembangan plugin WordPress, berikut adalah referensi lengkap:
Dasar-dasar Plugin
- Plugin Basics - Dasar-dasar pengembangan plugin
- Header Requirements - Plugin header yang benar
- Best Practices - Best practices pengembangan plugin
Keamanan
- Plugin Security - Panduan keamanan lengkap
- Data Validation - Validasi dan sanitasi data
- Securing Output - Escape output dengan benar
- Nonces - Menggunakan nonces untuk keamanan
- Checking User Capabilities - Capability checks
Internationalization
- Internationalization - Panduan i18n lengkap
- How to Internationalize Your Plugin - Step-by-step i18n
- Loading Text Domain - Load translation files
Admin Development
- Administration Menus - Membuat menu admin
- Including Assets - Enqueue scripts dan styles
- Settings API - Membuat settings page
Plugin Lifecycle
- Activation/Deactivation Hooks - Mengelola plugin lifecycle
- Uninstall Methods - Cleanup saat uninstall
WordPress.org Repository
- readme.txt Format - Format readme.txt untuk WordPress.org
- Submitting Your Plugin - Submit plugin ke repository
Coding Standards
- WordPress Coding Standards - Standar penulisan kode
- PHP Coding Standards - PHP coding standards
- JavaScript Coding Standards - JavaScript 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! 🚀