FAQ
Which AEM versions does it work with?
Section titled “Which AEM versions does it work with?”AEM 6.5, AMS, AEM as a Cloud Service and the local SDK, on any domain. The extension has no opinion about your AEM version because it never talks to AEM to work out where it is: it reads the hostname and path of the page in front of it and matches them against your configuration.
Does it work in the Universal Editor?
Section titled “Does it work in the Universal Editor?”No.
The toolbar works wherever AEM’s classic Touch UI editor does, which is AEM 6.5, AMS, AEM as a Cloud Service and the local SDK.
The Universal Editor is a different editing interface.
The toolbar works out where it is by matching the page URL’s path and query string against your configuration, and the Universal Editor keeps the content path in the part of the URL after the #, so there is nothing there for it to match on.
On a Universal Editor page the toolbar has nothing to work from and stays empty, and no amount of configuration changes that, so do not spend an afternoon on it.
Do we have to install anything in AEM?
Section titled “Do we have to install anything in AEM?”No. There is no AEM package, no code change and no service to run. Everything is in the browser, and the lookups that do reach AEM use endpoints that are already there.
Does it work on our customised author UI?
Section titled “Does it work on our customised author UI?”The parts that matter do.
The hostname decides the environment and whether you are on the author or the live side, and that is unaffected by any customisation.
Only the recognition of individual AEM screens, the page editor as against the sites console, is pattern-matched from the URL, and region-map.yaml is there for the rare estate whose author URLs have been changed.
Does it work on the published site too?
Section titled “Does it work on the published site too?”Yes, and that is half the point. On a published page the toolbar offers Edit Page, Page Properties and Open Enclosing Directory back into the author instance, plus DAM edit links on the images.
Is there a Firefox version?
Section titled “Is there a Firefox version?”There is a Firefox build in the repository, but it has not been published. Chrome only for now.
Setting it up
Section titled “Setting it up”Do my editors have to touch YAML?
Section titled “Do my editors have to touch YAML?”No. One person writes the configuration and hosts it. Everybody else installs the extension, clicks one link and presses import. See setting it up for a team.
The toolbar does not appear at all. What is wrong?
Section titled “The toolbar does not appear at all. What is wrong?”Nearly always the host entry.
A bare hostname such as author.acme.com implies https and matches any scheme and port, a full origin such as http://localhost:4502 matches exactly, and a port with no scheme such as localhost:4502 is ambiguous and is ignored.
If it is missing on the live page only, the public hostname is not listed under that site.
If the entry is right and you are authoring in the Universal Editor, no configuration will help: see does it work in the Universal Editor above.
View Live goes to a 404.
Section titled “View Live goes to a 404.”stripPath is wrong for that site, or the site key does not match the folder under /content.
Open a page in the editor, press View Live and read the path it produced: it will tell you which segment is wrong.
Our live URLs end in .html and the toolbar drops it.
Section titled “Our live URLs end in .html and the toolbar drops it.”Live URLs are built without the extension. One path transform puts it back, and worked example 2 has the rule.
Edit Page on a live page lands on a path that does not exist.
Section titled “Edit Page on a live page lands on a path that does not exist.”Check the site’s pathTransforms, which the toolbar turns around to get back: the liveReplacement becomes the pattern, its capture groups are read out of the live path, and they are written into the literal text of the authorPattern, so ^/catalogue/(.+)$ to /shop/$1 maps both ways.
It declines to guess in seven cases and leaves the live path as it is: an authorPattern that does not spell out the text it matched, such as one with alternation; a pattern that repeats a part, such as the /+ in ^/blog/+(.+)$, because nothing in the live path says how many times it matched; a pattern with an optional part in the middle, such as (?:draft-)? before more of the path, which two different author paths would both publish to the same live path; a live path that only shares a prefix with a mapped one, so ^/catalogue to /shop leaves /shopping-list alone; a reversal that does not survive the trip back out; a rule carrying more than three capture references, which would cost too much to match against a long path; and a rule that repeats a group which can itself repeat, such as the (a+)+ in ^/(a+)+b$, which a path that does not match would take the toolbar tens of seconds to give up on.
That prefix check cannot be made when the liveReplacement ends in a capture group, so write a prefix swap as ^/catalogue to /shop rather than as ^/catalogue(.*)$ to /shop$1.
The capture cap is read off every rule in the list, because each answer is checked by running the whole list back out again, so one rule carrying more than three stops the site’s other rules reversing too.
The repeated-group one costs only itself, so the site’s other rules still reverse, but it is the one case here that changes the live URL: the toolbar skips that rule going out as well, so View Live points somewhere wrong rather than nowhere, and writing the repetition once, as ^/(a+)b$, is the fix.
One or two captures is the normal shape, and a rule that needs more is better split across several transforms.
Press Edit Page on a live page before you hand the configuration to anyone.
Edit Page works on a section’s landing page but not on the pages under it.
Section titled “Edit Page works on a section’s landing page but not on the pages under it.”The rule for that section ends in an optional catch-all, such as ^/secure-content(?:\.html)?(?:/.*)?$ to /public-content.
That publishes every page under the section to the same live URL, so the live URL of a child page carries nothing that says which child it is.
Edit Page answers the section’s landing page from it, and a live URL below the replacement, which the rule never produces, is left on the untransformed path.
Write the rule as a plain prefix swap, ^/secure-content to /public-content, which keeps each page’s own path and reverses at every depth.
An earlier version of the configuration template shipped the catch-all form, so check yours if you started from it.
Where can we host the configuration package?
Section titled “Where can we host the configuration package?”Anywhere your team’s browsers can reach over HTTPS with no login in front of the files. That explicitly includes an intranet-only or VPN-only server, because every fetch happens from each user’s own machine; worked example 4 works that through.
Somebody has customised their own settings. Does our package overwrite them?
Section titled “Somebody has customised their own settings. Does our package overwrite them?”No. Each configuration file resolves as a personal override first, then your hosted package, then the bundled defaults. When your package changes a file somebody has overridden personally, they are shown a diff and choose which version to keep.
How long until an update reaches everyone?
Section titled “How long until an update reaches everyone?”Four hours by default, and each person can force it from Configuration, then Connection, then Fetch Package & Apply All Now.
Our package imported once and then stopped updating.
Section titled “Our package imported once and then stopped updating.”The URL is behind a login. The first import happens while the user is looking at the page, but the scheduled refresh is a plain fetch with no login flow. Open the manifest URL in an incognito window: if you get a login screen, so does the refresh.
Going further
Section titled “Going further”Each block below is a fragment of the file it names rather than the whole file: the key it sits under usually carries rows already, and pasting the block over the top loses them. The comment at the top of each block says whether it is something to add or a quote from the file as it ships.
Can a link point at something only some of our pages have?
Section titled “Can a link point at something only some of our pages have?”Yes.
A {page.<name>} placeholder in a link’s urlTemplate resolves to one of the AEM page’s own properties, read from the page itself on author pages, and an item naming a property the page does not carry is not rendered at all.
So a link to a related content fragment can be configured once for the whole site and stays silent on every page that has no such fragment.
The setup guide sets out the token in full under page property tokens: where the properties are read from, what a cleared or multi-value one is worth, and which fields the token resolves in.
Can we limit a link to one section of the site?
Section titled “Can we limit a link to one section of the site?”Yes, with a condition on the item.
It takes the same operators as message-matrix.yaml, so urlPathContains is usually the one you want.
# Added to the rows author/pageEditor already carries.author/pageEditor: - label: "Event details fragment" urlTemplate: "{instanceOrigin}/editor.html{page.eventDetailsContentFragmentPath}" icon: "contentFragment" condition: operator: "urlPathContains" value: "/content/acme/events/"The two narrowings stack: the condition keeps the link out of the rest of the site, and the page property keeps it off the event pages that have not been given a fragment.
Can a rule check the markup of the page an author is editing?
Section titled “Can a rule check the markup of the page an author is editing?”Not usefully, and that is worth knowing before you write a rule for author/pageEditor rather than after.
The page being edited is loaded into an iframe, #ContentFrame in AEM’s page editor, and that iframe is not the document the toolbar runs in.
iframeSelector on an assertion says which document that assertion is judged against, and each assertion in a tree carries its own, so branches judged in different documents sit in one rule.
What it does not give you is a useful view of the page being edited.
A rule without activeListen is judged once, when the toolbar starts, and nothing waits for the frame: the toolbar renders as soon as the editor’s own page is ready, whether or not the page being edited has arrived in the frame by then.
Judged before it arrives, the rule is answered by a blank document rather than dropped, so an elementExists finds nothing and an elementMissing fires and stays on the rail, because nothing judges that rule again on that page.
activeListen does not close the gap either, for the reason the answer below gives.
So assert against what the top document actually carries, which on the page editor is the URL, the page’s own properties and the editor chrome itself. A rule about a page’s own markup belongs where the page is the document the toolbar runs in, which is a published page or the author instance’s own preview.
# Added to the rows public/page already carries.public/page: - message: "This page still uses the retired hero component." messageType: "warning" icon: "alert" assertion: operator: "elementExists" value: ".cmp-hero--legacy"Can one rule combine several checks?
Section titled “Can one rule combine several checks?”Yes, with logicalOperator: "AND" or "OR" and an assertions list.
Every branch is judged in the same pass, so a check on the URL and a check on the page’s markup can decide one message between them.
# Added to the rows public/page already carries.public/page: - message: "Campaign page with no tracking code set." messageType: "warning" icon: "alert" assertion: logicalOperator: "AND" assertions: - operator: "urlPathContains" value: "/campaigns/" - operator: "elementMissing" value: "meta[name='acme:tracking-id']"It is written for the published page on purpose: scoped into the editor’s content frame, the same elementMissing is answered by whatever that frame holds at the moment the toolbar starts, which is the trap the answer above describes.
A branch the toolbar cannot judge is dropped on its own account and stays in the list rather than disappearing out of it, so an AND carrying one fails instead of passing as though that check had never been written.
An assertions: [] you wrote deliberately keeps its plain meaning: an AND with nothing to check passes and an OR fails.
Can a message follow a change on the page rather than only the page load?
Section titled “Can a message follow a change on the page rather than only the page load?”Yes, within one limit worth knowing before you write the rule.
activeListen: true puts the rule on a debounced watch of the document the toolbar is running in, and re-judges it on each batch of changes to that document.
On a published page and on the author instance’s own preview the page is that document, so a rule about the page’s markup appears and clears as the page changes under it.
# Added to the rows author/pagePreview already carries.author/pagePreview: - message: "The hero is still on the draft variant." messageType: "warning" icon: "alert" activeListen: true assertion: operator: "elementExists" value: ".hero[data-variant='draft']"The limit is the page editor.
The watch is on the toolbar’s own document only, so a change confined to the content frame sets off no re-evaluation, whatever iframeSelector the rule carries.
Such a rule is re-judged whenever the editor chrome around the frame happens to change, which is not the same as following the author’s edits, so do not write one that depends on it.
The toolbar ignores its own markup when it watches, and the message is removed again the moment the rule stops passing. Use it for state that changes in place; a rule about something that only changes on navigation does not need it.
Can a message show a value read off the page?
Section titled “Can a message show a value read off the page?”Yes.
{attr.<name>} reads an attribute off the element the assertion actually matched, and {attr.textContent} reads its text.
# Added to the rows public/page already carries.public/page: - message: "Tracking ID: {attr.content}" messageType: "info" assertion: operator: "elementExists" value: "meta[name='acme:tracking-id']"On a compound rule the value comes off the first branch that passed and named an element, which on an AND may be any of them, since every branch of an AND passed.
A branch that did not pass never lends its element, and nor does an elementMissing or an elementExists carrying negate: true.
The tree steps over those rather than stopping at them, so an AND of an elementMissing and an elementExists reads its value off the element the elementExists matched.
The value is read off the page again when the message is built, so the placeholder resolves empty unless the tree settled on a branch whose selector still matches an element.
A rule that quotes a value is worth writing on a branch that matches something, rather than on one that passes by matching nothing.
{url} and {environment} resolve in the same message text, and {page.<name>} does too on an author page.
Can a message be the fix rather than the notice?
Section titled “Can a message be the fix rather than the notice?”Yes.
A urlTemplate on a message makes it clickable, and it resolves the same placeholders the text does.
The rule that ships in message-matrix.yaml is the worked case: it notices that a preview is missing ?wcmmode=disabled and the message itself is the link that adds it.
# Quoted from the rows message-matrix.yaml ships with, not added to them.author/pagePreview: - message: "Previewing without ?wcmmode=disabled. Click to add it for a true published view." messageType: "warning" icon: "alert" urlTemplate: "?wcmmode=disabled" assertion: operator: "urlContains" value: "wcmmode=disabled" negate: trueA resolved URL has to name http, https, mailto or no scheme at all, and anything else drops the item rather than rendering it.
That check is on the resolved string, not on what you typed, because a page property or an asserted attribute can put anything in it.
Can one configuration serve sites that do not share the same conventions?
Section titled “Can one configuration serve sites that do not share the same conventions?”That is what the skipping rules are for.
A link or message naming a page property the page does not carry is not rendered, and nor is a link whose condition does not pass or a message whose assertion does not, so a rule written for one team’s convention costs the other teams nothing.
The corollary is the one thing it cannot do: a {page.<name>} rule can never tell you the property is missing, because the rule goes with it.
See can I warn when a page property is missing for what to do instead, and worked example 3 for a team’s own conventions written up in full.
What happens when I get a rule wrong?
Section titled “What happens when I get a rule wrong?”The rule is dropped and named once in the browser console, and the rest of the toolbar renders as usual.
That covers a field the operator needs left out, or cleared to nothing where the operator looks inside that field rather than compares it, an operator that is not one of the ten, a selector that is not valid CSS, an iframe the page does not carry, and an evaluation that throws.
The three fields compared for an exact value are the exception, paramValue, itemValue and varValue: ?param= and an item stored empty both read back as an empty string, so those may be written as "", and they drop the rule only when left out altogether or written with nothing after the colon.
The important part is that negate cannot turn a mistake over: it inverts a verdict the toolbar actually reached, so a broken rule can never be read as false, flipped, and shown on every page in the context.
elementMissing is the one operator that reads an invalid selector rather than dropping the rule over it, since nothing can match a selector the browser cannot parse, and the console says so in those words.
A compound rule can be wrong in ways of its own, in the logicalOperator, in the assertions list, or in a branch written where an assertion belongs.
Those are named in the console the same way, and a branch the toolbar cannot judge is dropped on its own account rather than disappearing out of its list, so an AND carrying one fails.
The comments at the top of message-matrix.yaml are the full list of the field and operator mistakes, and they ship with the extension.
Security and privacy
Section titled “Security and privacy”Why does it ask for access to all websites?
Section titled “Why does it ask for access to all websites?”AEM runs on customer-chosen domains, so the extension cannot name them in advance.
It reads the page URL to decide whether the current host is one you configured, and on any other host it does nothing beyond that check.
The one other thing that loads anywhere is the package importer, on any URL containing .aem-toolbar.json: that is how a configuration package is imported, and all it does is read the page’s own text and offer an import dialog that has to be accepted before anything is stored.
What does it collect?
Section titled “What does it collect?”Nothing. No analytics, no telemetry, no accounts, no cookies, and no request of any kind to the extension’s author. The privacy policy is the full statement.
What does it talk to, then?
Section titled “What does it talk to, then?”Your own AEM instances, and your own configuration URL if you set one up. The AEM requests are the logged-in check, the DAM asset lookup, the content fragments an author page uses, and the page’s own properties when one of your rules needs them. They carry the editor’s existing AEM session and go nowhere else.
Does it execute anything it downloads?
Section titled “Does it execute anything it downloads?”No.
A configuration package is YAML and JSON data that is validated before it is stored.
The extension uses no eval() and no remote script execution.
Does it keep a history of pages we open?
Section titled “Does it keep a history of pages we open?”No. It stores the content path of the last author page you opened, overwritten every time, so the options page can offer a property picker. That is one path in your own browser profile, and there is a button to forget it.
Limits
Section titled “Limits”“Content fragments” says “Could not load”.
Section titled ““Content fragments” says “Could not load”.”The instance refused the toolbar’s QueryBuilder request at /bin/querybuilder.json, which that feature depends on.
Plenty of organisations restrict it for non-admin users.
The toolbar says so rather than showing a wrong count, and stays quiet for a while afterwards.
Ask your AEM administrator whether QueryBuilder is available to authors.
Can I warn when a page property is missing?
Section titled “Can I warn when a page property is missing?”Not with a {page.<name>} rule.
A link, message or condition naming a property the page does not carry is not rendered at all, which is exactly what makes one configuration safe to apply across a whole site.
For “somebody forgot to do X”, assert against the rendered page instead, the way the noindex warning in worked example 3 does.
A page property rule never fires on our live site.
Section titled “A page property rule never fires on our live site.”Page properties are read from the AEM page node on the author instance, so a {page.<name>} rule only ever resolves on author pages.
A rule written under public/page will never appear.
We opened a site that is in AEM but not in our config.
Section titled “We opened a site that is in AEM but not in our config.”You get a partial toolbar. The author hostname matched, so the toolbar renders and Page Properties works, but there is no View Live because there is no public domain to send it to. It looks like a missing feature to an editor, so list every site you expect people to open.