원클릭으로
add-ajax-handler
Add a WordPress AJAX handler to a plugin, with nonce verification, input sanitisation, and JSON response.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Add a WordPress AJAX handler to a plugin, with nonce verification, input sanitisation, and JSON response.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Register a block pattern in a theme or plugin, using the WordPress 6.0+ file-based pattern system.
Add a new Gutenberg block to an existing plugin or theme.
Register a WP-CLI command in a plugin using a command class, following Dekode conventions.
Add a WordPress action or filter hook to a plugin or theme.
Scaffold a new must-use plugin in packages/mu-plugins/ - either a simple utility or a service-class integration.
Register a custom post type (and optionally a taxonomy) in a theme or plugin, following Dekode conventions.
| name | add-ajax-handler |
| description | Add a WordPress AJAX handler to a plugin, with nonce verification, input sanitisation, and JSON response. |
Register a WordPress AJAX handler in an existing plugin. Covers nonce generation, hook registration, input sanitisation, and JSON response. Also notes when to use the REST API instead.
Consider the REST API first. If the request returns structured data, accepts typed arguments, or needs to be callable from JS outside the admin,
add-rest-endpointis usually the better choice. Use AJAX handlers for admin-only actions, simple one-off requests, or when integrating with legacy code that already usesadmin-ajax.php.
| Input | Required | Notes |
|---|---|---|
| Plugin | Yes | Which plugin in packages/plugins/ |
| Action name | Yes | Machine name for the AJAX action, e.g. {plugin-slug}_save_item. Must be unique across all plugins. |
| Accessibility | Yes | Logged-in users only, or also non-logged-in (public)? |
| Capability | If logged-in only | Which capability is required, e.g. edit_posts, manage_options |
| Inputs | Yes | What $_POST parameters the handler expects |
| What it does | Yes | Description of the work the handler performs |
If any required inputs are missing, ask for them before writing any code.
Add a constant to the relevant class to avoid hardcoding the action string:
private const AJAX_ACTION = '{plugin-slug}_save_item';
hooks()public function hooks(): void {
add_action( 'wp_ajax_' . self::AJAX_ACTION, [ $this, 'handle_save_item' ] );
// Include the line below only if the action must work for non-logged-in users:
// add_action( 'wp_ajax_nopriv_' . self::AJAX_ACTION, [ $this, 'handle_save_item' ] );
}
Output the nonce via wp_localize_script() when enqueueing the script that will make the request:
public function enqueue_scripts(): void {
$asset = include plugin_dir_path( __FILE__ ) . 'assets/js/admin.asset.php';
wp_enqueue_script(
'{plugin-slug}-admin',
plugin_dir_url( __FILE__ ) . 'assets/js/admin.js',
$asset['dependencies'],
$asset['version'],
true
);
wp_localize_script(
'{plugin-slug}-admin',
'{pluginSlug}Ajax',
[
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( self::AJAX_ACTION ),
]
);
}
In JavaScript, send the nonce as a POST field:
const response = await window.fetch( window['{pluginSlug}Ajax'].ajaxUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams( {
action: '{plugin-slug}_save_item',
nonce: window['{pluginSlug}Ajax'].nonce,
item_id: itemId,
} ),
} );
const data = await response.json();
if ( ! data.success ) {
console.error( data.data.message );
}
/**
* Handles the save-item AJAX request.
*/
public function handle_save_item(): void {
// 1. Verify nonce
check_ajax_referer( self::AJAX_ACTION, 'nonce' );
// 2. Check capability (omit for public endpoints)
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( [ 'message' => __( 'Forbidden.', '{plugin-slug}' ) ], 403 );
}
// 3. Sanitise inputs — never read $_POST directly without sanitising
$item_id = absint( $_POST['item_id'] ?? 0 );
$title = sanitize_text_field( wp_unslash( $_POST['title'] ?? '' ) );
if ( ! $item_id || ! $title ) {
wp_send_json_error( [ 'message' => __( 'Invalid input.', '{plugin-slug}' ) ], 400 );
}
// 4. Perform the work
$result = \wp_update_post( [
'ID' => $item_id,
'post_title' => $title,
] );
if ( \is_wp_error( $result ) ) {
wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 );
}
// 5. Respond — wp_send_json_success() calls wp_die() internally
wp_send_json_success( [ 'item_id' => $item_id ] );
}
composer lint
composer lint-fix # if needed
composer lint # re-run to confirm clean
{plugin-slug}_action_name — prevents collisions with other pluginscheck_ajax_referer() first: call this before any capability check or input processing — die early if the nonce is invalid$_POST value: use the appropriate sanitiser for the data type (absint, sanitize_text_field, sanitize_email, esc_url_raw, etc.). Always wp_unslash() before sanitising text fields.wp_ajax_ (logged-in only) hooks, the user may not have the required capabilitywp_send_json_success() / wp_send_json_error(): always use these — they set the correct Content-Type header and call wp_die() — never echo + die()wp_send_json_error() — 400 for bad input, 403 for forbidden, 500 for server errorswp_ajax_nopriv_ by default: only add the non-authenticated hook when the action is explicitly designed to work without a loginwp_localize_script(): pass ajaxUrl and nonce through script localisation — never hardcode URLs or print nonces as inline JSA AJAX_ACTION constant, a hooks() registration, a wp_localize_script() call in the script enqueue method, and a handler method that verifies the nonce, checks capability, sanitises inputs, performs the work, and responds with wp_send_json_success() or wp_send_json_error(). Lint passes clean.