From redaxo-core
Builds and maintains custom REDAXO addons including package.yml, boot.php, install/update/uninstall scripts, lang files, fragments, backend pages, assets pipeline with reinstall-sync and cache-busting, plus Composer vendor dependencies.
npx claudepluginhub friendsofredaxo/claude-marketplace --plugin redaxo-coreThis skill uses the workspace's default tool permissions.
Addons live under `redaxo/src/addons/<addon_name>/`. Every addon must have a `package.yml`; everything else is optional but follows conventions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Executes ctx7 CLI to fetch up-to-date library documentation, manage AI coding skills (install/search/generate/remove/suggest), and configure Context7 MCP. Useful for current API refs, skill handling, or agent setup.
Share bugs, ideas, or general feedback.
Addons live under redaxo/src/addons/<addon_name>/. Every addon must have a package.yml; everything else is optional but follows conventions.
redaxo/src/addons/my_addon/
├── package.yml # Manifest – required
├── boot.php # Runs on every request after the addon is enabled
├── install.php # Runs once on install (DB schema, defaults)
├── uninstall.php # Runs on uninstall (clean up)
├── update.php # Runs on version upgrade
├── lang/
│ ├── de_de.lang
│ └── en_gb.lang
├── pages/ # Backend pages (one PHP file per subpage)
├── fragments/ # Reusable view fragments
├── lib/ # Your PHP classes (autoloaded)
├── functions/ # Procedural helpers (manually included)
└── assets/ # Public files (mirrored to assets/addons/my_addon/)
package: my_addon
version: '1.0.0'
author: 'Your Name'
supportpage: https://example.com
requires:
redaxo: '^5.18'
php: '>=8.1'
# Optional: addons your addon needs to be installed
requires_addons:
yform: '^4.0'
# Backend page registration
page:
title: 'My Addon'
icon: rex-icon fa-cube
perm: my_addon[]
subpages:
list:
title: 'List'
settings:
title: 'Settings'
perm: my_addon[settings]
# Default config values (read via $addon->getConfig('key'))
default_config:
items_per_page: 25
feature_enabled: 0
The perm: my_addon[] line creates a permission you can grant to user roles in the backend. my_addon[] = the addon as a whole; my_addon[settings] = a fine-grained permission.
Runs on every request once the addon is enabled. Register extension points and load helpers here.
<?php
$addon = rex_addon::get('my_addon');
// Vendor dependencies (composer.json + vendor/ in your addon) are auto-loaded
// by REDAXO. No manual `require __DIR__ . '/vendor/autoload.php'` needed.
// Note: this only works once the addon is INSTALLED — pre-install code that
// touches vendor/ classes will silently no-op.
// Register fragment directory
rex_fragment::addDirectory($addon->getPath('fragments/'));
if (rex::isBackend() && rex::getUser()) {
rex_view::addCssFile($addon->getAssetsUrl('backend.css'));
}
// Hook into the system
rex_extension::register('OUTPUT_FILTER', [my_addon_filter::class, 'apply']);
// Optional: include procedural helpers
require_once __DIR__ . '/functions/helpers.php';
$this inside boot.php also refers to the rex_addon instance, so $this->getAssetsUrl(...) works the same as $addon->getAssetsUrl(...).
Classes under lib/ are autoloaded automatically: lib/my_addon_filter.php → class my_addon_filter.
Throw a rex_functional_exception to abort with a user-visible error. Use rex_sql_table::ensure() for idempotent schema changes.
<?php
// install.php
rex_sql_table::get(rex::getTable('my_addon_items'))
->ensurePrimaryIdColumn()
->ensureColumn(new rex_sql_column('title', 'varchar(255)', false, ''))
->ensureColumn(new rex_sql_column('data', 'json', false))
->ensureColumn(new rex_sql_column('created_at', 'datetime', false))
->ensureIndex(new rex_sql_index('title', ['title'], rex_sql_index::UNIQUE))
->ensure();
// Set defaults
rex_config::set('my_addon', 'items_per_page', 25);
<?php
// uninstall.php
rex_sql_table::get(rex::getTable('my_addon_items'))->drop();
rex_config::removeNamespace('my_addon');
<?php
// update.php – runs after the new files are in place
$installed = rex_addon::get('my_addon')->getVersion();
if (rex_version::compare($installed, '1.1.0', '<')) {
rex_sql_table::get(rex::getTable('my_addon_items'))
->ensureColumn(new rex_sql_column('priority', 'int', false, '0'))
->ensure();
}
REDAXO does not serve files from src/addons/<addon>/assets/ directly. On install, those files are copied to the public path /assets/addons/<addon>/. Editing source files does not update the public version.
After editing any file in assets/:
echo "y" | redaxo/bin/console package:install my_addon
This re-syncs assets and re-runs install.php (which is why install.php must be idempotent). Always use this command instead of manually copying files.
Always append ?v=<version> to asset URLs in fragments / templates / backend pages so browsers don't serve stale JS/CSS:
<script src="<?= $addon->getAssetsUrl('script.js') ?>?v=<?= $addon->getVersion() ?>"></script>
In boot.php for backend assets:
rex_view::addCssFile($addon->getAssetsUrl('backend.css') . '?v=' . $addon->getVersion());
rex_view::addJsFile($addon->getAssetsUrl('backend.js') . '?v=' . $addon->getVersion());
// Save
rex_config::set('my_addon', 'key', $value);
// Read
$value = rex_addon::get('my_addon')->getConfig('key');
// Defaults via package.yml `default_config:` block (see above)
Unchecked checkboxes don't send a value, so the post-handler must coerce to int:
// pages/config.php
$addon = rex_addon::get('my_addon');
if (rex_post('save', 'bool')) {
$addon->setConfig('feature_enabled', rex_post('feature_enabled', 'int', 0));
echo rex_view::success('Settings saved.');
}
// Form
'<input type="checkbox" name="feature_enabled" value="1"
' . ($addon->getConfig('feature_enabled') ? 'checked' : '') . ' />';
// In package.yml default_config use 0/1, not true/false
pages/<subpage>.php)Each subpage from package.yml resolves to pages/<subpage>.php. The file outputs the page body; REDAXO wraps the chrome.
<?php
$content = '<fieldset><legend>Settings</legend>';
// ... form HTML ...
$content .= '</fieldset>';
$fragment = new rex_fragment();
$fragment->setVar('title', rex_i18n::msg('my_addon_title'), false);
$fragment->setVar('body', $content, false);
$fragment->setVar('buttons', $buttons ?? '', false);
echo $fragment->parse('core/page/section.php');
Use rex_be_controller::getCurrentPagePart(N) to inspect the current subpage path if you need conditional logic.
my_addon_title = My Addon
my_addon_item_saved = Item “{0}” has been saved.
echo rex_i18n::msg('my_addon_title');
echo rex_i18n::msg('my_addon_item_saved', $title); // {0} replaced
Always prefix every key with the addon name to avoid collisions.
Addons can ship their own composer.json + vendor/ directory. REDAXO autoloads vendor classes on install — no manual require needed.
vendor/ — changes get lost on composer update.When building a widget that gets embedded on third-party sites:
localStorage (no cookies needed for cross-domain).<script src=".../widget.js?v=2.3.1">./assets/addons/.rex_sql API → SQL injection risk, no table prefix handling.$published = true on rex_api_function class → 403 from frontend.rex_config instead.rex::getTable() → missing table prefix breaks multi-instance setups.boot.php.exit after rex_response::sendJson() → REDAXO appends HTML to JSON.<style> or <script> tags → blocked by CSP. Always external files via rex_view::addCssFile() / rex_view::addJsFile().install.php without checks – the user may re-install.rex_url::addonAssets() – breaks subdirectory deployments.perm: declarations – any backend user can then access the page.// Log exceptions (preferred way)
rex_logger::logException(new rex_exception('Something went wrong'));
rex_logger::logException($exception);
// logError requires 4 params; prefer logException
// rex_logger::logError($errno, $errstr, $errfile, $errline);
// Verbose error output for developers
if (rex::isDebugMode()) {
// ...
}
// Dump (uses Symfony VarDumper if debug addon is active)
dump($variable);
For CLI/diagnostic tasks, see the redaxo-console-commands skill — never write standalone bootstrap scripts under bin/.