The host plugin owns the engine, matching, sanitization, and per-user progress. Domain plugins own tour copy and selectors. Spotlight tours stay code-defined. Splash overlays can also come from a docs-site Splash Screen block on release-note posts.
Register a tour
Soft-depend. Hook the register action. Guard the registry. Do not add Requires Plugins: prc-wp-admin-tours.
$loader->add_action( 'prc_wp_admin_tours_register', $this, 'register_tours' );
public function register_tours( $registry ): void {
if ( ! is_object( $registry ) || ! method_exists( $registry, 'register' ) ) {
return;
}
$registry->register( array( /* Tour shape */ ) );
}
The action constant is PRC\Platform\Wp_Admin_Tours\Tour_Registry::ACTION_REGISTER (prc_wp_admin_tours_register). Tour_Parser runs at the registry boundary. Invalid tours are skipped and written to error_log once. Parsed tours are trusted inside the host.
Data shape
A Tour has id, title, version, autoStart, capability (default edit_posts), mode (spotlight default, or splash), screens, and a non-empty steps list.
ScreenMatch is one of { kind: pageSlug, pageSlug }, { kind: postTypeEditor, postType }, { kind: dashboard }, or { kind: anyAdmin }. anyAdmin matches any resolved wp-admin screen. dashboard matches index.php.
A TourStep has id, title, description, selector (null for a centered popover), optional waitMs (default 8000), advanceOnClick, clickOnNext, advanceWhenGone, disableActiveInteraction, side, and optional screens. When a step sets screens, it runs only on those screens. The engine resumes after navigation. disableActiveInteraction blocks clicks on the highlighted control. Use it on Send and Publish.
advanceOnClick advances when the highlighted control is clicked. Next does the same click when clickOnNext is omitted. clickOnNext is a CSS selector for a different control (for example a modal close button). After the step is shown, advanceWhenGone advances once if the highlighted node unmounts.
Ids are namespaced (prc-wp-admin-dataview/first-visit). The parser keeps / in ids.
Bump version after a material change. Completed and dismissed progress for an older version is treated as not started.
Progress
Progress is a sum type stored in user meta prc_wp_admin_tours_progress, keyed by tour id:
{ status: not_started }{ status: in_progress, stepIndex, version }{ status: completed, version }{ status: dismissed, version }
in_progress.stepIndex is the step to show on this page. Completed and dismissed stay unless the client sends not_started (reset) or the tour version increases. Writes are idempotent.
REST
POST /prc-api/v3/wp-admin-tours/progress
Body: { tourId, status, stepIndex? } with status of in_progress, completed, dismissed, or not_started.
not_started deletes that tour key. Permission: logged in and the tour capability.
Screen matching
- If
get_current_screen()->baseispostorpost-new, matchpostTypeEditorwith$screen->post_type. - Else if the base is
dashboard, matchdashboard. - Else if
$_GET['page']is set, matchpageSlug. - Else match
{ kind: admin }soanyAdminstill hits other wp-admin screens.
The engine localizes only tours the user can run whose tour.screens or any step.screens match the current screen. That is how a multi-page in-progress tour resumes in the campaign editor.
Engine
src/engine.js loads driver.js for mode: spotlight. It waits for step selectors (waitMs), skips missing targets, and advances on click when the step asks for it. Next can click clickOnNext or the highlighted control (advanceOnClick) before moving on. advanceWhenGone advances when that target leaves the DOM. Step change, skip, and done POST progress. Each matching tour also registers useCommand as Restart tour: {title}.
mode: splash does not use driver.js. The host overlay (src/welcome.jsx) covers the page. The first-login welcome keeps its chrome and start actions from Welcome::payload(). Catalog splashes render kses’d contentHtml and a Read the release notes action that opens the post permalink. Light mode uses a gray overlay; prefers-color-scheme: dark keeps the original dark overlay. Choosing a start action stores completed and navigates. Skip for now stores dismissed, then a matching spotlight tour can start.
Splash catalog
Editors author splash copy with prc-wp-admin-tours/splash-screen on a docs-site post. The block is editor-only: public render.php returns empty. DocsPress Pages stay Markdown-synced, so this block belongs on posts, not synced Pages.
PRC_DOCS_SITE_ID in vip-config/vip-config.php is the docs blog id (network root 1). Local and cloud can override with prc_wp_admin_tours_docs_site_id. Splash_Catalog::is_docs_site() also treats the docspress stylesheet as the docs site.
On save_post, transition_post_status, wp_trash_post, and deleted_post, the docs site rebuilds the network site option prc_wp_admin_tours_splash_catalog. Each row stores splashId, version, publishedAt (first publish time, not moved on later edits), permalink, showLogo, and InnerBlocks HTML run through do_blocks() and wp_kses_post(). Consumer sites read get_site_option() and do not switch_to_blog().
Tour_Registry::collect() registers catalog rows after prc_wp_admin_tours_register. Tour_Parser keeps an optional splash payload on splash-mode tours. Catalog ids are prc-wp-admin-tours/splash/{splashId}.
Matching drops a catalog splash when user_registered is after publishedAt. Welcome and code-defined spotlight tours are not gated this way. If user_registered is empty, the splash still shows. Ineligible splashes are not localized, so they also stay out of Restart tour. Remaining splash tours sort with welcome first, then catalog newest-first. One overlay runs per wp-admin load.
Shipped consumers
| Plugin | Tour id | Screens |
|---|---|---|
prc-wp-admin-tours | prc-wp-admin-tours/welcome | anyAdmin (first-login splash) |
prc-wp-admin-dataview | prc-wp-admin-dataview/first-visit | DataViews list page slugs |
prc-email-builder | prc-email-builder/campaign-first-visit | Campaigns list, then prc_email_campaign editor |
Stable hooks use data-prc-tour on wrappers the consumer owns.
Related
- User guide
- Tests:
php tests/prc-wp-admin-tours/phpunit/test-*.php,node --test tests/prc-wp-admin-tours/unit/*.test.mjs