Skip to main content
Skip to main content

Developers Doc

AJAX API: The Complete admin-ajax Reference

10 mins read 7 Views 3+

Alongside its REST API, Havenlytics uses WordPress’s classic admin-ajax.php transport for a specific set of interactions: frontend property search and map rendering, the contact-agent form submission, the Agent Workspace authentication flow, and several administrator-only maintenance tools. This reference lists every wp_ajax_* and wp_ajax_nopriv_* action registered in Havenlytics 3.3.1, the handler that runs, the nonce action that guards it, and whether it is available to logged-out visitors.

Note: Every AJAX action verifies a nonce. Some also require a capability. An action being registered for nopriv (logged-out) users does not mean it is unguarded — it still checks the shared frontend nonce. If an action is not listed here, it is not part of the plugin.

How Havenlytics AJAX actions are structured

Each action follows the standard WordPress pattern: the plugin hooks a handler onto wp_ajax_{action} (logged-in) and, where public access is needed, wp_ajax_nopriv_{action} (logged-out). The handler verifies a nonce, checks a capability where required, does its work, and returns wp_send_json_success() or wp_send_json_error(). Havenlytics uses three broad nonce-and-capability tiers:

  • Frontend / contact — a single shared nonce action hvnly_ajax_request, registered for both priv and nopriv. No capability check (these are public-facing).
  • Workspace auth — a distinct per-action nonce (the SessionAuthController::NONCE_* constants), registered for both priv and nopriv, with rate limiting on the sensitive flows.
  • Admin — a dedicated nonce plus a capability such as manage_options or activate_plugins, registered for priv only.

Frontend property-search actions

All share the nonce action hvnly_ajax_request and are available to both logged-in and logged-out visitors.

ActionHandler (class::method)Nonce actionAccess
hvnly_search_propertiesFrontend\AjaxHandler::search_propertieshvnly_ajax_requestpriv + nopriv
hvnly_load_moreFrontend\AjaxHandler::load_more_properties (delegates to search)hvnly_ajax_requestpriv + nopriv
hvnly_get_filtersFrontend\AjaxHandler::get_filter_optionshvnly_ajax_requestpriv + nopriv
hvnly_get_properties_for_mapFrontend\AjaxHandler::hvnly_get_properties_for_maphvnly_ajax_requestpriv + nopriv
hvnly_render_map_containerFrontend\AjaxHandler::hvnly_render_map_containerhvnly_ajax_requestpriv + nopriv
hvnly_sidebar_filter_searchSidebarAjaxHandler::sidebar_filter_searchhvnly_ajax_requestpriv + nopriv
hvnly_elementor_load_more_propertiesElementor\AjaxHandler::load_more_propertieshvnly_ajax_requestpriv + nopriv

Contact-agent submission

ActionHandler (class::method)Nonce actionAccess
hvnly_submit_contact_agentContactAgentAjaxRegistrar::dispatch → AjaxHandler::handle_submithvnly_ajax_requestpriv + nopriv

ContactAgentAjaxRegistrar is the primary registrar (wired from HookManager::register_ajax_hooks). The ContactAgent\AjaxHandler::register_hooks path carries an idempotent guard so the action is not double-registered.

Workspace authentication actions

These drive the Agent Workspace sign-in, registration, and email-verification flow. Each uses its own nonce action (the SessionAuthController::NONCE_* constants) verified through verify_nonce(), and each is available to logged-out users so a visitor can authenticate. Most run through guard_enabled() (Workspace-must-be-enabled) — the exceptions are logout and nonce refresh.

ActionHandler (class::method)Nonce actionAccess
hvnly_ws_loginSessionAuthController::loginhvnly_ws_auth_loginpriv + nopriv
hvnly_ws_registerSessionAuthController::register_userhvnly_ws_auth_registerpriv + nopriv
hvnly_ws_lostpasswordSessionAuthController::lost_passwordhvnly_ws_auth_lostpasswordpriv + nopriv
hvnly_ws_resetpasswordSessionAuthController::reset_passwordhvnly_ws_auth_resetpasswordpriv + nopriv
hvnly_ws_logoutSessionAuthController::logouthvnly_ws_auth_logoutpriv + nopriv
hvnly_ws_auth_noncesSessionAuthController::refresh_noncesnone (token refresh)priv + nopriv
hvnly_ws_verify_emailSessionAuthController::verify_emailhvnly_ws_auth_verify_emailpriv + nopriv (rate limited)
hvnly_ws_resend_verificationSessionAuthController::resend_verificationhvnly_ws_auth_resend_verificationpriv + nopriv (rate limited)
hvnly_ws_reg_set_statusRegistrationAdminActions::ajax_set_statushvnly_ws_reg_status + per-agent hvnly_ws_reg_status_{agent_id}priv; caps APPROVE_LISTINGS + PORTAL_ACCESS or manage_options

Security: The email verify/resend actions are rate limited by VerificationRateLimiter: verify is capped at 20 per IP per 10 minutes and 10 per selector per hour; resend at 10 per IP per hour with a 1-minute cooldown, 5 per account per hour, and 10 per account per day. Responses are uniform and generic so they cannot be used to probe which emails or tokens exist. Tokens must match ^[a-f0-9]{16}\.[a-f0-9]{64}$ before any lookup runs.

Admin maintenance actions

These are administrator-only. Each requires a capability in addition to its nonce and is registered for priv users only.

ActionHandler (class::method)Nonce actionCapability
hvnly_clear_cacheCacheAdmin::ajax_clear_cachehvnly_cache_noncemanage_options
hvnly_get_cache_statsCacheAdmin::ajax_get_cache_statshvnly_cache_noncemanage_options
hvnly_update_cache_settingsCacheAdmin::ajax_update_cache_settingshvnly_cache_noncemanage_options
hvnly_clear_shortcode_cacheCacheAdmin::ajax_clear_shortcode_cachehvnly_cache_noncemanage_options
hvnly_clear_dynamic_cssCacheAdmin::ajax_clear_dynamic_csshvnly_cache_noncemanage_options
hvnly_import_propertiesPropertyImportWizard::ajax_import_propertieshvnly_import_nonce + hvnly_import_csrfmanage_options
hvnly_cancel_importPropertyImportWizard::ajax_cancel_importhvnly_import_nonce + hvnly_import_csrfmanage_options
hvnly_save_google_api_keyPropertyImportWizard::ajax_save_google_api_keyhvnly_import_nonce + hvnly_import_csrfmanage_options
hvnly_save_map_providerPropertyImportWizard::ajax_save_map_providerhvnly_import_nonce + hvnly_import_csrfmanage_options
hvnly_dismiss_theme_noticeThemeRecommendationNotice::ajax_dismisshvnly_dismiss_theme_noticemanage_options
hvnly_deactivate_conflictConflictChecker::ajax_deactivate_pluginhvnly_ajax_nonce + per-plugin hvnly_deactivate_{file}activate_plugins
hvnly_get_icon_libraryHvnly_Advanced_Icon_Managerhvnly_advanced_icon_picker_nonce(admin icon picker)
hvnly_search_iconsHvnly_Advanced_Icon_Managerhvnly_advanced_icon_picker_nonce(admin icon picker)

Warning: The import wizard requires two nonces on every request — hvnly_import_nonce and hvnly_import_csrf — as a defense-in-depth CSRF measure. A request missing either is rejected. If you build custom tooling around the importer, send both fields.

Note: The hvnly_clear_cachehvnly_get_cache_stats, and hvnly_update_cache_settings actions are also registered from HookManager, so those three are wired up twice (a duplicate registration). Behaviour is unchanged — the same guarded handler runs — but be aware of the double registration when tracing hooks or profiling.

The nonce + capability pattern

Havenlytics AJAX security rests on two independent checks that must both pass:

  1. Nonce verification proves the request originated from a page the site itself rendered (CSRF protection). Frontend actions verify the shared hvnly_ajax_request nonce; each admin and Workspace action verifies its own dedicated nonce.
  2. Capability check proves the current user is allowed to perform the action (authorization). Frontend and contact actions are public and skip this; admin actions require manage_options or activate_pluginshvnly_ws_reg_set_status requires the portal capabilities APPROVE_LISTINGS + PORTAL_ACCESS (or manage_options).

The distinct nonce actions in play across the AJAX surface are: hvnly_ajax_requesthvnly_cache_noncehvnly_import_noncehvnly_import_csrfhvnly_dismiss_theme_noticehvnly_ajax_noncehvnly_deactivate_{file}hvnly_advanced_icon_picker_nonce, and the hvnly_ws_auth_* / hvnly_ws_reg_status family.

Calling an action from JavaScript

Post to admin-ajax.php with the action name and the matching nonce. The example below submits the contact-agent form using the shared frontend nonce.

// The nonce is localized onto the page as hvnly_ajax_request.
async function submitContactAgent( propertyId, name, email, message ) {
  const form = new FormData();
  form.append( 'action', 'hvnly_submit_contact_agent' );
  form.append( 'nonce', hvnlyFrontend.nonce );  // wp_create_nonce('hvnly_ajax_request')
  form.append( 'property_id', propertyId );
  form.append( 'name', name );
  form.append( 'email', email );
  form.append( 'message', message );

  const res = await fetch( hvnlyFrontend.ajaxUrl, {   // admin-ajax.php URL
    method: 'POST',
    credentials: 'same-origin',
    body: form
  } );

  const body = await res.json();
  if ( ! body.success ) {
    // e.g. { code: 'hvnly_contact_agent_invalid_nonce', status: 403 }
    console.error( body.data.message );
    return;
  }
  return body.data;
}

A bad nonce returns a structured error, for example:

{
  "success": false,
  "data": {
    "message": "Security check failed. Please refresh the page and try again.",
    "code": "hvnly_contact_agent_invalid_nonce",
    "status": 403
  }
}

Writing a properly-guarded handler (PHP)

If you extend Havenlytics with your own AJAX action, mirror the plugin’s pattern: register both hooks only if you need public access, verify a dedicated nonce, check a capability, then respond. Never skip either guard.

<?php
/**
 * A logged-in-only custom AJAX action.
 * Verifies its own nonce AND a capability before doing any work.
 */
add_action( 'wp_ajax_my_plugin_sync_property', function () {

    // 1. CSRF: verify a dedicated nonce (die on failure).
    check_ajax_referer( 'my_plugin_sync', 'nonce' );

    // 2. Authorization: confirm the user may do this.
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_send_json_error(
            array( 'message' => 'Insufficient permissions.' ),
            403
        );
    }

    // 3. Sanitize input.
    $property_id = isset( $_POST['property_id'] )
        ? absint( $_POST['property_id'] )
        : 0;

    if ( ! $property_id || get_post_type( $property_id ) !== 'hvnly_property' ) {
        wp_send_json_error( array( 'message' => 'Invalid property.' ), 400 );
    }

    // 4. Do the work, then respond.
    wp_send_json_success( array( 'property_id' => $property_id ) );
} );

Common mistake: Registering a handler on wp_ajax_nopriv_* without a nonce because “it is just a public read.” Every Havenlytics public action still verifies hvnly_ajax_request. An unguarded nopriv endpoint is an open door — always verify a nonce.

Best practices

  • Reuse the shared frontend nonce (hvnly_ajax_request) only for genuinely public actions; give admin tools their own nonce.
  • Localize, don’t hard-code. Read the AJAX URL and nonce from the localized script object rather than embedding /wp-admin/admin-ajax.php.
  • Return structured errors. Follow the { message, code, status } shape Havenlytics uses so clients can branch on code.
  • Send both import nonces. Any interaction with the import wizard needs hvnly_import_nonce and hvnly_import_csrf.
  • Prefer REST for new work. For structured CRUD, the REST API gives you schemas, discoverability, and consistent permission callbacks; reserve admin-ajax for the flows above.

Performance recommendations

  • Debounce search input. hvnly_search_properties and hvnly_sidebar_filter_search fire per keystroke if you let them — debounce on the client.
  • Page with hvnly_load_more instead of requesting large result sets in a single call.
  • Cache filter options. hvnly_get_filters results change rarely; cache them client-side for the session.
  • Separate map data from map markup. hvnly_get_properties_for_map (data) and hvnly_render_map_container (markup) are distinct on purpose — fetch data once and re-render locally.

Security notes

  • Rate-limited auth actions (hvnly_ws_verify_emailhvnly_ws_resend_verification) return uniform responses — do not build logic that depends on distinguishing “not found” from “throttled.”
  • Admin actions enforce a capability in addition to a nonce; never rely on the nonce alone for authorization.
  • The conflict-deactivation action layers a global nonce with a per-plugin nonce (hvnly_deactivate_{file}) and requires activate_plugins.
  • Always sanitize $_POST input with absint()sanitize_text_field(), etc., before use.

Common mistakes

  • Sending only one of the two import nonces and getting a rejected request.
  • Assuming nopriv means “no security” — the shared nonce is still verified.
  • Reusing hvnly_ajax_request for an admin action instead of a capability-gated dedicated nonce.
  • Treating a generic rate-limited verification response as a hard error rather than a throttle.
  • Hard-coding the admin-ajax URL instead of reading it from the localized settings.

Need more help?

Can't find what you're looking for? Our team and community are here to help you ship faster.