| name | structure-articles |
| description | Working with REDAXO articles via rex_article – fetching, listing, status, dates, URLs, and the start/notfound/site articles. Use when the user reads or modifies article data, builds article lists for templates, queries the structure tree, or handles per-language article variants. |
rex_article
rex_article represents a single article (page) in the structure tree. Articles live inside categories and inherit from a category template unless they override it.
Each article exists once per language: rex_article::get($id, $clang) returns a different instance for each clang, but they share the structural metadata (parent, priority, name).
Fetching
$article = rex_article::getCurrent();
$article = rex_article::get($id);
$article = rex_article::get($id, $clangId);
$start = rex_article::getSiteStartArticleId();
$notFound = rex_article::getNotfoundArticleId();
Always check for null:
$article = rex_article::get($id);
if (!$article) {
rex_logger::factory()->log('warning', "Article $id not found");
return;
}
Common getters
$article->getId();
$article->getName();
$article->getCategoryId();
$article->getStatus();
$article->getTemplateId();
$article->getCreateDate();
$article->getUpdateDate();
$article->getCreateUser();
$article->getUpdateUser();
$article->getValue('art_yrewrite_title');
$article->getUrl();
$article->getPath();
$article->isOnline();
$article->isStartArticle();
$article->isSiteStartArticle();
$article->isNotFoundArticle();
Listing articles in a category
$categoryId = 5;
$onlyOnline = true;
$articles = rex_article::getArticlesOfCategory($categoryId, $onlyOnline);
foreach ($articles as $article) {
echo '<li><a href="' . $article->getUrl() . '">'
. rex_escape($article->getName()) . '</a></li>';
}
To include offline articles (e.g. in admin tools), pass false. The result is already sorted by priority.
Listing across categories – use rex_sql
For more complex filters (date range, custom meta field, cross-category), drop down to SQL:
$sql = rex_sql::factory();
$sql->setQuery(
'SELECT id FROM ' . rex::getTable('article') . '
WHERE clang_id = :clang
AND status = 1
AND createdate >= :since
ORDER BY createdate DESC
LIMIT 10',
['clang' => rex_clang::getCurrentId(), 'since' => '2026-01-01']
);
$articles = [];
foreach ($sql as $row) {
if ($a = rex_article::get((int) $row->getValue('id'))) {
$articles[] = $a;
}
}
Article in a different language
$article = rex_article::getCurrent();
$en = rex_article::get($article->getId(), 1);
if ($en && $en->isOnline()) {
echo 'EN version: ' . $en->getUrl();
}
This is how language switchers know whether the current page even has a translation.
Programmatically creating / updating articles
Use rex_article_service, not direct SQL inserts. It handles path, priority, slug generation, and triggers ART_ADDED / ART_UPDATED extension points so other addons (yrewrite, search) get notified.
$data = [
'name' => 'New article',
'category_id' => 5,
'priority' => 1,
'template_id' => 1,
'clang' => rex_clang::getCurrentId(),
];
$articleId = rex_article_service::addArticle($data);
Editing:
rex_article_service::editArticle($articleId, rex_clang::getCurrentId(), [
'name' => 'Renamed',
'template_id' => 2,
]);
Deleting (removes all language variants):
rex_article_service::deleteArticle($articleId);
Common pitfalls
- Caching a
rex_article instance and using it after a different language's request – mix-up between clangs. Always re-fetch with the right clang.
- Iterating thousands of articles via
rex_article::get() in a loop – each call does a lookup. For bulk reads, use SQL and rex_article::get() only for items you actually display.
- Modifying
rex_article table directly via SQL – breaks the cache. Always go through rex_article_service.
- Forgetting that
getValue('field') only returns metadata (from meta_info-managed columns), not slice content. Slice content is fetched via rex_article_content (see structure-content skill).