therealworld / seo-module
Creates a sitemap.xml for products, categories, texts and other items from other modules in OXID7. Allows to set the title tags.
Package info
bitbucket.org/therealworld/seo-module
Type:oxideshop-module
pkg:composer/therealworld/seo-module
Requires
- php: ^8.3
- oxid-esales/oxideshop-ce: >=v7.4
- therealworld/scheduler-module: >=v2.2
- therealworld/tools-plugin: >=v3.7
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- v2.x-dev
- v2.4.0
- v2.3.5
- v2.3.4
- v2.3.3
- v2.3.2
- v2.3.1
- v2.3.0
- v2.2.1
- v2.2.0
- v2.1.8
- v2.1.7
- v2.1.6
- v2.1.5
- v2.1.4
- v2.1.3
- v2.1.2
- v2.1.1
- v2.1.0
- v2.0.18
- v2.0.17
- v2.0.16
- v2.0.15
- v2.0.14
- v2.0.13
- v2.0.12
- v2.0.11
- v2.0.10
- v2.0.9
- v2.0.8
- v2.0.7
- v2.0.6
- v2.0.5
- v2.0.4
- v2.0.3
- v2.0.2
- v2.0.1
- v2.0.0
- v1.7.23
- v1.7.22
- v1.7.21
- v1.7.20
- v1.7.19
- v1.7.18
- v1.7.17
- v1.7.16
- v1.7.15
- v1.7.14
- v1.7.13
- v1.7.12
- v1.7.11
- v1.7.10
- v1.7.9
- v1.7.8
- v1.7.7
- v1.7.6
- v1.7.5
- v1.7.4
- v1.7.3
- v1.7.2
- v1.7.1
- v1.7.0
- v1.6.6
- v1.6.5
- v1.6.4
- v1.6.3
- v1.6.2
- v1.6.1
- v1.6.0
- v1.5.5
- v1.5.4
- v1.5.3
- v1.5.2
- v1.5.1
- v1.5.0
- v1.4.6
- v1.4.5
- v1.4.4
- v1.4.3
- v1.4.2
- v1.4.1
- v1.4.0
- v1.3.7
- v1.3.6
- v1.3.5
- v1.3.4
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3.0
- v1.2.10
- v1.2.9
- v1.2.8
- v1.2.7
- v1.2.6
- v1.2.5
- v1.2.4
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2.0
- dev-master
This package is auto-updated.
Last update: 2026-10-25 14:42:05 UTC
README
Deutsche Fassung: README.de.md

description
The module takes care of what search engines see of the shop:
- sitemap.xml for articles, categories, CMS pages, manufacturers and vendors, built incrementally by a scheduler task, split into several files with an index file, optionally gzip-compressed, with language alternates and image links. Other modules add their own object types (e.g. news, events, people, ...).
- SEO title (
<title>) per article, category, CMS page, manufacturer and vendor, maintained in the admin's SEO tab or generated from a pattern when the object is saved. Meta description and keywords are generated from patterns as well. - Substance rule for thin pages: category, manufacturer and vendor pages without
enough sellable articles and without a description text of their own get
noindex, followand are left out of the sitemap. - Sold-out articles can answer with
410 Goneor404 Not Foundinstead of the core's redirect to the start page. - Structured data: article detail pages carry a schema.org
Productas JSON-LD.
| Composer package | therealworld/seo-module |
| Module id | trwseo |
| Requires | OXID eShop CE >=v7.4, PHP ^8.3, therealworld/tools-plugin >=v3.7, therealworld/scheduler-module >=v2.2 |
Installation
composer require therealworld/seo-module
vendor/bin/oe-console trw:module:setup trwseo
trw:module:setup (from therealworld/clirun-plugin) installs the module, runs its
migrations and activates it. Without the clirun-plugin use the standard OXID steps
(oe:module:install, the module migrations, oe:module:activate trwseo).
The migrations create the tables trwseositemapexport and trwseoupdatemeta and add the
columns OXISSITEMAP and OXSEOTITLE (_1, _2, _3) to oxarticles, oxcategories,
oxcontents, oxvendor and oxmanufacturers. On activation the DB views are regenerated.
The scheduler module (trwscheduler) must be active: the sitemap export and the meta
update run as its tasks.
The sitemap
How it is built
The task "sitemap.xml Export" (scheduler, default crontab 10 2 * * *, i.e. daily at
2:10 am) works in two phases:
- Collect: for every SEO model it writes the URLs into
trwseositemapexport. The table is kept between runs; only objects changed within the last "Update of the last days" are collected again. Rows of objects that no longer qualify (deleted, deactivated, excluded by a condition, thin, model switched off) are removed at the start of each run. If the table is empty, the run collects everything. - Write: the rows are written to
sitemap1.xml,sitemap2.xml, ... in the shop's document root. A file is closed at 50,000 entries or at the configured file size, andsitemap.xmlis written as the index pointing to these files. Numbered files left over from a larger earlier run are deleted.
With "Compress?" on, every file is additionally written as .gz and the index refers to
the .gz files.
Large shops do not need to finish in one scheduler tick: the task continues where it stopped.
What gets in
Per object type an object is exported when
- the type is switched on ("Active"),
- the object's own flag "sitemap.xml?" is set (SEO tab of the object in the admin; default for new columns: on for articles, categories and manufacturers, off for CMS pages and vendors),
- the optional "SQL-Condition" matches,
- for categories: it is not an external link (
OXEXTLINKempty), - for categories, manufacturers and vendors: it passes the substance rule (below).
Optionally the export adds alternate URLs for further languages (xhtml:link) and image
URLs of articles, categories, manufacturers and vendors (image:image).
Submit https://<shop>/sitemap.xml (or sitemap.xml.gz) to the search engines and/or
reference it in robots.txt; the module does not do that.
Thin pages: the substance rule
A category, manufacturer or vendor page is considered thin when both apply:
- it counts fewer sellable articles than the configured minimum
("... minimum articles for indexing", default
1), and - it has no description text of its own (categories: long description, manufacturers and vendors: short description).
A thin page
- renders
<meta name="robots" content="NOINDEX, FOLLOW" />, and - is not written into the sitemap.
Both use the same SQL statement, so page and sitemap cannot disagree. The rule is evaluated on every request; a page that gets articles again is indexable again immediately.
Details:
- Categories count the sellable articles of their whole subtree, so a parent node with filled subcategories is not thin.
- "Sellable" means: parent articles that pass the shop's active check.
0switches the rule off for that object type. Articles and CMS pages have no such rule (an article page is its own substance; sold-out articles are handled by the status code below).- Independent of the rule, every page after the first of a paged article list gets
NOINDEX, FOLLOW.
Sold-out articles
An article that still exists but cannot be bought any more (inactive, or out of stock with stock flag "not orderable"; for variants also a non-visible parent) is answered by the core with a redirect to the start page. To a search engine that is a soft 404. "Articles no longer available" changes that:
| Value | Effect |
|---|---|
| Redirect to the start page (default) | core behaviour, unchanged |
| 410 Gone | status 410 with the shop's 404 page; right for single copies, search engines drop the URL fastest |
| 404 Not Found | status 404 with the shop's 404 page; when the article may come back |
The error page template message/err_404 receives blTRWSeoGone (true for 410), so a theme
can word the sold-out case differently.
SEO title, meta description, keywords
- The SEO tab of articles, categories, CMS pages, manufacturers and vendors gets two fields: "SEO Meta Title" and "sitemap.xml?".
- A filled SEO title replaces the page's
<title>. An empty or broken one (only the fixed text of the pattern, e.g." nur hier!", or only separators like" - ") falls back to the regular title. - When an object is saved and its SEO title is empty or broken, it is generated from the
pattern of the group "Metadata Defaults" (one line per language,
de => ...). Meta description and keywords (oxobject2seodata) are only generated when they are empty. - Placeholders:
%titleeverywhere; articles and categories also%shortdescand%longdesc, articles also%keywords(search keys); manufacturers, vendors and CMS pages%desc. Further placeholders come from other modules: if you need e.g. the authors or brand names of an article in its title, a module can replace its own placeholder (say%author) inreplaceAdditonalSeoContent()on the article chain (see Further hooks).
Meta update task
The task "Update Metadata" has no default crontab and is started manually in the scheduler (Service > Task-Scheduler). It saves every object of every SEO model in every language again, so missing SEO titles, descriptions and keywords are generated. Its behaviour is controlled by the group "Metadata Update" (see below).
CLI: repair broken SEO titles
vendor/bin/oe-console trw:seo:repairtitles [--dry-run] [--model=oxarticles] [--limit=N]
Finds stored SEO titles that are broken (see above) and generates them again from the
pattern; objects with nothing to build from get an empty SEO title (and fall back to the
regular title). Asks for confirmation, writes the column directly (no save()), and empties
the page cache afterwards.
--dry-run: only report--model: only this SEO model--limit: at most this many titles per model and language
Structured data (JSON-LD)
Article detail pages get a schema.org Product in <head>: name (max. 150 characters),
description, existing pictures, article number (sku), gtin13 (from EAN/ISBN, only with a
valid check digit) and an Offer with gross price, currency, availability and URL.
- Shipping details (
shippingDetails) only appear when a shipping module fills the delivery price provider of the tools-plugin (e.g.trwshipping). - The return policy (
hasMerchantReturnPolicy) only appears when a withdrawal module fills the merchant return policy provider of the tools-plugin.
Configuration
Extensions > Modules > the-real-world - SEO > Settings
Sitemap Product / Categories / Content-Pages / Vendors / Manufacturers
The same four options exist per object type:
| Option | Default | Effect |
|---|---|---|
| Active | on | Write this object type into the sitemap at all. Off removes its rows on the next run. |
| Changefrequency | daily | <changefreq> of the URLs |
| URL-Priority | 0.5 | <priority> of the URLs |
| SQL-Condition | empty (CMS pages: OXFOLDER="CMSFOLDER_USERINFO") | Additional conditions on the object table, FIELD=value, several separated by \|, e.g. OXHIDDEN=0\|OXSHOWSUFFIX=1. Only equality, combined with AND. |
Additionally:
| Group | Option | Default | Effect |
|---|---|---|---|
| Sitemap Product | Articles no longer available | Redirect to the start page | see Sold-out articles |
| Sitemap Categories | Categories: minimum articles for indexing | 1 | see Thin pages; 0 = off |
| Sitemap Manufacturers | Manufacturers: minimum articles for indexing | 1 | as above |
| Sitemap Vendors | Vendors: minimum articles for indexing | 1 | as above |
additional Sitemap-Options
| Option | Default | Effect |
|---|---|---|
| Index languages | none | Further languages (besides the default language) whose URLs are added as alternates. Multi-select. |
| Index Images? | on | Add image URLs (articles, categories, manufacturers, vendors) |
Export
| Option | Default | Effect |
|---|---|---|
| Number of positions that are processed per tick | 10 | Rows per batch in collect and write; lower it if the server struggles |
| Size of the XML files | 10MB | Size at which a sitemap file is closed and the next one started (5MB or 10MB) |
| Compress? | on | Write .gz files additionally and reference them in the index |
| Update of the last days | 7 | Objects changed within this many days are collected again on each run. A large value (e.g. 99999) forces a full re-export. |
Metadata Defaults
Patterns for SEO title, meta description and meta keywords of categories, articles,
manufacturers, vendors and CMS pages (15 options), one line per language (de => ...,
en => ...). Defaults e.g. article title %title nur hier! / %title only here!,
category title Beste Angebote für %title / Best deals for %title. A pattern only applies
when the field is still empty (or the title is broken) at save time.
Metadata Update
Affects the manual task "Update Metadata" only.
| Option | Default | Effect |
|---|---|---|
| Clear metadata before manual update? | off | Empty SEO title, description and keywords first, so all are generated anew |
| Update SEO URLs during meta update? | off | Also renew the SEO URLs: non-fixed URLs are marked as expired (the shop builds them again on the next access); fixed URLs are skipped |
| Delete SEO URLs before update? | off | Only with the option above: delete the URLs and their history (oxseo, oxseohistory) instead of expiring them |
Logging
The tasks write info entries ("Data collected for type ...", "sitemap.xml created ...",
removed obsolete files) and errors (sitemap file not writable, no index written) via the
tools-plugin's ToolsLog.
Limits and caveats
- The sitemap files are written into the shop's document root; it must be writable.
- SQL-Condition only supports
FIELD=valuepairs; values are compared as strings with AND. - A full re-export happens when the export table is empty, or with a large "Update of the last days".
- The substance rule needs the scheduler run to update the sitemap; the robots tag changes immediately.
For developers
Chain extensions
- Controllers:
ArticleDetailsController(status code, SEO title, JSON-LD),ArticleListController(isNoIndex(), SEO title, canonical URL — also covers manufacturer and vendor lists),ContentController,ManufacturerListController,VendorListController(SEO title). - Admin:
ArticleSeo,CategorySeo,ContentSeo,ManufacturerSeo,VendorSeo(fieldsoxseotitle,oxissitemap),ModuleConfiguration(language multi-select). - Models:
Article,Category,Content,Manufacturer,Vendor(pattern-based meta data onsave(),setSaveSeoData(false)switches it off for bulk imports).
Templates / blocks
layout/base.html.twig(all frontend themes): blockhead_meta_robotsoutputsNOINDEX, FOLLOWwhen the view setsisNoIndex; blockhead_link_canonicalappends the JSON-LD ondetails.- Admin:
object_seo.html.twig(blockadmin_object_seo_form) andmodule_config.html.twig(blockadmin_module_config_var_type_arr).
Adding an SEO model from another module
SeoSitemapGenerateData and SeoMetaGenerateData are registered as module classes and can
be extended through the chain (e.g. by a module that brings news or event pages):
- Append the table name to the hidden setting
arrTRWSeoAdditionalSeoModels(and toarrTRWSeoAdditionalObjectsWithImagesfor images), typically in the module'sonActivate. - Provide the settings
boolTRWSeoActive<model>,strTRWSeoChangeFreq<model>,strTRWSeoPriority<model>,strTRWSeoSQLCondition<model>and optionallynumTRWSeoNoIndexMinItems<model>in the own module; they are read with the model name as module id. - Override
getExternList()andgetExternNameTimeStampField()(sitemap),getWantedIdsQuery()(stale-row cleanup;nullleaves rows alone) andgetSeoTitleModel()(meta update,trw:seo:repairtitles).
Extending the substance rule
SeoSitemapGenerateData::getSeoSubstanceScoreParts() returns named SQL expressions that are
summed and compared against the threshold; getSeoSubstanceBase() names the view of a
model. Add a part (e.g. the number of articles linked to a person), or a base for a new model. A part that should
be enough on its own weighs as much as the threshold (see buildTextScorePart()).
hasSeoSubstance($model, $id) answers the question for a single object and is what the
frontend uses — call it from an own detail controller to set isNoIndex.
Further hooks
SeoTitleModelInterface+ traitSeoTitleBuilder: generated SEO titles for own models;replaceAdditonalSeoContent(string)on a model fills own placeholders.Article::extendSeoStructuredData(array): arrayon the article chain adds JSON-LD fields (e.g. a module for second-hand goods can setoffers.itemConditiontohttps://schema.org/UsedCondition).
Database
trwseositemapexport: collected sitemap rows (incremental)trwseoupdatemeta: work list of the meta update task- Columns
OXISSITEMAP,OXSEOTITLE[_1|_2|_3]on the five core tables;down()keeps them on purpose. - Migrations in
migration/data, tableoxmigrations_trwseo.