Hook là cơ chế WordPress cho phép code can thiệp vào lifecycle core mà không sửa core. Hai loại hook: action (làm gì đó tại thời điểm X) và filter (modify giá trị trước khi WordPress dùng).
Bài này giải thích khác biệt fundamental, 6 hook lifecycle quan trọng nhất, priority và accepted_args, pattern remove_action, custom hook trong plugin riêng, 4 pitfall hay gặp khi mới chuyển từ functions.php sang plugin scale.
Action so với Filter — khác biệt fundamental trong WordPress core
Action là hook chạy code tại 1 thời điểm trong lifecycle WordPress, không trả về giá trị. Filter là hook modify 1 giá trị trước khi WordPress dùng, bắt buộc return giá trị sau modify hoặc nguyên gốc nếu không can thiệp.
Action dùng cho side effect như gửi email, log database, push CRM. Filter dùng cho transform data như escape HTML, format giá, prepend emoji vào title.
So sánh nhanh action và filter qua use case
Phân biệt action filter là bước đầu tiên hiểu hook system. Bốn use case sau cover khoảng 80% trường hợp dev hay gặp.
- Action — khi X xảy ra làm Y: ví dụ user submit form thì gửi email và log database — dùng
do_action('w22_form_submit', $data)để trigger. - Filter — trước khi dùng giá trị Z modify nó: ví dụ trước khi hiển thị title thì uppercase chữ đầu — dùng
apply_filters('the_title', $title)để cho phép override. - Side effect không cần return: log activity, dispatch webhook, invalidate cache — đều là action vì WordPress không cần kết quả ngược lại.
- Transform data có return: sanitize input, prepend hoặc append string, replace placeholder — đều là filter vì pipeline cần giá trị sau modify.
6 hook lifecycle quan trọng nhất plugin nào cũng phải tương tác
WordPress core expose hơn 1.500 hook. Sáu hook sau là điểm tiếp xúc đầu tiên — plugin nào cũng phải hook vào ít nhất 2-3 trong nhóm này.
plugins_loaded — sau khi mọi plugin load xong
Chạy sau khi tất cả plugin được load nhưng trước theme. Đây là hook chuẩn để register feature phụ thuộc plugin khác — ví dụ plugin add-on cho WooCommerce cần đợi WC load xong mới hook tiếp.
init — register CPT, taxonomy, shortcode
Chạy sau khi WordPress loaded core nhưng trước header và admin render. Đây là hook chính để register_post_type, register_taxonomy, add_shortcode và register_rest_route.
Tham khảo signature đầy đủ tại developer.wordpress.org/reference/hooks/init — note rõ context và những function không nên gọi trong init.
wp_loaded — sau khi WordPress fully loaded
Chạy sau khi WordPress load core, plugin, theme và parse query xong. Phù hợp cho action không phụ thuộc context admin hay frontend cụ thể — ví dụ trigger cron check, validate license key.
wp_footer — inject script trước thẻ body đóng
Chạy ngay trước </body> ở frontend. Inject script analytics, modal HTML, debug bar — đây là hook tiêu chuẩn cho mọi thứ cần xuất hiện cuối page.
admin_init và admin_menu — feature admin
admin_init chạy sớm trong admin context, dùng để register_setting hoặc check capability. admin_menu chạy khi WordPress build menu sidebar — dùng add_menu_page hoặc add_options_page ở đây.
shutdown — task cleanup không block response
Hook cuối cùng, chạy sau khi response đã gửi tới browser. Dùng cho task không cần block response — log analytics, invalidate cache cross-server, flush buffered metric.
Lưu ý: shutdown vẫn chia sẻ DB connection và memory với request gốc — task chạy ở đây vẫn tốn resource server dù user không thấy. Task nặng nên đẩy qua wp_cron hoặc queue ngoài thay vì shutdown.
3 hook đặc biệt liên quan tới REST và AJAX
Plugin hiện đại 2026 thường có REST endpoint và AJAX handler. Ba hook sau là điểm vào tiêu chuẩn cho 2 loại request này.
- rest_api_init: đăng ký REST route qua
register_rest_route— hook này chỉ chạy khi request là REST, tiết kiệm CPU so với init. - wp_ajax_{action}: handler cho AJAX request từ user logged-in — replace bằng action name của plugin.
- wp_ajax_nopriv_{action}: handler cho AJAX từ user chưa login — bắt buộc nếu form public không yêu cầu đăng nhập.
Priority và accepted_args — quy tắc khi có multiple callback
Mỗi hook có thể bind nhiều callback. Priority và thứ tự register quyết định thứ tự thực thi — sai priority dẫn tới bug khó debug vì callback chạy nhầm thứ tự.
Signature đầy đủ của add_action và add_filter
add_action($hook_name, $callback, $priority = 10, $accepted_args = 1);
add_filter($hook_name, $callback, $priority = 10, $accepted_args = 1);
4 mức priority và khi nào dùng
WordPress không có rule cứng cho priority nhưng cộng đồng có convention rõ. Tuân theo convention giúp plugin không xung đột với plugin khác cùng hook.
- Priority 1-9: reserved cho core và plugin cần chạy rất sớm — plugin custom nên tránh dùng dải này.
- Priority 10 (default): dùng cho 90% case — đủ cho hầu hết feature không có thứ tự đặc biệt.
- Priority 20-50: chạy sau plugin SEO và cache đã thêm output — dùng cho filter cuối cùng cần see output của plugin khác.
- Priority 99 và PHP_INT_MAX: chạy cuối cùng để override mọi thứ — dùng cho remove_action plugin core hoặc final override.
accepted_args — khai báo số argument callback nhận
Mặc định accepted_args = 1 nghĩa là callback chỉ nhận 1 argument. Hook truyền nhiều argument cần khai báo đúng số lượng — thiếu thì callback nhận thiếu, bug âm thầm.
// Hook 'save_post' truyền 3 args: $post_id, $post, $update
add_action('save_post', function($post_id, $post, $update) {
if ($update && $post->post_type === 'service') {
// logic update existing service
}
}, 10, 3); // ← phải khai báo 3
Khai báo thiếu argument là pattern bug số 1 với hook nhiều tham số. Reference docs WordPress luôn ghi rõ số arg — copy-paste signature từ docs an toàn nhất.
remove_action và remove_filter — pattern unhook đúng cách
Khi plugin hoặc core add hook không mong muốn, có thể remove. Yêu cầu biết chính xác tên function callback và priority gốc — anonymous function không remove được.
Remove hook của WordPress core
// Bỏ admin bar bump style trên frontend
remove_action('wp_head', '_admin_bar_bump_cb');
// Bỏ emoji script (giảm 13KB và 2 HTTP request)
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('wp_print_styles', 'print_emoji_styles');
Remove hook của plugin khác — vấn đề timing
Plugin thường add hook trong plugins_loaded hoặc init. Remove phải gọi sau khi plugin add — dùng after_setup_theme hoặc priority cao hơn.
add_action('init', function () {
// WooCommerce add 'wp_footer' callback ở init priority 10
remove_action('wp_footer', 'wc_print_notices', 10);
}, 20); // priority 20 chạy sau WC priority 10
Anonymous function KHÔNG remove được
Pattern add_action('hook', function() {}) không có reference name nên không thể truyền vào remove_action. Pattern unhook được: declare named function rồi reference qua tên.
function w22_my_callback() { /* logic */ }
add_action('init', 'w22_my_callback');
// Sau này có thể remove:
remove_action('init', 'w22_my_callback');
Custom hook trong plugin — do_action và apply_filters
Plugin chuyên nghiệp expose custom hook để dev khác extend mà không cần fork code. Đây là pattern extension point — khác biệt giữa plugin “đóng” và plugin “mở”.
Custom action expose extension point
// Trong plugin Web22 Forms
public function process_submission($data) {
// Validate, save DB
do_action('w22_forms_after_submit', $data, $this->submission_id);
// Plugin hoặc theme khác có thể hook vào để: gửi Slack, push CRM
}
Custom filter cho phép modify behavior
// Cho phép theme hoặc plugin khác modify danh sách field bắt buộc
public function get_required_fields() {
$fields = ['email', 'name', 'message'];
return apply_filters('w22_forms_required_fields', $fields);
}
// Theme hoặc plugin khác extend:
add_filter('w22_forms_required_fields', function($fields) {
$fields[] = 'phone';
return $fields;
});
3 nguyên tắc khi đặt tên custom hook
Custom hook là API public của plugin — đặt tên xấu khó đổi sau vì sẽ break code dùng. Ba nguyên tắc giúp đặt tên rõ và scale lâu dài.
- Prefix bằng slug plugin: mọi hook custom bắt đầu bằng
w22_forms_hoặc tương đương — tránh xung đột với hook plugin khác trùng tên ngắn. - Verb rõ ràng: dùng
before,after,can,shouldđể mô tả thời điểm — vdw22_forms_before_savedễ hiểu hơnw22_forms_save. - Document signature trong PHPDoc: ghi rõ argument, type, return value ngay trên dòng
do_action— dev khác đọc code biết hook signature mà không phải reverse engineer.
4 pitfall phổ biến khi mới làm việc với hook
Bốn lỗi sau chiếm khoảng 70% bug liên quan hook trong code plugin mới. Biết trước để tránh là tiết kiệm vài ngày debug.
Pitfall 1 — anonymous function khoá khả năng remove
Đã giải thích ở mục remove_action. Solution: declare named function nếu có khả năng cần remove sau, anonymous chỉ dùng cho hook one-off không bao giờ phải gỡ.
Pitfall 2 — race condition khi 2 plugin cùng priority
Plugin A hook vào init priority 10, plugin B hook cùng priority 10 muốn modify output A. Không deterministic plugin nào chạy trước — tuỳ thứ tự WordPress load plugin.
Solution: B dùng priority 20 để chắc chắn chạy sau A. Khi viết plugin custom, tránh giả định plugin khác chạy trước plugin của mình.
Pitfall 3 — filter quên return
Bug phổ biến nhất với filter — quên dòng return ở cuối callback. Filter chain return null làm vỡ pipeline, giá trị các filter sau bị mất.
// SAI — quên return
add_filter('the_title', function($title) {
if (is_singular('product')) {
$title = '🛒 ' . $title;
}
// Forgot return!
});
// ĐÚNG
add_filter('the_title', function($title) {
if (is_singular('product')) {
$title = '🛒 ' . $title;
}
return $title; // ← BẮT BUỘC
});
Pitfall 4 — hook vào hook deprecated
WordPress đôi khi rename hook (vd wp_head đổi cách trigger ở 6.0). Hook deprecated bị remove ở major version sau, plugin sẽ break.
Bật WP_DEBUG = true để catch warning doing_it_wrong hoặc deprecated_hook — xuất hiện trong debug.log mỗi khi hook deprecated được trigger.
Best practice tổng kết khi viết hook cho plugin production
Sau khi hiểu khái niệm và 4 pitfall, đây là 6 best practice rút từ kinh nghiệm maintain plugin production lâu dài. Áp dụng giảm bug và xung đột với plugin khác xuống mức tối thiểu.
6 nguyên tắc khi viết hook
- Prefix hook custom bằng slug plugin: mọi hook do plugin expose bắt đầu bằng
w22_forms_hoặc tương đương — tránh xung đột tên với plugin khác. - Document signature qua PHPDoc: mỗi
do_actionhoặcapply_filterscustom có comment PHPDoc ghi rõ argument, type, return — dev khác đọc code biết cách extend. - Tránh logic nặng trong filter chạy mỗi paragraph: filter như
the_contentchạy nhiều lần trong 1 request — query DB hoặc file IO ở đây kéo TTFB tăng vài trăm ms. - Named function thay anonymous khi có khả năng cần remove: anonymous lock khả năng unhook — chọn named function cho mọi hook có rủi ro plugin khác cần gỡ.
- Test hook chain trên fresh install: hook hoạt động trên dev máy chưa chắc hoạt động trên site khách có 30 plugin chạy chung — test fresh + bật 5 plugin phổ biến bắt xung đột.
- Log hook trigger trong debug mode: bật
SAVEQUERIESvà custom logger để track callback nào tốn thời gian nhất — bottleneck thường ở callback chứ không phải số hook.
Câu hỏi thường gặp
has_action và has_filter khác nhau như thế nào?
Cùng signature, kiểm tra hook đã có callback hay chưa. has_action dùng cho hook loại action, has_filter cho filter. Trả về false nếu không có callback nào, trả về int priority nếu có.
Use case phổ biến: trước khi remove hook, dùng has_action check tồn tại — tránh log warning khi remove hook chưa được add.
did_action dùng để làm gì?
Đếm số lần action đã được trigger từ khi WordPress load. Hữu ích debug timing — did_action('init') trả 1 nếu init đã chạy, 0 nếu chưa.
Pattern thường dùng: check pre-condition trước khi gọi function phụ thuộc init — ví dụ if (did_action('init')) { register_my_thing(); } tránh register lặp.
Hook nào chạy trước, init hay plugins_loaded?
plugins_loaded chạy trước. Thứ tự đầy đủ: muplugins_loaded → plugins_loaded → setup_theme → after_setup_theme → init → wp_loaded.
Tham khảo full lifecycle tại developer.wordpress.org/plugins/hooks — đặc biệt nếu plugin có dependency phức tạp với plugin khác.
add_filter có thể return data type bất kỳ không?
Có. Signature filter là callback($value) với $value là bất kỳ data type.
Quy tắc bất thành văn: return cùng type với input để không break filter sau trong chain.
Filter the_content nhận string phải return string. Filter the_posts nhận array phải return array.
Vi phạm sẽ làm plugin sau filter bị bug vì kỳ vọng type khác.
Performance impact khi register 50 hook trở lên?
Negligible. WordPress lưu hook trong array global, lookup O(1).
50 hook tốn khoảng 0,5-1ms overhead tổng — không đáng lo cho mọi site.
Đáng quan tâm chỉ khi callback nặng — ví dụ DB query trong filter the_content chạy mỗi paragraph, hoặc file IO trong hook trigger 100 lần mỗi page. Bottleneck là callback chứ không phải số hook.
Tài nguyên và bước tiếp theo
Hiểu hook là điều kiện cần để build plugin extend được. Các topic chuyên sâu liên quan trong cụm WordPress development giúp hoàn thiện stack từ structure đến deploy.
- Tạo plugin WordPress từ đầu — 7 bước build chuẩn 2026 — apply hook vào plugin structure thật từ activation đến uninstall.
- Plugin boilerplate WordPress 2026 — WPPB vs Underscores vs custom — boilerplate hook chain sẵn theo OOP pattern.
- register_post_type WordPress — 25 tham số + show_in_rest 2026 — register CPT là use case kinh điển của hook init.
- register_taxonomy WordPress — hierarchical vs flat + term meta 2026 — taxonomy hook chain bổ sung cho CPT.
- Dịch vụ thiết kế website WordPress chuyên nghiệp — tham khảo gói thi công plugin custom có document hook đầy đủ.
Cần Web22 build plugin có hook architecture mở để team mở rộng feature sau? Đội WordPress Developer Web22 — block + plugin + REST API — bàn giao kèm document custom hook, sample integration code và bảo hành cấu trúc 12 tháng.


