Directory Structure
Every addon follows a standardized directory structure that mirrors Laravel's application layout. Understanding this structure is essential for organizing your code effectively.
Complete Structure
Addons/YourAddonName/
├── module.json # Addon manifest (required)
├── composer.json # PHP dependencies
├── package.json # NPM dependencies
├── webpack.mix.js # Asset compilation
├── functions.php # Lifecycle functions
├── menu.json # Navigation menu
│
├── Config/ # Configuration files
│ └── config.php
│
├── Console/ # Artisan commands
│ └── Commands/
│ └── YourCommand.php
│
├── Database/
│ ├── Migrations/ # Database migrations
│ │ └── 2024_01_01_create_table.php
│ └── Seeders/ # Data seeders
│ └── YourAddonSeeder.php
│
├── Helpers/ # Helper functions
│ └── helper.php
│
├── hooks/ # Hook implementations
│ └── hooks.php
│
├── Http/
│ ├── Controllers/ # Request handlers
│ │ └── YourController.php
│ ├── Middleware/ # HTTP middleware
│ │ └── YourMiddleware.php
│ └── Requests/ # Form validation
│ └── YourFormRequest.php
│
├── Jobs/ # Queue jobs
│ └── YourJob.php
│
├── Listeners/ # Event listeners
│ └── YourListener.php
│
├── Models/ # Eloquent models
│ └── YourModel.php
│
├── Providers/ # Service providers
│ ├── YourAddonServiceProvider.php
│ ├── EventServiceProvider.php
│ └── RouteServiceProvider.php
│
├── public/ # Web-accessible static assets
│ ├── css/ # Compiled CSS files
│ │ └── addon.css
│ ├── js/ # Compiled JavaScript files
│ │ └── addon.js
│ ├── images/ # Images, icons, SVGs
│ │ └── logo.png
│ └── fonts/ # Web fonts
│
├── Resources/
│ ├── assets/ # Source assets (NOT public)
│ │ ├── js/
│ │ │ └── app.js
│ │ └── sass/
│ │ └── app.scss
│ ├── lang/ # Translations
│ │ └── en/
│ │ └── app.php
│ └── views/ # Blade templates
│ ├── layouts/
│ └── index.blade.php
│
├── Routes/ # Route definitions
│ ├── web.php # Web routes
│ └── api.php # API routes
│
├── Rules/ # Validation rules
│ └── CustomRule.php
│
├── Settings/ # Addon settings
│ ├── config.php # Addon metadata
│ ├── install/ # Installation SQL
│ │ └── v1.0.sql
│ └── uninstall/ # Uninstall SQL
│ └── uninstall.sql
│
└── Tests/ # Test files
├── Feature/
└── Unit/
The public/ Directory
The public/ directory is the only part of your addon that is web-accessible. When the addon is enabled, a symlink is created:
public/Addons/YourAddonName/ → Addons/YourAddonName/public/
This means a file at Addons/YourAddonName/public/css/addon.css is accessible in the browser at /Addons/YourAddonName/css/addon.css.
What Goes in public/
| Content | Example |
|---|---|
| Compiled CSS | public/css/addon.css |
| Compiled JavaScript | public/js/addon.js |
| Images and icons | public/images/logo.png |
| Web fonts | public/fonts/custom-font.woff2 |
| Downloadable files | public/downloads/sample.csv |
| Third-party libraries | public/plugins/library/lib.min.js |
What Does NOT Go in public/
| Content | Correct Location |
|---|---|
| SASS/SCSS source files | Resources/assets/sass/ |
| Uncompiled JavaScript | Resources/assets/js/ |
| Blade templates | Resources/views/ |
| PHP files | Anywhere except public/ |
| Configuration | Config/ |
| Environment secrets | .env or Config/ |
Never place PHP files, configuration files, API keys, or any sensitive data in the public/ directory. Everything in public/ is directly accessible from the browser.
Resources/assets/ vs public/
These two directories serve different purposes:
Resources/assets/-- Source files that need compilation (SASS, ES6 JavaScript, TypeScript). These are processed by Laravel Mix/Vite and the compiled output goes topublic/.public/-- Ready-to-serve files that the browser loads directly. Either hand-written CSS/JS or compiled output fromResources/assets/.
Resources/assets/sass/app.scss → (Laravel Mix) → public/css/addon.css
Resources/assets/js/app.js → (Laravel Mix) → public/js/addon.js
If you're writing plain CSS/JS without a build step, place them directly in public/.
Core Files
module.json
The addon manifest file. This is required for the addon to be recognized by the system.
{
"name": "YourAddonName",
"alias": "youraddonname",
"description": "What your addon does",
"keywords": [],
"priority": 0,
"providers": [
"Addons\\YourAddonName\\Providers\\YourAddonNameServiceProvider"
],
"files": [
"Helpers/helper.php"
],
"requires": []
}
| Field | Description |
|---|---|
name | Addon name in PascalCase, must match directory name |
alias | Lowercase identifier used in views, config, and routes |
description | Brief description of the addon's purpose |
keywords | Search keywords for the addon marketplace |
priority | Load order (lower numbers load first) |
providers | Fully qualified service provider class names |
files | PHP files to autoload (helpers, functions) |
requires | Other addon names this addon depends on |
composer.json
Defines PHP dependencies and PSR-4 autoloading. Addon composer.json files are automatically merged with the main application's dependencies via wikimedia/composer-merge-plugin.
{
"name": "vendor/youraddonname",
"autoload": {
"psr-4": {
"Addons\\YourAddonName\\": ""
}
},
"require": {
"guzzlehttp/guzzle": "^7.0"
}
}
After adding dependencies, run composer update from the project root.
functions.php
Contains lifecycle functions called during install, update, and uninstall:
<?php
function install_addon($addon_name)
{
// Run during addon installation
// Execute SQL files, setup initial data
}
function update_addon($addon_name)
{
// Run during addon update
// Apply incremental changes
}
function uninstall_addon($addon_name)
{
// Run during addon uninstall
// Cleanup data if needed
}
menu.json
Defines sidebar navigation for your addon. See Configuration for details.
Directory Details
Config/
Configuration files that can be published to the main config/ directory.
// Config/config.php
return [
'name' => 'Your Addon',
'api_endpoint' => env('YOUR_ADDON_API_URL', 'https://api.example.com'),
'cache_ttl' => 3600,
];
Access in code:
config('youraddonname.api_endpoint');
Console/
Artisan commands for CLI operations and scheduled tasks.
namespace Addons\YourAddonName\Console;
use Illuminate\Console\Command;
class SyncCommand extends Command
{
protected $signature = 'youraddon:sync';
protected $description = 'Sync data with external service';
public function handle()
{
$this->info('Syncing...');
// Your logic here
}
}
Register in your service provider:
$this->commands([
\Addons\YourAddonName\Console\SyncCommand::class,
]);
Database/Migrations/
Database schema changes. Migrations are auto-discovered and run during php artisan migrate.
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (!Schema::hasTable('your_addon_table')) {
Schema::create('your_addon_table', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('name');
$table->timestamps();
});
}
}
public function down(): void
{
Schema::dropIfExists('your_addon_table');
}
};
Models/
Eloquent models for your addon's database tables.
namespace Addons\YourAddonName\Models;
use Illuminate\Database\Eloquent\Model;
class YourModel extends Model
{
protected $table = 'your_addon_table';
protected $fillable = ['user_id', 'name'];
public function user()
{
return $this->belongsTo(\App\Models\User::class);
}
}
Helpers/
Global helper functions. Add the file path to the files array in module.json to autoload:
// Helpers/helper.php
if (!function_exists('your_addon_helper')) {
function your_addon_helper($value)
{
return strtoupper($value);
}
}
hooks/
Hook implementations for extending core functionality. See Hooks Integration.
// hooks/hooks.php
add_hook('AddContact', 5, function ($vars) {
$contact = $vars['contact'];
// React to contact addition
});
Providers/
Service providers that bootstrap your addon:
- YourAddonServiceProvider.php -- Main provider; loads hooks, views, config, migrations
- RouteServiceProvider.php -- Loads route files with middleware
- EventServiceProvider.php -- Registers event listeners
Routes/
Route definitions for web and API endpoints:
// Routes/web.php - Web routes with session/auth middleware
// Routes/api.php - Stateless API routes
Settings/
Addon metadata and SQL scripts for the addon manager:
Settings/
├── config.php # Addon metadata displayed in admin panel
├── install/
│ ├── v1.0.sql # Initial install SQL
│ └── v1.1.sql # Version 1.1 migrations
└── uninstall/
└── uninstall.sql # Cleanup SQL
Naming Conventions
| Type | Convention | Example |
|---|---|---|
| Addon directory | PascalCase | EmailValidator |
| Alias | lowercase | emailvalidator |
| Controllers | PascalCase + Controller | SettingsController |
| Models | Singular PascalCase | ValidationRule |
| Migrations | snake_case with date prefix | 2024_01_01_create_rules_table |
| Views | kebab-case | validation-settings.blade.php |
| Config keys | dot notation | emailvalidator.api_key |
Best Practices
- Keep
public/minimal -- Only place files that the browser needs to load directly - Use
Resources/assets/for source files -- SASS, ES6, TypeScript belong here - Group by feature -- For large addons, organize controllers and views by feature
- Keep controllers thin -- Move business logic to service classes
- Namespace properly -- All classes must be under
Addons\YourAddonName\ - Never hardcode paths -- Use
module_path()andasset()helpers