User guides
Corex Guides puts a Guides screen in the admin. Corex registers its own guides through it — reading and answering messages, publishing a post, checking whether an email was sent — and your site registers its own through exactly the same public API.
This matters because a client’s guide is about the client’s site. It documents their post types and their flows, it ships with their plugin, and it must survive a Corex upgrade without anybody editing Corex.
Scaffold one
Section titled “Scaffold one”wp corex make:guide ProjectsThat writes Guides/ProjectsGuide.php in your plugin, with a topic, two steps, and a warning — the
parts authors skip when starting from an empty file.
Register it
Section titled “Register it”From your service provider’s boot():
use Corex\Guides\GuideRegistry;use Corex\Support\Facades\Corex;
Corex::onReady( static function (): void { Corex::make( GuideRegistry::class )->registerDeferred( static fn (): array => [ ( new ProjectsGuide() )->guide() ] );} );Use registerDeferred(), not register()
Section titled “Use registerDeferred(), not register()”Both exist, and only one is safe from a plugin.
Corex boots on plugins_loaded at priority 10. Your plugin very likely boots on plugins_loaded at
priority 10 too — the generated starter does. Which of you WordPress loads first depends on your
plugin’s directory name, so register() works or silently does nothing depending on what you called
your folder.
registerDeferred() takes a factory and runs it the first time anything reads the registry,
which is an admin request — long after every plugin has loaded. There is no race left to lose.
Write the guide
Section titled “Write the guide”use Corex\Guides\Guide;use Corex\Guides\GuideStep;use Corex\Guides\GuideTopic;
Guide::for( 'projects', __( 'Managing projects', 'perego' ) ) ->withSummary( __( 'Add a project, choose where it appears, and publish it.', 'perego' ) ) ->inSection( 'content' ) ->onScreen( 'edit.php?post_type=perego_project' ) ->requiring( 'edit_posts' ) ->withTopic( GuideTopic::for( 'add', __( 'Add a project', 'perego' ), '', [ new GuideStep( __( 'Choose Projects, then Add Project.', 'perego' ), __( 'The editor opens with an empty project.', 'perego' ), ), new GuideStep( __( 'Choose Publish.', 'perego' ), __( 'The project appears on the Projects page.', 'perego' ), warning: __( 'Publishing makes it public immediately.', 'perego' ), ), ], ) );What each part does
Section titled “What each part does”| Method | Effect |
|---|---|
inSection() | Groups the guide on the Guides screen. Sections appear in key order, alphabetically — that is the only lever you have over where your section lands. |
onScreen() | The admin address the guide describes. The Guides screen renders it as a link straight to that screen, so a reader who has finished the steps is one click from doing them. Use the address WordPress itself uses: edit.php for posts, edit.php?post_type=… for anything else. |
requiring() | The capability a reader needs. Anyone without it never sees the guide — they could not follow it. Omit it and the guide is shown to anyone who can open the admin. |
ordered() | Position within the section. Lower first; the default is 50. |
A step is an instruction and an expected result, not a paragraph. The pair is what makes a step checkable: somebody does the first and can tell from the second whether it worked. A guide written as prose reads fine and leaves people stuck with no way to know they are stuck.
Add warning: to anything hard to undo. It renders before the instruction — a caution somebody
meets after the fact is decoration.
Replace a Corex guide
Section titled “Replace a Corex guide”Guides are keyed by id, so registering your own under a Corex id replaces it:
Guide::for( 'corex-publishing', __( 'How we publish here', 'perego' ) )Without a container
Section titled “Without a container”If you cannot reach the container, use the filter. Anything returned that is not a Guide is
discarded rather than rendered — this screen is what somebody opens when they are already stuck.
add_filter( 'corex_guides', static function ( array $guides ): array { $guides[] = ( new ProjectsGuide() )->guide();
return $guides;} );Screenshots
Section titled “Screenshots”A step can carry one:
use Corex\Guides\GuideScreenshot;
new GuideStep( __( 'Choose Projects.', 'perego' ), __( 'The list opens.', 'perego' ), screenshot: new GuideScreenshot( 'projects-list', __( 'The Projects list.', 'perego' ) ),);The alt text is required, not optional — a screenshot with no text alternative is a step a
screen-reader user simply does not get.
The capture id links the image to the script that produces it in both directions: the script fails
loudly for an id it was asked for and could not capture, and a reviewer can find the code behind any
image in the tree. Corex captures its own with
node tests/e2e/capture-guide-screenshots.mjs; add your ids to your own equivalent so your images
cannot quietly stop matching your product.
Guides for inactive features
Section titled “Guides for inactive features”There is nothing to do. Register your guides from your own service provider, and a deactivated
plugin registers nothing — so its guides are simply absent. No is_active() check, and no list of
plugin slugs to keep in step with reality.
When the guides are not enough
Section titled “When the guides are not enough”A reader who did not find their answer needs a person, not another page. The Guides screen ends with a Still stuck? panel: a category, a message, and their email, sent to the address the site owner configured.
Changing the address
Section titled “Changing the address”Two ways, and the second wins:
-
In code.
addons/corex-guides/config/guides.php— one line.return ['support' => ['email' => 'support@example.com','enabled' => true,],]; -
In the admin. CoreX Settings → Guides → Support email. This writes the
corex_guides_support_emailoption, which layers over the file, so a site owner can change it without a deploy. Leave it blank to fall back to the shipped address.
Setting Offer the support form to off removes the panel entirely. That is different from leaving the address blank: with the panel on and no deliverable address, the screen says support is not set up here rather than drawing a form that would discard what somebody typed.
How the message is sent
Section titled “How the message is sent”Through Corex Mail when addons/corex-email is active, and through wp_mail() when it is not.
Neither is required — a help form must not stop working because a site does not use the mail add-on.
The reader’s own address becomes the Reply-To, if it validates; if it does not, the header is
dropped rather than passed through.
The outcome is reported honestly. If the transport refuses the message, the panel says it did not send and gives the text back, rather than showing a confirmation for something nobody received.
What it will not do
Section titled “What it will not do”- It does not retry. One message per user per minute, enforced server-side, so a double-click or a replayed POST does not arrive twice.
- It does not store anything. The message is an email. If you want a record, route the address to a shared inbox.
- It carries no attachments — text, the sender, and the site URL.