redaxo/source
REDAXO is an easy-to-learn, flexible open-source CMS with content built from custom modules and full control over input and output. Multilingual, highly extendable, and adaptable to your workflow, it has been powering websites since 2004 and is backed by a friendly, helpful community.
REDAXO is a full PHP CMS, not a Composer library you require into a Laravel app — so the first decision is integration mode: run it as a standalone app and talk to it over HTTP/JSON, or share its database. Plan for the former.
Fastest orientation path
demo_base — it's the only demo fully translated to English and it walks through templates, modules, categories, and the media pool. That's ~30 minutes to a working mental model.REX_ARTICLE[], and each module has an input form and an output fragment. That pairing is 80% of day-to-day work.From source (per the repo README)
git clone https://github.com/redaxo/core redaxo && cd redaxo
composer install
docker-compose up -d # REDAXO_PORT=8080 docker-compose up -d if :80 is taken
The default branch is 5.x; PRs target it. PHP 8.3+ is required.
Where to look first
redaxo/src/core/CHANGELOG.md — the release notes are unusually detailed and are the de-facto upgrade guide. They're written in German, but they name every changed class/method.redaxo/src/addons/ — structure, mediapool, metainfo, media_manager, cronjob, backup, be_style, install are all first-party addons. They are the best real-world reference for the addon API, extension points, and backend pages.First use case to build: a two-column page template plus your own module (e.g. a teaser grid) rendered through REX_ARTICLE[]. If you're Laravel-side, the equivalent first task is a tiny rex_api_* JSON endpoint addon that Laravel fetches and caches.
Content modeling via modules and templates. Templates are layouts with placeholders; modules pair an input form (backend) with an output fragment (frontend). Keep markup in modules, not templates, so editors can reorder. Escape output with rex_escape($value, 'html') / 'html_attr'.
Extension points instead of core edits. rex_extension::register('OUTPUT_FILTER', $callback) (and PACKAGES_INCLUDED, ART_INIT, etc.) is the service-provider analogue. A boot.php per addon is where you wire these. Never patch redaxo/src/core — changes vanish on core update.
Addon/plugin skeleton as the unit of packaging. package.yml + boot.php + install.php + uninstall.php + pages/ + lib/. This is how you ship anything reusable across projects, and it keeps composer check green.
Schema-as-code with rex_sql_table. There is no artisan migrate. The pattern is:
rex_sql_table::get(rex::getTable('teaser'))
->ensurePrimaryIdColumn()
->ensureColumn(new rex_sql_column('title', 'varchar(255)'))
->ensure();
Call it from install.php, not on every request. It's idempotent, and it is the idiomatic migration mechanism.
Data access. rex_sql::factory() for raw queries; rex_article::get($id), rex_category::get($id), rex_article_slice for content. These objects go through the instance pool — build on them rather than writing parallel ORM layers, or you'll get cache coherency bugs.
Headless integration with Laravel. Expose a namespaced endpoint addon returning JSON (rex_api_* classes or a custom page with rex_response::json()), scoped by rex_clang for multilingual sites. Cache on the Laravel side; use the shared DB only as a last resort, since it couples you to internals that change between 5.x minors.
Scheduled work via the cronjob addon. Register jobs through the addon's API rather than system cron entries pointing at arbitrary PHP — it gives you logging, backend visibility, and safe-mode behavior for free.
Media handling. Use mediapool types plus media_manager effects for derived images. Don't hand-roll thumbnails; the effect pipeline handles WebP/AVIF and the per-type caching.
Multilingual discipline. rex_clang appears everywhere — queries, URLs, permission checks. Always carry the clang id explicitly rather than assuming the current one, especially in API endpoints.
Quality gates if you contribute back. composer check runs cs (rector + php-cs-fixer, fixes in place), sa (PHPStan + Psalm), phpunit, and taint. composer baseline regenerates analysis baselines.
NOASSERTION, the README badge says MIT, and LICENSE.md exists. Verify LICENSE.md before using it in a client deliverable.?safemode=1 bookmarks and support instructions no longer work — a real annoyance mid-debug.use_directory_protection_check config option disables it — set it off in dev and CI.rex_sql_table::ensure() was over-eager on MySQL vs MariaDB. Column type and default normalization differed, so ensure re-issued ALTER on every call. Fixed in 5.21.5; pin at least that version, and never call ensure() at runtime.rex_article::get($nonexistent) no longer re-hits the DB each time — good for performance, but a stale null persists for the request. Don't assume a miss is a fresh lookup.rex_csrf_token::factory($scope) plus a check on every write, and escape everything at output.rex_file::mimeType() is PHP-version sensitive. Newer PHP returns text/javascript; 5.21.5 normalizes it to application/javascript. Pass the original filename if provided, and never hardcode MIME comparisons.rex_api_package now enforces admin. Scripts that deactivated/reactivated addons through that endpoint with a demoted session will start failing — correctly.OUTPUT_FILTER can double-escape or clobber each other's <head> output. Pick a narrow EP and document your ordering assumption.rex::isDebugMode(), then the logs under redaxo/data/log/ (rex_logger). Backend pages you can't reach are usually a permission-scope problem, not a fatal.composer psalm:no-cache exists for a reason — Psalm's cache goes stale after refactors and produces phantom errors.backup[export] only get REDAXO-prefixed tables, excluding rex_user, rex_user_passkey, rex_user_session. Any custom export tooling must replicate those restrictions or it becomes a credential-leak vector.How can I help you explore Laravel packages today?