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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/redaxo-core:redaxo-addon-developmentThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Addons live under `redaxo/src/addons/<addon_name>/`. Every addon must have a `package.yml`; everything else is optional but follows conventions.
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/.
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
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.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub friendsofredaxo/claude-marketplace --plugin redaxo-core