Create a Model
This guide shows two ways to scaffold a new model for WNCMS:
- Use Laravel’s built-in Artisan generators
- Use the WNCMS helper command
wncms:create-model(recommended for backend CRUD)
The result is a local model that extends WNCMS’s BaseModel, plus an admin controller, migration, views, permissions, and routes.
Before you start
- Ensure WNCMS is installed and autoloaded.
- Confirm
routes/custom_backend.phpis included by yourroutes/web.php(WNCMS does this by default). - Decide your model’s singular and plural names. Example in this doc:
Article/articles.
Option A — Laravel built-in generators
1) Generate files
php artisan make:model Article -m
php artisan make:controller Backend/ArticleController --resource --model=Article2) Update the model to extend BaseModel
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Wncms\Models\BaseModel;
class Article extends BaseModel
{
use HasFactory;
protected $guarded = [];
// Optional: tag types used by this model
// public const TAG_TYPES = ['article_category', 'article_tag'];
// Optional: casts, translations, etc.
// protected $casts = ['published_at' => 'datetime'];
// protected $translatable = ['title', 'excerpt', 'content'];
}3) Make the backend controller extend WNCMS BackendController
<?php
namespace App\Http\Controllers\Backend;
use Wncms\Http\Controllers\Backend\BackendController;
class ArticleController extends BackendController
{
// Usually no need to override anything for basic CRUD.
// You can customize policies, validation, columns, etc.
}4) Create a migration
Edit the generated migration (example columns):
Schema::create('articles', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('website_id')->nullable()->index(); // multisite support
$table->string('title');
$table->string('slug')->unique();
$table->text('excerpt')->nullable();
$table->longText('content')->nullable();
$table->timestamp('published_at')->nullable();
$table->timestamps();
});Run it:
php artisan migrate5) Register routes (if not using the WNCMS command below)
Append to routes/custom_backend.php:
<?php
use App\Http\Controllers\Backend\ArticleController;
Route::get('articles', [ArticleController::class, 'index'])->middleware('can:article_index')->name('articles.index');
Route::get('articles/create', [ArticleController::class, 'create'])->middleware('can:article_create')->name('articles.create');
Route::get('articles/create/{id}', [ArticleController::class, 'create'])->middleware('can:article_clone')->name('articles.clone');
Route::get('articles/{id}/edit', [ArticleController::class, 'edit'])->middleware('can:article_edit')->name('articles.edit');
Route::post('articles/store', [ArticleController::class, 'store'])->middleware('can:article_create')->name('articles.store');
Route::patch('articles/{id}', [ArticleController::class, 'update'])->middleware('can:article_edit')->name('articles.update');
Route::delete('articles/{id}', [ArticleController::class, 'destroy'])->middleware('can:article_delete')->name('articles.destroy');
Route::post('articles/bulk_delete', [ArticleController::class, 'bulk_delete'])->middleware('can:article_bulk_delete')->name('articles.bulk_delete');6) Create backend views
WNCMS expects resources/views/backend/{plural_snake}/:
resources/views/backend/articles/
├─ index.blade.php
├─ create.blade.php
└─ edit.blade.phpName your routes and views using the plural snake case (e.g., backend.articles.index) to match WNCMS conventions.
7) Add permissions and menu (optional but recommended)
- Create permissions like
article_index,article_create, etc. using your preferred method or a small seeder. - Add a backend menu item pointing to
route('articles.index').
Option B — WNCMS quick command (recommended)
Use the all-in-one WNCMS command to generate model, migration, controller, views, permissions, and routes. It also normalizes snake/camel names inside the generated controller.
php artisan wncms:create-model ArticleWhat it does:
make:model Articlemake:migration create_articles_tablemake:controller Backend/ArticleController --resource --model=Article- Rewrites view names (
backend.articles.*), route names (articles.*), cache tags, and model word helpers to snake case
- Rewrites view names (
wncms:create-model-view articleto createindex/create/editviewswncms:create-model-permission articleto seed basic permissionsOptionally appends a full routes block to
routes/custom_backend.phpand prepends theusestatement for the controller
You will see a confirmation prompt before routes are appended.
Run migration:
php artisan migrateAfter scaffolding
Translation keys
If you use BaseModel::getModelName() or wncms_model_word('article'), add translations:
resources/lang/en/wncms.php
'word' => [
'article' => 'Article',
'articles' => 'Articles',
],Or package-scoped keys if you later move this into a package.
Tag types (optional)
If your model needs tags, declare:
public const TAG_TYPES = ['article_category', 'article_tag'];WNCMS will auto-register them at model boot.
Media & translations (optional)
To use media library or translations:
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Wncms\Translatable\Traits\HasTranslations;
class Article extends BaseModel implements HasMedia
{
use InteractsWithMedia;
use HasTranslations;
protected $translatable = ['title', 'excerpt', 'content'];
}Common tips
- Keep controller names pluralized and placed under
App\Http\Controllers\Backend\. - Use plural snake for route and view names (e.g.,
articles.index) to match WNCMS helpers and stubs. - Include
website_idin migrations if your site uses multisite. - Clear caches when adding menus or permissions if they’re cached.
- Follow WNCMS’s naming: columns like
status, timestamps, and slugs make integration smoother.
Quick checklist
- Model extends
Wncms\Models\BaseModel - Backend controller extends
Wncms\Http\Controllers\Backend\BackendController - Migration created and migrated
- Views under
resources/views/backend/{plural}/ - Routes registered to
routes/custom_backend.php - Permissions created and assigned
- Optional:
TAG_TYPES, translations, media collections, and translatable fields added