Troubleshooting

The silent failures come first: no error, no log line, no exception — just a result that looks plausible. They are the expensive ones.

Nothing appears where it should

Symptom Likely cause
The [H1] SEO tab does not appear in a content app The decoration file name does not match the target app's name — not its folder, not its label. Silent: no tab, no log line. Compare with name: inside the app definition.
The tab does not appear in page properties, on an instance that is not the H1 platform The shipped decorations name frontend-md3 in their own path, so on an instance without that light module they never apply — silently. Reproduce them in your own light module; see Developers · Installation.
The tab appears but a panel (Metadata or Sitemap) is missing That feature's decoration was not applied to that particular dialog. The shipped set puts Metadata on Base and FrontPage, and Sitemap on Base, FrontPage and CaseStudyDetail.
Fields save without complaint, but REST, llms.txt and the FTL all behave as if they were empty A composite in the chain is missing itemProvider: currentItemProvider, so the values were written into a child node instead of flat properties on the node. The editor reads them back correctly, which is what makes this one expensive. It must be present at every level.
An app or dialog "does not update" after a change An invalid definition is not replaced — Magnolia keeps the previous one live. Check Tools › Definitions for errors in the file you just edited.
The Redirects app is not in the launcher The admincentral decoration did not apply, or the module did not start. Check Tools › Modules first.

The sitemap

Symptom Likely cause
A collection's items are missing Three candidates, all silent: the source workspace has no anonymous read ACL; h1sitemapSourceWorkspace names a workspace that does not exist; or the sitemap's root path does not exist in the source workspace — items are collected under the same root as the sitemap, so /h1/sitemap reads items from /h1 of that workspace.
A whole section is missing Exclude from sitemap is ticked on the section's landing page. It removes the page and its entire subtree, and the traversal does not descend.
/robots and /llms are listed as normal pages Expected: the sitemap only skips the node names sitemap and robots.txt. Tick Exclude from sitemap on the robots and llms pages.
/sitemap.xml returns 404 There is no page named sitemap at that root, or the template is not available in the site (templates.availability.templates).
/sitemap.xml renders but is empty The root has no mgnl:page children the current user can read. On the public instance that means nothing is published, or website has no anonymous read ACL. An empty urlset is a valid answer.
A <loc> came out as /en.html A loc that is only a language prefix is that language's home and must be rendered as a directory URL. The shipped sitemap.ftl handles it by comparing the segment against model.locales; a custom renderer has to do the same.
x-default points somewhere unexpected By design it points at the default language's variant, not at the unprefixed path — except on the home page, where the default language is the bare path.
A priority I typed is ignored The stored value does not match ^(0(\.\d+)?|1(\.0+)?)$, so the service falls back to the computed value. The form validates the same expression, so this usually means the value was written by an import or by hand.

Metadata

Symptom Likely cause
No publisher, and no fallback for ogImage h1-sites is absent; or no site's rootWebsite matches; or anonymous cannot read h1-sites. All three degrade silently to the per-node values.
A content item's metadata has no globals at all The content-item channel resolves the site only from marca. Without that query parameter there is no site, so ogSiteName, publisher and the image fallback are all missing.
404 on a path that exists Either the node is not an mgnl:page (asking for a component's path is a 404 by design), or marca prefixed the path into something else. The prefixing is idempotent, so ?marca=M3 with /M3/home stays /M3/home — but ?marca=M3 with /other/home becomes /M3/other/home.
noindex: false on every response, even where nothing was set Expected: noindex and nofollow are primitive booleans and are always serialised. Everything else that is empty is omitted from the JSON rather than returned as null.
The response has no canonical By design. The front end builds it with its own routing; a JCR path is not a URL. The Freemarker include does emit <link rel="canonical">.
ogImage looks wrong: jcr:2f9c… Expected: a damLinkField persists jcr:<uuid> and the headless channel returns the reference raw. Resolve it on the consumer side.
Structured data ignores the Schema.org type and the topics Raw JSON-LD (override) is filled in. It replaces the generated block entirely.
The wrong language comes back The language is resolved from Accept-Language, matched against the current site's configured locales, falling back to the site default. Outside a web context, and when no site resolves, the global i18n configuration is used.
An item's title falls back oddly The chain is h1seoTitletitlename. Content types usually have name and no title, which is why name is in the chain at all.

robots.txt and llms.txt

Symptom Likely cause
/robots.txt returns 404 There is no page named robots at that root. A page that exists with an empty body answers {"content":""}, which is valid and means "allow everything".
The Sitemap: line appears twice It was typed into robots.txt content. Each channel appends it with its own domain; do not store it.
llms.txt shows a brand's title and description at the root Site resolution for llms.txt also matches a site whose rootWebsite starts with the root, so at / every site matches and the longest rootWebsite wins. On a multi-brand instance, build llms.txt per brand (/brand-a/llms).
A page marked No index is gone from llms.txt but its children are still there Expected, and deliberately different from the sitemap: h1seoNoindex drops that page's own entry and keeps descending.
A section is missing entirely from llms.txt Every page in it was excluded, so the section has no entries and is dropped.
A dynamic page's children are missing Expected: a dynamic page emits one entry per collection item and stops descending.

Redirects

Symptom Likely cause
A rule does not fire Active is not ticked — the rule is loaded only if the property exists and is true; or a more specific rule wins (exact > prefix > regex, then longest pattern); or you are inside the 60 s TTL on an instance where the observation listener could not be registered. That last one logs a warning once, at load time: "No se pudo registrar el observador".
A rule filed under /brand-a fires on another brand Brand folders are organisational. The virtual URI channel evaluates all active rules with no brand scoping; only the REST channel filters by folder. Encode the brand in sourcePattern if the rule must be brand-specific.
The target gained a /es I did not write On a multilingual site the target always carries a language, chosen as: the target's own → the request's → the site default. That is intentional — a bare path is not canonical and would cost a second redirect. The default language's home stays bare (/).
A prefix rule redirects the section landing page too Expected: prefix matches the pattern itself as well as everything under it. /blog/* catches /blog and /blog/x.
A regex rule never matches The stored pattern is not a valid regex — it is logged and skipped — or it is not anchored, or sourcePattern does not start with ^ and was rejected on save.
Re-importing an export duplicated every rule The name column was dropped or emptied. It is the identity of the row; without it the node name is derived from sourcePattern and a rule created from the app (whose node is called 0, 1, …) is not recognised as the same one.
A CSV import put rules in the wrong folder The marca column wins over the folder open in AdminCentral. That is what makes export → edit → re-import symmetrical; check the column.
active came back false after an import The cell is parsed with Boolean.parseBoolean, so only true (in any casing) is true: 1, yes, and x all become false. A blank cell is different — it leaves the stored value untouched on an update, and leaves the property absent on a create, which also means the rule is not applied.
The CSV export downloaded everything while a brand folder was selected The export action needs folderParam: folder to pass the selected path. A folder that does not exist yields an empty file, which is the honest answer rather than a full export in disguise.

MCP tools

Symptom Likely cause
None of the five tools is listed h1-mcp-api is not installed. It is an optional dependency: without it there is no McpToolRegistry, nobody reads h1McpTools/, and the rest of the module works unchanged.
seo_metadata refuses a content type Its app has no h1-seo-metadata decoration, so the h1seo* fields are not in its form. The guardrail exists because writing them there would leave properties that neither the editor shows nor the metadata endpoint serves.
Another content type's tool rejects an h1seo* field Correct behaviour: seo_metadata declares those fields as its own, so the content type's tool names it instead of discarding the value silently.
Creating a sitemap page fails saying one already exists Either the exact path exists, or a sibling with a different name already uses the same template. One per brand is enforced.
create refuses the brand marca must be a single segment with no / and no ., and the brand page must already exist in website as an mgnl:page.

General

Symptom Likely cause
Editing an item renamed its node and broke path references Magnolia derives the node name from the item's name property. A human-readable or i18n name renames the node on every edit.
Everything is fine on author and silently empty in production The public channels read as the anonymous user, except the redirects, which read in the system context. Check the ACLs in Permissions before looking at the code.
The CSV import command fails with a ClassNotFoundException after an upgrade The 1.0.2 delta did not run. The command stopped being a class of this module; the node has to be replaced, not merely bootstrapped when missing. Check config:/modules/h1-seo/commands/h1-seo/importRedirectsFromCsvclass must be com.habito1.helpers.csv.ui.CsvImportCommand.