The Havenlytics bootstrap process is deterministic: the same ordered sequence runs on every request, from the entry file’s procedural setup through the HvnlyNab singleton and into Bootstrap::init(), which stands up every service in a fixed order. This guide walks the sequence step by step for professional WordPress developers who need to know exactly what exists at each moment and where to hook in. It complements Plugin Architecture & Lifecycle with the low-level detail.
Stage 1: havenlytics.php load order
When WordPress includes havenlytics.php, procedural code runs immediately — before any class bootstrap. This stage prepares the environment so the singleton can be built safely.
- Define constants (
HVNLYNAB_VERSION, paths, URLs, slug). require_oncethe migration bootstrap,includes/Core/Migration/migration-bootstrap.php, loadingMigrationTrait/MigrationInterfacebefore the Composer autoloader.require_oncethe shared function files:utility-functions.php,email-functions.php,field-options.php,deletion-safety.php,template-bootstrap.php.- Define
HVNLYNAB_TEMPLATE_DEBUGasfalseif not already set. require_once vendor/autoload.php(resolves__DIR__first, falls back toHVNLYNAB_PATH).- Re-run
hvnly_load_migration_dependencies()after autoload. ErrorHandler::init(__FILE__)if the class exists; otherwise show a “Core class missing” admin notice andreturn.ConflictChecker::get_instance()if the class exists.add_action('plugins_loaded', ContactAgentAjaxRegistrar::register, 4).- Define the final class
HvnlyNaband theHVNLY_NAB()accessor. - Call
HVNLY_NAB()at the end of the file, instantiating the singleton.
Note: The contact-agent AJAX registrar hooks in at
plugins_loadedpriority 4 — one priority earlier than the maininit_plugin()at priority 5 — so AJAX endpoints are ready before the rest of the plugin initializes.
Stage 2: HvnlyNab constructor and init_plugin
Calling HVNLY_NAB() runs HvnlyNab::__construct(), which performs guards and defers the real work.
basic_environment_check()— PHP >= 7.4 (MIN_PHP), WP >= 6.0 (MIN_WP). On failure it queues admin notices, self-deactivates onadmin_init, and the constructor aborts.EnvironmentChecker::check()— a duplicate PHP/WP gate; aborts on failure.register_lifecycle_hooks()— activation and deactivation hooks.add_action('plugins_loaded', [$this, 'init_plugin'], 5).
When WordPress fires plugins_loaded at priority 5, init_plugin() runs the following, with the whole body wrapped in try/catch that calls ErrorHandler::record_fatal():
- Bail if
ErrorHandler::has_fatal_recorded(). init_appsero()— telemetry client, try/catch, silent-failing.$this->container['bootstrap'] = new Bootstrap();then->init().add_action('plugins_loaded', <Elementor integration>, 20)— loadsincludes/Integrations/Elementor/Bootstrap.phpand calls::get_instance()only if\Elementor\PluginorELEMENTOR_VERSIONis present.do_action('hvnly_loaded').
<?php
// hvnly_loaded is the earliest safe point for extension code:
// Bootstrap::init() has already populated the container.
add_action( 'hvnly_loaded', function () {
$engine = HVNLY_NAB()->engine();
// Register your own hooks, endpoints, or shortcodes here.
} );
Stage 3: Bootstrap::init() ordered steps
Bootstrap::init() is the service-startup pipeline. The order matters — later steps depend on services registered by earlier ones.
load_core_dependencies()— reserved no-op. Ifis_admin(), initializesMigrationAdminNotices.init_engine()— requiresincludes/HvnlyEngine.phpand calls\HvnlyEngine::get_instance()if the class exists; otherwise builds an anonymous fallback engine.init_shared_services()—AvatarService::register_hooks(); registersHelper(Helpers::get_instance()),DB(new Database()), andrest_api(new Controller()). All calls areclass_exists-guarded and wrapped in try/catch.init_settings_system()— registerssettings_manager(SettingsManager::get_instance()) andstyle_generator(DynamicStyleGenerator::get_instance()).check_version_update()— runs migrations if needed (see below).init_services()— registerscache_manager(new CacheManager()) andhook_manager(new HookManager()), then splits into admin/frontend services.register_hooks()— delegates toHookManager::register_hooks().schedule_events()— delegates toScheduler::schedule_events().init_enterprise_features()—EnterpriseCache::get_instance().
The admin/frontend service split
Step 6 loads different services depending on the request context, which keeps each request lean.
- Admin only (
is_admin()) —init_admin_services()registersadmin(new Admin()),cache_admin(new CacheAdmin()), andworkspace(WorkspaceBootstrap::get_instance()). - Frontend or AJAX (
!is_admin() || DOING_AJAX) —init_frontend_services()registersfrontend(new Frontend()).
Tip: Because the frontend service also loads during AJAX (
DOING_AJAX), front-end functionality remains available toadmin-ajax.phprequests. Design AJAX handlers accordingly.
Performance: Admin-only services (the settings admin, cache admin, and Workspace bootstrap) never load on front-end page views. Keep your own admin-heavy code behind an
is_admin()check to match this pattern.
Version check and migrations
Step 5, check_version_update(), is where page-load migrations run. If a migration is needed or the stored DB version is behind HVNLYNAB_VERSION, it runs the migrator; on success it updates the hvnly_db_version option, calls Installer::update(), and schedules a rewrite flush. This runs on every eligible init, which is how an upgraded plugin folder self-heals its data without re-activation.
<?php
// Force migrations during local development or a controlled deploy.
// Runtime-checked constant; define before the plugin boots.
define( 'HVNLY_FORCE_MIGRATIONS', true );
Stage 4: Hooks registered by HookManager
Step 7 delegates to HookManager::register_hooks(), which registers the runtime WordPress hooks that keep the plugin working during a request. This is the authoritative list.
| Hook | Callback | Notes |
|---|---|---|
admin_menu (99) | maybe_redirect_after_activation | Onboarding redirect after activation. |
activated_plugin (10, 2 args) | on_plugin_activated | Reacts to plugin activation. |
plugin_action_links_{basename} | plugin_action_links | Adds Docs / Get Started / Clear Cache links. |
| (rewrite) | RewriteRulesManager::register() | Wires permalink handling and flush hooks. |
wp_ajax_hvnly_clear_cache | handle_cache_ajax | Requires cache enabled + manage_options. |
wp_ajax_hvnly_get_cache_stats | handle_cache_ajax | Nonce hvnly_cache_nonce. |
wp_ajax_hvnly_update_cache_settings | handle_cache_settings_ajax | Requires manage_options. |
| (conditional) | ContactAgentAjaxRegistrar::register() | If the class exists. |
save_post_hvnly_property | CacheInvalidator::invalidate | Clears search/query caches on property save. |
delete_post | CacheInvalidator::invalidate | Clears caches on delete. |
hvnly_daily_cache_cleanup | CacheManager::clear_expired_cache | Daily cron; scheduled if not already. |
init | localization_setup | wp_set_script_translations('havenlytics-app', ...). |
doing_it_wrong_trigger_error (10) | textdomain_notice | Suppresses a known textdomain notice. |
admin_enqueue_scripts | admin_scripts | Empty placeholder. |
Security: The cache AJAX handlers require
hvnly_is_cache_enabled(), themanage_optionscapability, and thehvnly_cache_noncenonce. Mirror this capability-plus-nonce pattern in any AJAX endpoint you add. See Security and AJAX API.
Event scheduling
Step 8 calls Scheduler::schedule_events(), which schedules the daily cache cleanup and daily analytics cleanup events. Note a documented internal detail: Scheduler::init() is never called, so the custom weekly and monthly cron intervals are not registered — only the two daily events reliably schedule. Do not rely on weekly or monthly Havenlytics cron intervals in your own code.
Warning: Because the weekly/monthly custom intervals are unregistered, scheduling against them will silently fail to recur. If you need periodic work, register your own interval via
cron_schedules.
Best practices
- Hook extension logic on
hvnly_loaded, afterBootstrap::init()has populated the container. - Respect the admin/frontend split: guard admin-only code with
is_admin()and remember AJAX loads the frontend service. - Reuse the cache AJAX security pattern (capability + nonce) for your own endpoints.
- Let migrations run naturally on admin page-load; only force them with
HVNLY_FORCE_MIGRATIONSin controlled scenarios.
Common mistakes
- Assuming all services exist everywhere. Admin services are absent on front-end requests, and vice versa.
- Depending on weekly/monthly cron. Only the daily events register.
- Hooking too early. Code on
plugins_loadedbefore priority 5 runs before the container is built. - Skipping nonce/capability checks on AJAX handlers, unlike the built-in cache handlers.