Custom Page Modules
This page describes the recommended implementation sequence for custom content modules.
Step 0 - Scaffold with the generator
Tagixo ships an artisan generator that scaffolds the module class + companion Blade view in one command:
php artisan make:tagixo-module AlertBox --context=page,mail,pdf --icon=heroicon-o-exclamation-triangle
By default the file lands at app/tagixo/Modules/AlertBox.php with namespace App\Tagixo\Modules, and a Blade companion view at resources/views/tagixo/modules/alert-box.blade.php. Adjust the destination globally via config('tagixo.scaffolding'):
// config/tagixo.php
'scaffolding' => [
'path' => 'app/tagixo', // change to relocate everywhere
'namespace' => null, // null = derived from path (capitalize each segment)
],
Override per-command with --path= + --namespace= (both required together), or scaffold directly into a third-party plugin you are developing with --package=path/to/plugin (the command reads the plugin's composer.json PSR-4 map and places the file accordingly).
After scaffolding, the command prints a copy-pasteable registration snippet for config/tagixo.php (or for the plugin's service provider in package mode).
You can of course still write the class by hand — the steps below explain the shape the generator produces, and what you'll customize next.
Step 1 - Define the module class
Use the core module abstraction and return a ModuleDefinition.
use Ccast\Tagixo\Components\Module;
use Ccast\Tagixo\Core\DefaultsBuilder;
use Ccast\Tagixo\Core\ModuleDefinition;
use Ccast\Tagixo\Core\Props\TextProp;
class AlertBoxModule extends Module
{
public static function getLabel(): string { return __('Alert Box'); }
public static function getIcon(): string { return 'heroicon-o-exclamation-triangle'; }
public static function define(): ModuleDefinition
{
return ModuleDefinition::make()
->content(
TextProp::make('title')->default('Alert'),
TextProp::make('message')->default('Your message')
)
->design('typography', 'spacing', 'background', 'border')
->defaults([
...DefaultsBuilder::background()->color('#fff7ed'),
...DefaultsBuilder::border()->width(1)->style('solid')->color('#fdba74')->radius(8),
])
->contexts(['page', 'mail', 'pdf']);
}
}
Your module class is responsible for:
- identity
- schema
- defaults
- contexts
- optional sub-elements
- optional child constraints
Step 2 - Create Blade output view
Provide a stable module view for renderer output.
Keep the Blade view simple and deterministic. Do not move builder logic into the view.
Typical responsibilities of the view:
- read content props
- emit semantic HTML
- expose stable class names/selectors for styling
If the module needs context-specific wrappers, keep that decision in PHP, not in ad-hoc frontend conditionals.
Step 3 - Add builder preview renderer
If your frontend builder requires module-specific preview logic, add the Vue renderer in your module mapping.
Not every module needs custom preview code. If the server-rendered output is enough, keep the preview path simple.
Step 4 - Register the module
Config-based:
// config/tagixo.php
'modules' => [
App\Tagixo\Modules\AlertBoxModule::class,
],
Runtime registration is also supported.
Step 5 - Context and child constraints
Set context scope explicitly and define child rules when needed:
allowsChildren()getAllowedChildren()
Container-style modules should declare their children intentionally. Do not leave this ambiguous.
Step 6 - Sub-elements
If the module exposes stylable inner parts, define them through subElements(...).
Use sub-elements for cases such as:
- title vs description
- card shell vs badge
- repeated item label/value pairs
- button label vs button container
Sub-elements keep nested styling explicit instead of burying it in undocumented CSS selectors.
Step 7 - Verify end-to-end
- appears in bootstrap payload
- appears in picker in expected contexts
- saves/loads correctly
- renders HTML/CSS correctly
- survives preview + save + reload cycles
- behaves correctly in every declared context
Migration safety
Never change module type IDs after content exists in production unless you provide migration scripts for stored JSON.
Recommended module implementation checklist
Before shipping a custom module, verify:
- defaults are complete
- the view renders without undefined indexes
- context restrictions are correct
- design PropTypes are intentional
- sub-elements are documented if used
- saved JSON remains backward-compatible