Basic Components
This guide covers the basic components that make up a Botble CMS plugin.
Database
Migrations
After generating a plugin, it will create a first migration file in the database/migrations
directory. You should modify this file before activating the plugin. When activating a plugin, its migrations will run automatically.
Example:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class () extends Migration {
public function up(): void
{
Schema::create('foo_items', function (Blueprint $table) {
$table->id();
$table->string('name', 255);
$table->string('description', 400)->nullable();
$table->longText('content')->nullable();
$table->string('status', 60)->default('published');
$table->integer('user_id')->unsigned();
$table->string('image', 255)->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('foo_items');
}
};
Migrations are used to create and modify database tables. Each migration file should:
- Have a descriptive name that indicates what it does
- Include both
up()
anddown()
methods - Use the Schema builder to create or modify tables
- Follow Laravel's migration conventions
Seeders
Seeders are used to populate your database with test data. They are located in the database/seeders
directory.
Example:
<?php
namespace Botble\Foo\Database\Seeders;
use Botble\Base\Supports\BaseSeeder;
use Botble\Foo\Models\Item;
class FooSeeder extends BaseSeeder
{
public function run(): void
{
Item::truncate();
for ($i = 1; $i <= 20; $i++) {
Item::create([
'name' => 'Sample Item ' . $i,
'description' => 'This is a sample item',
'content' => 'Detailed content for sample item ' . $i,
'status' => 'published',
'user_id' => 1,
]);
}
}
}
Helpers
Helpers are utility functions and constants that can be used throughout your plugin.
constants.php
This file defines all PHP constants for your plugin. It must have a constant for its screen name.
Example:
if (!defined('FOO_MODULE_SCREEN_NAME')) {
define('FOO_MODULE_SCREEN_NAME', 'foo');
}
The module screen name constant is used to identify your plugin in various parts of the system, such as when registering custom fields or hooks.
helpers.php
This file contains utility functions specific to your plugin.
Example:
if (!function_exists('foo_get_items')) {
/**
* Get a list of items
*
* @param int $limit
* @param array $with
* @return \Illuminate\Database\Eloquent\Collection
*/
function foo_get_items(int $limit = 10, array $with = [])
{
return Item::query()
->where('status', 'published')
->orderBy('created_at', 'DESC')
->with($with)
->limit($limit)
->get();
}
}
Helper functions should:
- Be wrapped in a
!function_exists()
check to avoid conflicts - Have descriptive names prefixed with your plugin name
- Include proper PHPDoc comments
- Use dependency injection through the service container when possible
Resources
The resources directory contains assets, language files, and views for your plugin.
Language Files
Language files contain translations for your plugin's text. They are located in the resources/lang/{locale}
directory.
Example (resources/lang/en/foo.php
):
return [
'name' => 'Foo',
'create' => 'New Item',
'edit' => 'Edit Item',
'items' => [
'name' => 'Items',
'create' => 'Create new item',
'edit' => 'Edit item',
'list' => 'List items',
'menu' => 'Items',
'settings' => 'Settings',
'form' => [
'name' => 'Name',
'description' => 'Description',
'content' => 'Content',
'image' => 'Image',
'status' => 'Status',
],
],
];
To support multiple languages, create additional language files in the appropriate directories. For example, for Vietnamese support, create resources/lang/vi/foo.php
.
Views
Views contain the HTML for your plugin's pages. They are located in the resources/views
directory.
Example (resources/views/create.blade.php
):
{!! Form::open(['route' => 'foo.create']) !!}
<div class="row">
<div class="col-md-9">
<div class="card">
<div class="card-body">
<div class="form-group mb-3">
<label for="name">{{ trans('core/base::forms.name') }}</label>
<input type="text" class="form-control" name="name" id="name" value="{{ old('name') }}" placeholder="{{ trans('core/base::forms.name_placeholder') }}">
</div>
<div class="form-group mb-3">
<label for="description">{{ trans('core/base::forms.description') }}</label>
<textarea class="form-control" rows="4" name="description" id="description" placeholder="{{ trans('core/base::forms.description_placeholder') }}">{{ old('description') }}</textarea>
</div>
<div class="form-group mb-3">
<label for="content">{{ trans('core/base::forms.content') }}</label>
{!! Form::customEditor('content', old('content')) !!}
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-body">
<div class="form-group mb-3">
<label for="status">{{ trans('core/base::tables.status') }}</label>
{!! Form::customSelect('status', \Botble\Base\Enums\BaseStatusEnum::labels(), old('status')) !!}
</div>
<div class="form-group mb-3">
<label>{{ trans('core/base::forms.image') }}</label>
{!! Form::mediaImage('image', old('image')) !!}
</div>
</div>
</div>
<div class="mt-3">
<button class="btn btn-primary" type="submit">{{ trans('core/base::forms.create') }}</button>
<button class="btn btn-danger" type="reset">{{ trans('core/base::forms.reset') }}</button>
</div>
</div>
</div>
{!! Form::close() !!}
Assets
Assets include CSS, JavaScript, and image files for your plugin. They are located in the resources/assets
directory.
Example structure:
resources/assets/
├── css/
│ └── foo.css
├── js/
│ └── foo.js
└── images/
└── logo.png
To publish these assets to the public directory, add the following to your service provider's boot()
method:
$this->publishes([
__DIR__ . '/../../resources/assets' => public_path('vendor/foo'),
], 'foo-assets');
Routes
Routes define the URLs for your plugin's pages. They are located in the routes
directory.
Web Routes
Web routes are defined in routes/web.php
and are used for browser-accessible pages.
Example:
<?php
use Botble\Base\Facades\BaseHelper;
use Illuminate\Support\Facades\Route;
use Botble\Foo\Http\Controllers\FooController;
Route::group(['namespace' => 'Botble\Foo\Http\Controllers', 'middleware' => ['web', 'core']], function () {
Route::group(['prefix' => BaseHelper::getAdminPrefix(), 'middleware' => 'auth'], function () {
Route::group(['prefix' => 'foo', 'as' => 'foo.'], function () {
Route::resource('', 'ItemController')->parameters(['' => 'item']);
Route::delete('items/destroy', [
'as' => 'deletes',
'uses' => 'ItemController@deletes',
'permission' => 'foo.destroy',
]);
Route::get('settings', [
'as' => 'settings',
'uses' => 'FooController@getSettings',
'permission' => 'foo.settings',
]);
Route::post('settings', [
'as' => 'settings.update',
'uses' => 'FooController@postSettings',
'permission' => 'foo.settings',
]);
});
});
// Public routes (no auth required)
if (defined('THEME_MODULE_SCREEN_NAME')) {
Route::group([
'prefix' => 'foo',
'as' => 'public.foo.',
], function () {
Route::get('', [
'as' => 'index',
'uses' => 'PublicController@index',
]);
Route::get('{slug}', [
'as' => 'detail',
'uses' => 'PublicController@detail',
]);
});
}
});
API Routes
API routes are defined in routes/api.php
and are used for API endpoints.
Example:
<?php
use Illuminate\Support\Facades\Route;
Route::group([
'prefix' => 'api/v1',
'namespace' => 'Botble\Foo\Http\Controllers\API',
'middleware' => ['api'],
], function () {
Route::get('items', [
'as' => 'api.foo.index',
'uses' => 'ItemController@index',
]);
Route::get('items/{id}', [
'as' => 'api.foo.show',
'uses' => 'ItemController@show',
]);
});
Routes should be organized by:
- Authentication requirements (auth vs. public)
- Purpose (admin vs. public)
- HTTP method (GET, POST, etc.)
- Resource type (following RESTful conventions)