KIếN THứC WEBSITE › WORDPRESS

Hook, action và filter WordPress — concept fundamental

Hook, action và filter WordPress — concept fundamental 2026

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

hook, action và filter wordpress — Action so với Filter — khác biệt fundamental trong WordPress core
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

6 hook lifecycle quan trọng nhất plugin nào cũng phải tương tác
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_shortcoderegister_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.

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

Priority và accepted_args — quy tắc khi có multiple callback
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 — vd w22_forms_before_save dễ hiểu hơn w22_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_action hoặc apply_filters custom 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_content chạ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 SAVEQUERIES và 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.

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.