wp-javascript
WordPress JavaScript & AJAX
Reference for JavaScript integration in WordPress: script registration and enqueuing, passing data from PHP to JS, AJAX request/response patterns, the Heartbeat API, jQuery usage, and loading strategies.
1. Enqueuing Scripts
WordPress manages JavaScript dependencies through a registration and enqueuing system.
Never output <script> tags directly — always use the enqueue API.
Basic Pattern
add_action( 'wp_enqueue_scripts', 'map_enqueue_frontend_scripts' );
function map_enqueue_frontend_scripts(): void {
wp_enqueue_script(
'map-frontend', // Handle (unique identifier).
plugins_url( 'assets/js/frontend.js', __FILE__ ), // Full URL to file.
array( 'jquery' ), // Dependencies.
'1.0.0', // Version (cache busting).
array( 'in_footer' => true ) // Load in footer.
);
}
Enqueue Hooks
| Hook | Where Scripts Load | Callback Parameter |
|---|---|---|
wp_enqueue_scripts |
Frontend pages | None |
admin_enqueue_scripts |
Admin pages | $hook_suffix (page filename) |
login_enqueue_scripts |
Login page | None |
Conditional Loading (Admin)
Load scripts only on your plugin's admin page — not on every admin screen:
add_action( 'admin_enqueue_scripts', 'map_enqueue_admin_scripts' );
function map_enqueue_admin_scripts( string $hook_suffix ): void {
// $hook_suffix examples: 'toplevel_page_my-plugin', 'settings_page_my-plugin-settings'.
if ( 'toplevel_page_my-plugin' !== $hook_suffix ) {
return;
}
wp_enqueue_script(
'map-admin',
plugins_url( 'assets/js/admin.js', __FILE__ ),
array( 'jquery', 'wp-util' ),
MAP_VERSION,
array( 'in_footer' => true )
);
}
Conditional Loading (Frontend)
add_action( 'wp_enqueue_scripts', 'map_enqueue_conditionally' );
function map_enqueue_conditionally(): void {
// Only load on single posts.
if ( ! is_single() ) {
return;
}
wp_enqueue_script( 'map-single', /* ... */ );
}
Register vs Enqueue
// Register (makes handle available, doesn't load yet).
wp_register_script( 'map-charts', plugins_url( 'assets/js/charts.js', __FILE__ ), array(), '2.0.0', true );
// Enqueue later when needed (e.g., inside a shortcode callback).
function map_chart_shortcode( $atts ): string {
wp_enqueue_script( 'map-charts' ); // Now it loads.
return '<div id="map-chart"></div>';
}
Registration is useful when a script should only load conditionally (inside a shortcode, meta box, or specific template).
2. Passing Data from PHP to JavaScript
wp_localize_script()
Passes PHP values to JavaScript as a global object. Must be called after the
script is enqueued/registered and before wp_head()/wp_footer() fires.
wp_enqueue_script( 'map-ajax', plugins_url( 'assets/js/ajax.js', __FILE__ ), array( 'jquery' ), '1.0.0', true );
wp_localize_script( 'map-ajax', 'mapAjax', array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'map_ajax_nonce' ),
'homeUrl' => home_url( '/' ),
'i18n' => array(
'loading' => __( 'Loading...', 'my-plugin' ),
'error' => __( 'An error occurred.', 'my-plugin' ),
),
) );
In JavaScript, the data is available as a global:
console.log( mapAjax.ajaxUrl ); // "https://example.com/wp-admin/admin-ajax.php"
console.log( mapAjax.nonce ); // "a1b2c3d4e5"
console.log( mapAjax.i18n.loading ); // "Loading..."
Limitations:
- All values are cast to strings (numbers become
"5", booleans become""or"1"). - For complex data, use
wp_add_inline_script()withwp_json_encode()instead.
wp_add_inline_script()
Injects a <script> block tied to a registered handle. Supports 'before' or
'after' positioning (default: 'after').
wp_enqueue_script( 'map-app', plugins_url( 'assets/js/app.js', __FILE__ ), array(), '1.0.0', true );
wp_add_inline_script( 'map-app', sprintf(
'var mapConfig = %s;',
wp_json_encode( array(
'apiBase' => rest_url( 'map/v1/' ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'maxItems' => 50, // Stays as integer.
'debug' => WP_DEBUG, // Stays as boolean.
) )
), 'before' );
Use 'before' when your main script needs the config variable at load time.
Script Translation (i18n)
For Block Editor scripts using wp.i18n.__():
wp_set_script_translations( 'map-editor', 'my-plugin', plugin_dir_path( __FILE__ ) . 'languages' );
3. Script Dependencies & Built-in Libraries
Common WordPress Script Handles
| Handle | Library | Notes |
|---|---|---|
jquery |
jQuery (latest bundled) | Runs in noConflict mode |
jquery-core |
jQuery core without migrate | Lighter, no deprecated API shims |
wp-api-fetch |
@wordpress/api-fetch |
REST API requests with nonce |
wp-element |
@wordpress/element (React wrapper) |
Block Editor components |
wp-data |
@wordpress/data |
Block Editor state management |
wp-hooks |
@wordpress/hooks |
JS action/filter system |
wp-i18n |
@wordpress/i18n |
__(), _n() for JS |
wp-util |
wp.ajax, wp.template |
AJAX helpers, Underscore templates |
underscore |
Underscore.js | Utility library |
backbone |
Backbone.js | MV* framework |
wp-mediaelement |
MediaElement.js | Audio/video player |
thickbox |
ThickBox | Modal dialogs |
jQuery in WordPress
WordPress loads jQuery in noConflict mode — the $ shortcut is not available globally.
// WRONG — $ is undefined in noConflict mode.
$( '.my-element' ).hide();
// Option 1: Use the full name.
jQuery( '.my-element' ).hide();
// Option 2: Wrap in an IIFE (recommended).
( function( $ ) {
$( '.my-element' ).hide();
$( document ).ready( function() {
// DOM ready code.
} );
} )( jQuery );
// Option 3: jQuery ready shorthand.
jQuery( function( $ ) {
$( '.my-element' ).hide();
} );
Loading Strategies (WordPress 6.3+)
| Strategy | Behavior |
|---|---|
defer |
Script executes after DOM is constructed, in order |
async |
Script executes immediately when downloaded, no order |
| (default) | Blocking — halts parsing until script runs |
wp_enqueue_script( 'map-analytics', plugins_url( 'assets/js/analytics.js', __FILE__ ), array(), '1.0.0', array(
'in_footer' => true,
'strategy' => 'defer', // Non-blocking, ordered execution.
) );
WordPress automatically adjusts strategies to prevent dependency conflicts. If a
dependency uses blocking, the dependent script cannot use async.
4. AJAX
WordPress routes all AJAX requests through wp-admin/admin-ajax.php. The standard
pattern: register wp_ajax_{action} (and optionally wp_ajax_nopriv_{action}) hooks,
verify the nonce with check_ajax_referer(), sanitize input, process data, and return
a JSON response via wp_send_json_success() or wp_send_json_error(). On the client
side, use jQuery.post() with the localized AJAX URL and action name.
Response Functions
| Function | Use Case |
|---|---|
wp_send_json_success( $data ) |
{ success: true, data: $data } |
wp_send_json_error( $data ) |
{ success: false, data: $data } |
wp_send_json( $data ) |
Raw JSON (no success wrapper) |
All three call wp_die() internally — do not echo or exit after them.
Nonce Verification
| Function | Use Case |
|---|---|
check_ajax_referer( $action, $key ) |
Dies on failure (use for AJAX handlers) |
wp_verify_nonce( $_POST['nonce'], $action ) |
Returns false on failure (manual check) |
Admin-Only vs Public AJAX
| Hook Pattern | Who Can Call |
|---|---|
wp_ajax_{action} |
Logged-in users only |
wp_ajax_nopriv_{action} |
Non-logged-in users only |
| Both hooks registered | All users |
Register nopriv only if the endpoint must work for anonymous visitors.
Always verify a nonce even on logged-in-only endpoints.
5. Fetch API & wp.apiFetch (Modern Alternative)
For modern JavaScript (no jQuery dependency), use wp.apiFetch or the native
Fetch API with the WordPress REST API instead of admin-ajax.php.
wp.apiFetch
// Automatically includes X-WP-Nonce header.
wp.apiFetch( { path: '/wp/v2/posts?per_page=5' } )
.then( function( posts ) {
console.log( posts );
} );
// POST request.
wp.apiFetch( {
path: '/map/v1/settings',
method: 'POST',
data: { key: 'value' },
} ).then( function( response ) {
console.log( response );
} );
Requires wp-api-fetch as a dependency:
wp_enqueue_script( 'map-modern', plugins_url( 'assets/js/modern.js', __FILE__ ), array( 'wp-api-fetch' ), '1.0.0', true );
Native Fetch with REST API
fetch( mapConfig.apiBase + 'settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': mapConfig.nonce,
},
body: JSON.stringify( { key: 'value' } ),
} )
.then( r => r.json() )
.then( data => console.log( data ) );
When to Use What
| Approach | Best For |
|---|---|
| admin-ajax.php | Legacy code, simple form submissions, no REST API |
wp.apiFetch |
Block Editor scripts, admin React UI |
| Native Fetch + REST | Headless/decoupled, modern JS, custom REST routes |
6. Heartbeat API
The Heartbeat API provides near-real-time server polling. WordPress uses it for post locking, autosave, and login session management.
How It Works
- Browser fires a "tick" every 15-120 seconds (default: 60s on most pages, 15s on post editor).
- Client-side code attaches data via
heartbeat-sendevent. - Server processes data through
heartbeat_receivedfilter. - Client receives response via
heartbeat-tickevent.
Hooks
| Hook | Type | Side | Purpose |
|---|---|---|---|
heartbeat-send |
Event | JS | Attach data before sending |
heartbeat-tick |
Event | JS | Process server response |
heartbeat-error |
Event | JS | Handle connection errors |
heartbeat_received |
Filter | PHP | Process data for logged-in users |
heartbeat_nopriv_received |
Filter | PHP | Process data for logged-out users |
heartbeat_settings |
Filter | PHP | Modify heartbeat interval |
Use Cases
- Post locking — warn when another user is editing the same post.
- Autosave — periodically save draft content.
- Session management — extend logged-in session while user is active.
- Real-time notifications — poll for new data without WebSockets.
- Dashboard widgets — auto-refresh stats or activity feeds.
7. wp.template (Underscore Templates)
WordPress bundles wp-util which provides wp.template() for client-side
HTML rendering using Underscore.js template syntax. Define templates in PHP
using <script type="text/html" id="tmpl-{name}"> blocks (typically in
admin_footer), then render in JavaScript with wp.template( '{name}' ).
Requires wp-util as a script dependency.
Template Syntax
| Syntax | Purpose | Example |
|---|---|---|
{{ data.val }} |
Escaped output (XSS-safe) | {{ data.title }} |
{{{ data.val }}} |
Raw HTML output (trust the source) | {{{ data.html }}} |
<# code #> |
JavaScript logic (if/for) | <# if (data.x) { #> |
8. Common Mistakes
| Mistake | Fix |
|---|---|
Outputting <script> tags directly |
Always use wp_enqueue_script() |
| Loading scripts on every admin page | Check $hook_suffix in admin_enqueue_scripts callback |
Using $ without jQuery wrapper |
Wrap in (function($) { ... })(jQuery); — noConflict mode |
Hardcoding admin-ajax.php URL in JS |
Pass via wp_localize_script() using admin_url('admin-ajax.php') |
| Forgetting nonce in AJAX requests | Always create with wp_create_nonce(), verify with check_ajax_referer() |
Echoing after wp_send_json_*() |
These call wp_die() internally — no code runs after them |
Not registering nopriv hook for public AJAX |
Frontend AJAX for visitors needs wp_ajax_nopriv_{action} |
| Loading jQuery from CDN instead of bundled | Use the WordPress handle jquery — prevents version conflicts |
Using wp_localize_script() for non-string data |
Use wp_add_inline_script() with wp_json_encode() instead |
| Heartbeat running at 15s on non-editor pages | Use heartbeat_settings filter to slow it down or disable |
| Not declaring script dependencies | List all deps so WordPress loads them in correct order |
| Mixing admin-ajax and REST API unnecessarily | Pick one approach per feature — REST API for new code |
More from peixotorms/odinlayer-skills
elementor-development
Use when building Elementor addons, creating custom widgets, or managing Elementor components. Covers Widget_Base class (get_name, get_title, get_icon, register_controls, render, content_template), widget registration via elementor/widgets/register hook, addon structure and plugin header, wp_enqueue_script for widget assets, get_script_depends, get_style_depends, inline editing toolbars, custom widget categories, manager registration (register/unregister), selector tokens ({{WRAPPER}}), deprecation handling, and Elementor CLI commands.
65elementor-hooks
Use when hooking into Elementor lifecycle events, injecting controls, filtering widget output, or using the JS APIs. Covers elementor/init, elementor/element/before_section_end, elementor/element/after_section_end, elementor/widget/render_content filter, elementor/frontend/after_enqueue_styles, frontend JS hooks (elementorFrontend.hooks, frontend/element_ready), editor JS hooks (elementor.hooks), $e.commands API ($e.run, $e.commands.register), $e.routes, $e.hooks (registerUIBefore, registerUIAfter), control injection patterns, CSS file hooks, forms hooks (Pro), and query filters.
26elementor-themes
Use when building Elementor-compatible themes, registering theme locations, creating dynamic tags, or extending the Finder. Covers register_location, theme_builder locations, elementor_theme_do_location, Theme_Document and theme conditions, Tag_Base for dynamic tags (register_tag, get_value, render), Finder extension (Category_Base, register via elementor/finder/register), Context_Menu customization (elements/context-menu/groups filter), Hello Elementor theme (elementor-hello-theme, hello_elementor_* filters), and hosting page cache integration hooks.
25elementor-controls
Use when adding controls to Elementor widgets, creating custom controls, or referencing control type parameters. Covers add_control with types (TEXT, SELECT, SLIDER, COLOR, MEDIA, REPEATER, CHOOSE, NUMBER, SWITCHER, URL, ICONS), TYPOGRAPHY and BACKGROUND group controls, BORDER, BOX_SHADOW group controls, add_responsive_control, add_group_control, CSS selectors ({{WRAPPER}}, {{VALUE}}), condition and conditions for conditional display, dynamic content tags, POPOVER_TOGGLE, and global styles integration.
16elementor-forms
Use when creating custom Elementor form actions, custom form field types, form validation, or processing form submissions. Covers Elementor Pro forms (ElementorPro\Modules\Forms), Action_Base (get_name, get_label, run, register_settings_section, on_export), after_submit processing, Field_Base (field_type, render field HTML, validation callback, update_controls), content_template for editor preview, form action registration, export_type handling, update_record patterns, elementor_pro/forms/validation hook, email filters, and webhook response handling.
12flyonui
Use when building with FlyonUI — Tailwind CSS component library with CSS classes and optional JS plugins. Covers CSS component classes, JS plugin system (accordion, carousel, collapse, combobox, datatable, dropdown, select, tabs, tooltip, etc.), theming, installation, class reference, and plugin initialization via MCP tools.
9