| Install | |
|---|---|
composer require antikode/anti-cms-builder |
|
| PHP: | ^8.2 |
A powerful Laravel package for building dynamic CRUD interfaces with minimal boilerplate code. This package provides form builders, info lists, table builders, React components, and advanced custom field management to accelerate development while ensuring consistency across your application.
🚧 Development Status: This package is actively maintained and published to Packagist. While stable for production use, new features and improvements are added regularly. Please review the changelog when updating versions.
Note: This package is currently in active development. While published to Packagist, new features and breaking changes may be introduced frequently. Please check the changelog before updating.
composer require antikode/anticms-builder
If you want to use the latest development version with cutting-edge features:
composer.json:For HTTPS:
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/antikode/anticms-builder.git"
}
]
}
For SSH:
{
"repositories": [
{
"type": "vcs",
"url": "git@github.com:antikode/anticms-builder.git"
}
]
}
composer require antikode/anticms-builder:dev-main
The package will automatically register its service provider through Laravel's package discovery.
For full functionality, ensure you have these database structures:
Required for Custom Fields:
custom_fields table for storing custom field dataRequired for Multilingual Support:
translations table for multilingual contentRequired for Media Features:
Example Migration Structure:
// Custom fields table
Schema::create('custom_fields', function (Blueprint $table) {
$table->id();
$table->morphs('customfieldable');
$table->string('template')->nullable();
$table->json('data');
$table->unsignedBigInteger('parent_id')->nullable();
$table->integer('sort')->default(0);
$table->timestamps();
});
// Translations table (example)
Schema::create('translations', function (Blueprint $table) {
$table->id();
$table->morphs('translatable');
$table->string('locale');
$table->json('data');
$table->timestamps();
});
Optionally, you can publish the configuration file:
php artisan vendor:publish --tag=anti-cms-builder-config
This will create a config/anti-cms-builder.php file where you can customize:
return [
// Default models used by the package
'models' => [
'file' => 'App\\Models\\File',
'media' => 'App\\Models\\Media',
'translation' => 'App\\Models\\Translations\\Translation',
'custom_field' => 'App\\Models\\CustomField\\CustomField',
],
// Service implementations
'services' => [
'post' => 'App\\Services\\PostService',
'template' => 'App\\Services\\TemplateService',
'custom_field' => 'App\\Services\\CustomFieldService',
],
// Language settings
'default_language' => 'en',
'languages' => [
'en' => 'English',
'ar' => 'Arabic',
],
// File upload configuration
'uploads' => [
'disk' => 'public',
'path' => 'uploads',
],
];
Create a controller that uses the UseCrudController trait:
<?php
namespace App\Http\Controllers;
use AntiCmsBuilder\Forms\FormBuilder;
use AntiCmsBuilder\Tables\TableBuilder;
use AntiCmsBuilder\Traits\UseCrudController;
use AntiCmsBuilder\FieldTypes\InputField;
use AntiCmsBuilder\FieldTypes\SelectField;
use AntiCmsBuilder\Tables\Columns\TextColumn;
use App\Models\Product;
use Illuminate\Database\Eloquent\Builder;
class ProductController extends Controller
{
use UseCrudController;
protected string $model = Product::class;
public function forms(FormBuilder $builder): FormBuilder
{
return $builder->forms([
InputField::make()
->name('name')
->label('Product Name')
->placeholder('Enter product name')
->required()
->multilanguage()
->toArray(),
SelectField::make()
->name('category_id')
->label('Category')
->placeholder('Select category')
->loadOptionFromRelation('category', 'name')
->toArray(),
]);
}
public function tables(TableBuilder $builder): TableBuilder
{
return $builder
->query(fn (Builder $query) => $query->with('category'))
->columns([
TextColumn::make()
->name('name')
->searchable()
->sortable()
->toArray(),
TextColumn::make()
->name('category.name')
->label('Category')
->toArray(),
]);
}
}
Use the Route::crud() macro to register CRUD routes:
Route::crud('/products', ProductController::class, [
'middleware' => ['auth'],
'as' => 'admin.products'
]);
This generates all necessary CRUD routes:
GET /products - Index pageGET /products/create - Create formPOST /products - Store new recordGET /products/details/{id}/edit - Edit formPUT /products/details/{id}/update - Update recordDELETE /products/details/{id}/delete - Delete recordThe package includes various field types:
// Input Field
InputField::make()
->name('title')
->label('Title')
->type('text') // text, email, number, password, etc.
->required()
->multilanguage()
->toArray(),
// Textarea Field
TextareaField::make()
->name('description')
->label('Description')
->rows(5)
->multilanguage()
->toArray(),
// Rich Text Editor
TexteditorField::make()
->name('content')
->label('Content')
->multilanguage()
->toArray(),
// Select Field
SelectField::make()
->name('category_id')
->label('Category')
->loadOptionFromRelation('category', 'name')
->required()
->toArray(),
// Image Field
ImageField::make()
->name('featured_image')
->label('Featured Image')
->required()
->toArray(),
// Toggle Field
ToggleField::make()
->name('is_active')
->label('Active Status')
->default(true)
->toArray(),
// Repeater Field
RepeaterField::make()
->name('specifications')
->label('Product Specifications')
->fields([
InputField::make()->name('key')->label('Property')->toArray(),
InputField::make()->name('value')->label('Value')->toArray(),
])
->toArray(),
Enable multilingual support for any field:
InputField::make()
->name('title')
->multilanguage() // This field will support multiple languages
->toArray(),
Data is automatically stored in the translations table with language keys.
Add custom validation rules:
InputField::make()
->name('email')
->type('email')
->rules(['required', 'email', 'unique:users,email'])
->toArray(),
Customize save and update behavior:
public function forms(FormBuilder $builder): FormBuilder
{
return $builder
->forms([...])
->save(function (Request $request) {
// Custom save logic
$product = Product::create($request->validated());
// Additional processing...
return $product;
})
->afterSave(function ($record, $operation, $request) {
// Execute after save/update
if ($operation === 'create') {
// Send notification for new records
}
});
}
The package includes comprehensive tests covering all major functionality.
Run the full test suite:
composer test
Or run specific test groups:
# Run only unit tests
./vendor/bin/phpunit tests/Unit
# Run only feature tests
./vendor/bin/phpunit tests/Feature
# Run with coverage
./vendor/bin/phpunit --coverage-html coverage
Unit Tests: Test individual classes and methods
FieldTypes/: Tests for all field type classesTables/: Tests for table builder functionalityFeature Tests: Test complete workflows
UseCrudControllerTest.php: End-to-end CRUD operationsSupport Files: Test models, controllers, and migrations for testing environment
The package uses Laravel's testing framework. Test models and controllers are provided in tests/Support/ for testing custom functionality.
The package uses GitHub Actions for continuous integration. The CI workflow:
See .github/workflows/ci.yml for the complete configuration.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.
The AntiCmsBuilder package consists of three main components:
Handles dynamic form creation, validation, and data persistence for Laravel models. It provides a fluent API for building complex forms with support for multilingual content, relationships, custom fields, media handling, and advanced validation.
Enables the creation of queryable, filterable tables with customizable columns and sorting.
Provides reusable CRUD controller methods (index, create, store, edit, update, destroy) integrated with the builders.
Create a controller that uses the UseCrudController trait:
<?php
namespace App\Http\Controllers;
use AntiCmsBuilder\Forms\FormBuilder;
use AntiCmsBuilder\Tables\TableBuilder;
use AntiCmsBuilder\Traits\UseCrudController;
use AntiCmsBuilder\FieldTypes\InputField;
use AntiCmsBuilder\FieldTypes\SelectField;
use AntiCmsBuilder\Tables\Columns\TextColumn;
use App\Models\Product;
use Illuminate\Database\Eloquent\Builder;
class ProductController extends Controller
{
use UseCrudController;
protected string $model = Product::class;
public function forms(FormBuilder $builder): FormBuilder
{
return $builder->forms([
InputField::make()
->name('name')
->label('Product Name')
->placeholder('Enter product name')
->required()
->multilanguage()
->toArray(),
SelectField::make()
->name('category_id')
->label('Category')
->placeholder('Select category')
->loadOptionFromRelation('category', 'name')
->toArray(),
]);
}
public function tables(TableBuilder $builder): TableBuilder
{
return $builder
->query(fn (Builder $query) => $query->with('category'))
->columns([
TextColumn::make()
->name('name')
->searchable()
->sortable()
->toArray(),
TextColumn::make()
->name('category.name')
->label('Category')
->toArray(),
]);
}
}
Use the Route::crud() macro to register CRUD routes:
Route::crud('/products', ProductController::class, [
'middleware' => ['auth'],
'as' => 'admin.products'
]);
The Route::crud() method generates RESTful routes:
| HTTP Method | URL Path | Controller Method | Route Name |
|---|---|---|---|
| GET | /{url}/ | index | {$as}.index |
| GET | /{url}/create | create | {$as}.create |
| POST | /{url}/ | store | {$as}.store |
| GET | /{url}/details/{id}/edit | edit | {$as}.edit |
| PUT | /{url}/details/{id}/update | update | {$as}.update |
| DELETE | /{url}/details/{id}/delete | delete | {$as}.delete |
| DELETE | /{url}/details/{id}/force-delete | forceDelete | {$as}.delete.force |
| GET | /{url}/details/{id}/restore | restore | {$as}.restore |
make(string $model): selfCreates a new FormBuilder instance for the specified model.
$builder = FormBuilder::make(Product::class);
forms(array|callable $forms): selfDefines the form fields. Accepts either an array of field definitions or a callable that returns an array.
Array Usage:
$builder->forms([
InputField::make()->name('title')->toArray(),
TextareaField::make()->name('description')->toArray(),
]);
Callable Usage with Dependency Injection:
$builder->forms(function ($record, $operation) {
$fields = [
InputField::make()->name('title')->toArray(),
];
if ($operation === 'update') {
$fields[] = InputField::make()->name('slug')->disabled()->toArray();
}
return $fields;
});
Supported Injectable Parameters:
$record (Model|null): The current model instance$operation (string): Either 'create' or 'update'save(callable $save): selfDefines a custom save callback for create operations.
$builder->save(function (Request $request) {
$product = Product::create($request->validated());
// Custom logic after creation
$this->sendNotification($product);
return $product;
});
update(callable $update): selfDefines a custom update callback for update operations.
$builder->update(function (Request $request, Model $model) {
$model->update($request->validated());
// Custom logic after update
$this->logUpdate($model);
return $model;
});
afterSave(callable $afterSave): selfDefines a callback to execute after both save and update operations.
$builder->afterSave(function ($record, $operation, $request) {
if ($operation === 'create') {
// Logic for new records
$this->sendWelcomeEmail($record);
} else {
// Logic for updated records
$this->logUpdate($record);
}
});
Supported Injectable Parameters:
$record (Model): The saved/updated model instance$operation (string): Either 'create' or 'update'$request (Request): The HTTP request instanceAll fields follow this basic structure:
[
'field' => 'input', // Field type
'name' => 'field_name', // Field name/key
'label' => 'Field Label', // Display label
'attribute' => [ // Field attributes
'placeholder' => 'Enter value',
'required' => true,
'max' => 255,
// ... other attributes
],
'multilanguage' => true, // Enable multilingual support
]
InputField::make()
->name('title')
->label('Title')
->placeholder('Enter title')
->type('text') // text, email, number, password, etc.
->required()
->max(255)
->min(3)
->multilanguage()
->toArray()
TextareaField::make()
->name('description')
->label('Description')
->placeholder('Enter description')
->rows(5)
->required()
->max(1000)
->multilanguage()
->toArray()
TexteditorField::make()
->name('content')
->label('Content')
->placeholder('Enter rich content')
->multilanguage()
->toArray()
SelectField::make()
->name('category_id')
->label('Category')
->placeholder('Select category')
->loadOptionFromRelation('category', 'name')
->required()
->toArray()
SelectField::make()
->name('tags')
->label('Tags')
->multiple()
->loadOptionFromRelation('tags', 'name')
->toArray()
ImageField::make()
->name('featured_image')
->label('Featured Image')
->required()
->toArray()
ToggleField::make()
->name('is_active')
->label('Active Status')
->default(true)
->toArray()
RepeaterField::make()
->name('specifications')
->label('Product Specifications')
->fields([
InputField::make()->name('key')->label('Property')->toArray(),
InputField::make()->name('value')->label('Value')->toArray(),
])
->toArray()
SelectField::make()
->name('category_id')
->label('Category')
->loadOptionFromRelation('category', 'name')
->toArray()
SelectField::make()
->name('tags')
->label('Tags')
->multiple()
->loadOptionFromRelation('tags', 'name')
->toArray()
RepeaterField::make()
->name('variants')
->label('Product Variants')
->relation('variants') // HasMany relation
->fields([
InputField::make()->name('name')->label('Variant Name')->multilanguage()->toArray(),
InputField::make()->name('price')->label('Price')->type('number')->toArray(),
ImageField::make()->name('image')->label('Variant Image')->toArray(),
])
->toArray()
SelectField::make()
->name('category_id')
->label('Category')
->loadOptionFromRelation('category', 'name', function ($query) {
return $query->where('is_active', true)->orderBy('name');
})
->toArray()
InputField::make()
->name('title')
->label('Title')
->multilanguage() // Enable multilingual support
->toArray()
Multilingual fields are automatically stored in the translations table with the following structure:
// Database structure
translations: [
'en' => ['title' => 'English Title', 'description' => 'English Description'],
'ar' => ['title' => 'Arabic Title', 'description' => 'Arabic Description'],
]
FormBuilder automatically generates validation rules based on field attributes:
InputField::make()
->name('email')
->type('email')
->required()
->max(255)
->toArray()
// Generates: 'email' => ['required', 'email', 'max:255']
InputField::make()
->name('username')
->rules(['required', 'unique:users,username', 'min:3'])
->toArray()
InputField::make()
->name('title')
->multilanguage()
->required()
->toArray()
// Generates validation for each language:
// 'translations.en.title' => ['required']
// 'translations.ar.title' => ['required']
public function forms(FormBuilder $builder): FormBuilder
{
return $builder->forms([
// Basic Information
InputField::make()
->name('name')
->label('Product Name')
->placeholder('Enter product name')
->required()
->max(255)
->multilanguage()
->toArray(),
TextareaField::make()
->name('description')
->label('Description')
->placeholder('Enter product description')
->rows(5)
->max(1000)
->multilanguage()
->toArray(),
// Relationships
SelectField::make()
->name('category_id')
->label('Category')
->placeholder('Select category')
->loadOptionFromRelation('category', 'name')
->required()
->toArray(),
SelectField::make()
->name('tags')
->label('Tags')
->multiple()
->loadOptionFromRelation('tags', 'name')
->toArray(),
// Media
ImageField::make()
->name('featured_image')
->label('Featured Image')
->required()
->toArray(),
// Pricing
InputField::make()
->name('price')
->label('Price')
->type('number')
->step('0.01')
->min(0)
->required()
->toArray(),
// Status
ToggleField::make()
->name('is_active')
->label('Active')
->default(true)
->toArray(),
// Specifications (Repeater)
RepeaterField::make()
->name('specifications')
->label('Product Specifications')
->fields([
InputField::make()
->name('key')
->label('Property')
->required()
->multilanguage()
->toArray(),
InputField::make()
->name('value')
->label('Value')
->required()
->multilanguage()
->toArray(),
])
->toArray(),
]);
}
The InfoList system provides a powerful way to display structured information about your models in a clean, organized format. It supports various entry types, sections, custom formatting, and automatic data processing.
InfoLists are used to display model data in detail views, providing a structured way to present information with different entry types and sections. The system automatically handles data formatting, relationships, multilingual content, and conditional visibility.
Create an InfoList in your controller:
<?php
namespace App\Http\Controllers;
use AntiCmsBuilder\InfoLists\InfoListBuilder;
use AntiCmsBuilder\InfoLists\Section;
use AntiCmsBuilder\InfoLists\Entries\TextEntry;
use AntiCmsBuilder\InfoLists\Entries\BooleanEntry;
use AntiCmsBuilder\InfoLists\Entries\ImageEntry;
use AntiCmsBuilder\InfoLists\Entries\RelationshipEntry;
use AntiCmsBuilder\InfoLists\Entries\DateEntry;
use AntiCmsBuilder\Traits\UseCrudController;
use App\Models\Product;
class ProductController extends Controller
{
use UseCrudController;
protected string $model = Product::class;
public function infoList(InfoListBuilder $builder): InfoListBuilder
{
return $builder
->record($builder->record)
->sections([
Section::make('Product Information')
->entries([
TextEntry::make()
->name('name')
->label('Product Name')
->toArray(),
TextEntry::make()
->name('description')
->label('Description')
->limit(200)
->toArray(),
TextEntry::make()
->name('price')
->label('Price')
->format(fn ($value) => '$' . number_format($value, 2))
->toArray(),
])
->toArray(),
Section::make('Status & Availability')
->entries([
BooleanEntry::make()
->name('is_active')
->label('Active Status')
->trueLabel('Active')
->falseLabel('Inactive')
->toArray(),
])
->toArray(),
]);
}
}
Displays text content with optional formatting and character limits:
TextEntry::make()
->name('title')
->label('Title')
->limit(100) // Character limit
->copyable() // Add copy to clipboard functionality
->markdown() // Render as markdown
->html() // Render as HTML
->format(fn ($value) => strtoupper($value)) // Custom formatting
->toArray()
Displays boolean values with customizable labels and colors:
BooleanEntry::make()
->name('is_active')
->label('Status')
->trueLabel('Active')
->falseLabel('Inactive')
->trueColor('success')
->falseColor('danger')
->toArray()
Displays dates with customizable formatting:
DateEntry::make()
->name('created_at')
->label('Created Date')
->dateFormat('F j, Y g:i A') // Custom date format
->toArray()
Displays images with customizable dimensions and styling:
ImageEntry::make()
->name('featured_image')
->label('Featured Image')
->height(200)
->width(300)
->circular() // Display as circular image
->square() // Display with square aspect ratio
->toArray()
Displays related model data:
RelationshipEntry::make()
->name('category')
->label('Category')
->displayUsing('name') // Specify which column to display
->badge() // Display as a badge/tag
->toArray()
Group related entries into sections with titles, descriptions, and icons:
Section::make('Product Details')
->description('Basic product information')
->icon('heroicon-o-information-circle')
->collapsed(false) // Whether section is collapsed by default
->entries([
// Entry definitions...
])
->visible(fn ($record) => $record->is_published) // Conditional visibility
->toArray()
TextEntry::make()
->name('status')
->label('Status')
->state(fn ($record) => $record->getStatusLabel()) // Custom value retrieval
->format(fn ($value, $record) =>
"<span class='badge badge-{$record->status_color}'>{$value}</span>"
)
->html() // Render as HTML
->toArray()
TextEntry::make()
->name('category.parent.name') // Access nested relationships
->label('Parent Category')
->toArray()
RelationshipEntry::make()
->name('user.profile')
->label('Author Profile')
->displayUsing('display_name')
->toArray()
TextEntry::make()
->name('internal_notes')
->label('Internal Notes')
->visible(fn ($record) => auth()->user()->can('view-internal-notes'))
->hidden(fn ($record) => !$record->has_internal_notes)
->toArray()
public function infoList(InfoListBuilder $builder): InfoListBuilder
{
return $builder
->record($builder->record)
->sections([
// Basic Information Section
Section::make('Product Information')
->description('Basic product details and specifications')
->icon('heroicon-o-cube')
->entries([
TextEntry::make()
->name('name')
->label('Product Name')
->copyable()
->toArray(),
TextEntry::make()
->name('description')
->label('Description')
->limit(300)
->markdown()
->toArray(),
TextEntry::make()
->name('sku')
->label('SKU')
->copyable()
->toArray(),
TextEntry::make()
->name('price')
->label('Price')
->format(fn ($value) => $value ? '$' . number_format($value, 2) : '—')
->toArray(),
])
->toArray(),
// Status Section
Section::make('Status & Availability')
->entries([
BooleanEntry::make()
->name('is_active')
->label('Active Status')
->trueLabel('Active')
->falseLabel('Inactive')
->trueColor('success')
->falseColor('danger')
->toArray(),
BooleanEntry::make()
->name('in_stock')
->label('Stock Status')
->trueLabel('In Stock')
->falseLabel('Out of Stock')
->toArray(),
])
->toArray(),
// Relationships Section
Section::make('Relationships')
->entries([
RelationshipEntry::make()
->name('category')
->label('Category')
->displayUsing('name')
->badge()
->toArray(),
RelationshipEntry::make()
->name('user')
->label('Created By')
->displayUsing('name')
->toArray(),
])
->toArray(),
// Media Section
Section::make('Media')
->collapsed(true)
->entries([
ImageEntry::make()
->name('featured_image')
->label('Featured Image')
->height(200)
->square()
->toArray(),
])
->visible(fn ($record) => $record->featured_image)
->toArray(),
// Timestamps Section
Section::make('System Information')
->collapsed(true)
->entries([
DateEntry::make()
->name('created_at')
->label('Created Date')
->dateFormat('F j, Y g:i A')
->toArray(),
DateEntry::make()
->name('updated_at')
->label('Last Updated')
->dateFormat('F j, Y g:i A')
->toArray(),
])
->toArray(),
])
->entries([
// Global entries (displayed outside of sections)
TextEntry::make()
->name('slug')
->label('URL Slug')
->copyable()
->format(fn ($value) => url($value))
->toArray(),
]);
}
The InfoListBuilder automatically processes data based on entry types:
Access nested relationships and properties:
TextEntry::make()
->name('category.parent.name') // Nested relationship
->toArray()
TextEntry::make()
->name('translations.en.title') // Multilingual content
->toArray()
When using the UseCrudController trait, InfoLists are automatically integrated into the detail view. The system will:
infoList method to build the structureInfoLists work with your existing CSS framework. Entry types include semantic classes for styling:
.info-entry-text for text entries.info-entry-boolean for boolean entries.info-entry-image for image entries.info-entry-relationship for relationship entries.info-section for sections.info-section-collapsed for collapsed sectionspublic function tables(TableBuilder $builder): TableBuilder
{
return $builder
->query(fn (Builder $query) => $query->with(['category', 'user']))
->columns([
TextColumn::make()
->name('title')
->label('Title')
->searchable()
->sortable()
->description(fn ($record) => $record->slug)
->toArray(),
TextColumn::make()
->name('category.name')
->label('Category')
->toArray(),
TextColumn::make()
->name('created_at')
->label('Created Date')
->sortable()
->format(fn ($date) => $date->format('Y-m-d H:i'))
->toArray(),
])
->filters([
SelectField::make()
->name('category_id')
->label('Filter by Category')
->loadOptionFromRelation('category', 'name')
->query(fn (Builder $query, $value) =>
$query->where('category_id', $value)
)
->toArray(),
]);
}
The package includes pre-built React components for CRUD operations:
These components are automatically used when you use the UseCrudController trait and provide a complete CRUD interface with:
The package includes a powerful custom fields system that allows you to create template-based custom fields with hierarchical structure.
To enable custom fields on your model, implement the HasCustomField contract:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use AntiCmsBuilder\Contracts\HasCustomField;
use AntiCmsBuilder\Traits\CustomFields;
class Product extends Model implements HasCustomField
{
use CustomFields;
// Your model implementation
}
The HasCustomField contract requires two methods:
interface HasCustomField
{
public function customFields(): \Illuminate\Database\Eloquent\Relations\MorphMany;
public function getRootCustomFields(): Collection;
}
The CustomFields trait provides default implementations:
customFields(): Morphs to many custom field recordsgetRootCustomFields(): Returns root-level custom fields with their children, ordered by sort// In your FormBuilder
RepeaterField::make()
->name('custom_template')
->label('Custom Fields')
->template('product_specifications') // References a template
->toArray()
The custom field model can be configured in config/anti-cms-builder.php:
'models' => [
'custom_field' => 'App\\Models\\CustomField\\CustomField',
],
'services' => [
'custom_field' => 'App\\Services\\CustomFieldService',
],
The package includes an interactive CLI command for building JSON page structures:
php artisan page:build
This command provides an interactive interface to:
storage/app/json/pages/The command uses Laravel Prompts for a user-friendly CLI experience and integrates with the ComponentManager for component management.
$builder->forms(function ($record, $operation) {
$fields = [
InputField::make()->name('name')->toArray(),
];
// Add slug field only for updates
if ($operation === 'update') {
$fields[] = InputField::make()
->name('slug')
->disabled()
->toArray();
}
// Add status field only for existing records
if ($record && $record->exists) {
$fields[] = SelectField::make()
->name('status')
->options([
['value' => 'draft', 'label' => 'Draft'],
['value' => 'published', 'label' => 'Published'],
])
->toArray();
}
return $fields;
});
$builder->save(function (Request $request) {
// Custom save logic
$model = new Product();
$model->name = $request->name;
$model->slug = Str::slug($request->name);
$model->save();
return $model;
});
$builder->afterSave(function ($record, $operation, $request) {
// Send notifications
if ($operation === 'create') {
Mail::to($record->user)->send(new ProductCreated($record));
}
// Update search index
$record->searchable();
// Log activity
activity()
->performedOn($record)
->log($operation === 'create' ? 'Product created' : 'Product updated');
});
ImageField::make()
->name('featured_image')
->label('Featured Image')
->required()
->toArray()
The system automatically handles alt text for images:
// Request data structure
[
'featured_image' => 123, // File ID
'featured_image_alt' => 'Alt text' // Alt text (automatically handled)
]
RepeaterField::make()
->name('gallery')
->label('Image Gallery')
->fields([
ImageField::make()->name('image')->label('Image')->toArray(),
InputField::make()->name('caption')->label('Caption')->multilanguage()->toArray(),
])
->toArray()
When using models that implement HasCustomField, the system automatically handles custom field data:
// Data is automatically processed and stored in custom_fields relationship
// with proper hierarchical structure and template association
Create custom field types by extending the base FieldType class:
<?php
namespace AntiCmsBuilder\FieldTypes;
class CustomField extends FieldType
{
protected string $type = 'custom';
public function customMethod(): self
{
$this->attributes['custom_attribute'] = true;
return $this;
}
}
You can override default behavior in your controllers:
class ProductController extends Controller
{
use UseCrudController;
protected string $model = Product::class;
// Override default validation
protected function defaultValidation($data = null): array
{
return [
'custom_field' => 'required|string|max:255',
];
}
// Add custom logic after saving
public function afterSave($record, $request, $operation): void
{
// Custom logic here
if ($operation === 'create') {
// Do something after creating
}
}
// Custom status options
protected function statusOptions(): array
{
return [
['value' => 'draft', 'label' => 'Draft'],
['value' => 'published', 'label' => 'Published'],
];
}
}
anticms-builder/
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions CI workflow
├── config/
│ └── anti-cms-builder.php # Package configuration file
├── resources/
│ └── js/
│ ├── Components/
│ │ ├── fields/
│ │ │ ├── Builder.jsx # Core builder component
│ │ │ └── FieldBuilderComponent.jsx
│ │ ├── form/
│ │ │ └── CreateEditFormWithBuilder.jsx
│ │ └── Table/
│ │ ├── DynamicRowActions.jsx
│ │ └── DynamicTableActions.jsx
│ ├── Pages/
│ │ └── CRUD/
│ │ ├── Actions/
│ │ │ └── ActionBuilders.jsx
│ │ ├── Create.jsx
│ │ ├── Edit.jsx
│ │ ├── Index.jsx
│ │ └── TestPackagePage.jsx
│ └── utils/
│ └── resolverPage.js
├── src/
│ ├── Console/
│ │ └── Commands/
│ │ └── PageBuilderCommand.php
│ ├── Contracts/
│ │ ├── HasCustomField.php
│ │ ├── HasForm.php
│ │ └── HasMeta.php
│ ├── FieldTypes/
│ │ ├── Traits/
│ │ │ └── SelectOptionTrait.php
│ │ ├── CustomField.php
│ │ ├── FieldType.php
│ │ ├── FileField.php
│ │ ├── ImageField.php
│ │ ├── InputField.php
│ │ ├── MultiSelectField.php
│ │ ├── RepeaterField.php
│ │ ├── Section.php
│ │ ├── SelectField.php
│ │ ├── TextareaField.php
│ │ ├── TexteditorField.php
│ │ └── ToggleField.php
│ ├── Filters/
│ │ └── SelectField.php
│ ├── Forms/
│ │ └── FormBuilder.php
│ ├── Support/
│ │ └── Color.php
│ ├── Tables/
│ │ ├── Actions/
│ │ │ ├── BulkAction.php
│ │ │ ├── RowAction.php
│ │ │ └── TableAction.php
│ │ ├── Columns/
│ │ │ └── TextColumn.php
│ │ └── TableBuilder.php
│ ├── Traits/
│ │ ├── CustomFields.php
│ │ └── UseCrudController.php
│ ├── AntiCmsBuilderServiceProvider.php
│ ├── ComponentManager.php
│ ├── FieldManager.php
│ ├── FieldService.php
│ ├── Resolver.php
│ └── SortHelper.php
├── tests/
│ ├── Feature/
│ │ └── UseCrudControllerTest.php
│ ├── Support/
│ │ ├── migrations/
│ │ │ └── 2024_01_01_000000_create_test_models_table.php
│ │ ├── TestController.php
│ │ └── TestModel.php
│ ├── Unit/
│ │ ├── FieldTypes/
│ │ │ ├── FieldTypeTest.php
│ │ │ ├── InputFieldTest.php
│ │ │ └── SelectFieldTest.php
│ │ ├── Tables/
│ │ │ └── DynamicParameterTest.php
│ │ ├── FieldServiceTest.php
│ │ ├── FormBuilderTest.php
│ │ ├── ServiceProviderTest.php
│ │ └── TableBuilderTest.php
│ ├── README.md
│ └── TestCase.php
├── composer.json
├── phpunit.xml
└── README.md
HasCustomField and uses the CustomFields traitphp artisan vendor:publish --tag=anti-cms-builder-configphp artisan vendor:publish --tag=anti-cms-builder-resourcesdd($formBuilder->getForms()) to inspect form structure$formBuilder->getRules() for validation rulesMIT License
See CHANGELOG.md for recent changes and version history.
For issues and questions:
tests/ for usage examplesThis documentation covers the complete functionality of the AntiCmsBuilder package. For more specific use cases or advanced customizations:
src/ directoryresources/js/config/anti-cms-builder.php