Skip to main content

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/

ContentExample
Compiled CSSpublic/css/addon.css
Compiled JavaScriptpublic/js/addon.js
Images and iconspublic/images/logo.png
Web fontspublic/fonts/custom-font.woff2
Downloadable filespublic/downloads/sample.csv
Third-party librariespublic/plugins/library/lib.min.js

What Does NOT Go in public/

ContentCorrect Location
SASS/SCSS source filesResources/assets/sass/
Uncompiled JavaScriptResources/assets/js/
Blade templatesResources/views/
PHP filesAnywhere except public/
ConfigurationConfig/
Environment secrets.env or Config/
Security

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 to public/.
  • public/ -- Ready-to-serve files that the browser loads directly. Either hand-written CSS/JS or compiled output from Resources/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": []
}
FieldDescription
nameAddon name in PascalCase, must match directory name
aliasLowercase identifier used in views, config, and routes
descriptionBrief description of the addon's purpose
keywordsSearch keywords for the addon marketplace
priorityLoad order (lower numbers load first)
providersFully qualified service provider class names
filesPHP files to autoload (helpers, functions)
requiresOther 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
}

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

TypeConventionExample
Addon directoryPascalCaseEmailValidator
Aliaslowercaseemailvalidator
ControllersPascalCase + ControllerSettingsController
ModelsSingular PascalCaseValidationRule
Migrationssnake_case with date prefix2024_01_01_create_rules_table
Viewskebab-casevalidation-settings.blade.php
Config keysdot notationemailvalidator.api_key

Best Practices

  1. Keep public/ minimal -- Only place files that the browser needs to load directly
  2. Use Resources/assets/ for source files -- SASS, ES6, TypeScript belong here
  3. Group by feature -- For large addons, organize controllers and views by feature
  4. Keep controllers thin -- Move business logic to service classes
  5. Namespace properly -- All classes must be under Addons\YourAddonName\
  6. Never hardcode paths -- Use module_path() and asset() helpers