Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Catchadmin Laravel Package

jaguarjack/catchadmin

Laravel-based admin panel framework for rapid backend development, with built-in RBAC permissions, modular architecture, CRUD code generation, and a Vue front end. Ready-made user, role, menu, and department modules help teams scaffold secure, extensible admin apps quickly.

View on GitHub
Deep Wiki
Context7

Getting Started

CatchAdmin is a modular admin-system framework built on Laravel + Vue 3, not a Composer library you require into an existing app. Day-to-day work happens in two sibling directories: modules/ (backend) and web/ (frontend). Anything you do outside those two trees is plain Laravel and will not get permissions, dynamic routing, or the table/form conventions for free.

First 30 minutes

  1. Clone the repo, then run the install skill path: php artisan catch:install (this bootstraps DB, admin user, and module registration). Use composer run dev to bring up the combined backend/asset dev loop.
  2. Register and install modules before touching them: php artisan catch:module:install --all, then per-module php artisan catch:migrate {Module} and php artisan catch:db:seed {Module}.
  3. Orient yourself in this order:
    • modules/Permissions/ and modules/System/ — read these first, they are the canonical reference for controllers, CatchModel usage, and routes/route.php style.
    • modules/{Module}/Installer.php — how a module announces itself.
    • web/src/views/{module}/ — the frontend counterpart, one directory per module in lowerCamelCase.
    • web/src/components/catchTable/ and web/src/composables/curd/ — the table and CRUD primitives every list page uses.
  4. Build the frontend contract early: php artisan catch:build produces the assets that public/ serves, so a stale build is the #1 cause of "my backend change did nothing."

First real use case: ship one CRUD module end to end

Don't start by hand-writing a controller. Start from a table definition and let the generator produce the vertical slice, then edit the generated files. The pipeline is table → migration → model → controller → route.php → menu → Vue page.

Table: products
Fields:
- name (string, 100, required)
- category_id (foreign key -> categories)
- price (decimal 10,2, required)
- stock (integer, default 0)
- status (tinyint, default 1)

Naming is derived, so getting the input right saves rework: {table} = products (snake plural), {Model} = Product, {Module} = Product, {module} = product, {resources} = products (kebab plural), {resource} = product.

Then, for anything the generator can't scaffold — a new menu, an extra migration, a bespoke model:

php artisan catch:make:migration Product add_sku_to_products
php artisan catch:make:model Product Product products
php artisan catch:make:menu Product ProductController "商品管理"

Your first meaningful edit after generation is almost always $searchable / $fields / $form on the model — that single class drives list filtering, column selection, and form persistence.

Implementation Patterns

Model-as-configuration

Catch\Base\CatchModel is the workhorse. Instead of writing query scopes and request-validated field lists per controller, you declare intent on the model and the base controller honours it.

class Product extends CatchModel
{
    protected $table = 'products';

    public array $searchable = [
        'name'     => 'like',
        'status'   => '=',
        'category_id' => '=',
    ];

    protected array $fields = ['id', 'name', 'price', 'stock', 'status', 'created_at'];

    protected array $form = ['name', 'category_id', 'price', 'stock', 'status'];

    protected string $sortField = 'id';
    protected bool $sortDesc = true;

    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class, 'category_id');
    }
}

Supported search operators are like, =, in, between — keep the map small and explicit rather than trying to express joins through it. For many-to-many form submissions use $formRelations = ['roles', 'tags'] so the base layer syncs pivots for you. For tree data (categories, departments) set $parentIdColumn and $asTree = true rather than building recursion in the controller.

Thin controllers, framework-owned responses

Backend controllers receive parameters, coordinate the flow, and return data. Do not wrap in response()->json(['code' => 0, ...]) — the unified envelope is applied automatically, and hand-rolling it breaks the frontend's catch-table and CURD composables.

class ProductController extends Controller
{
    public function __construct(private readonly ProductService $service) {}

    public function index(): mixed
    {
        return Product::getList();   // search/sort/paginate applied by CatchModel
    }
}

Business logic goes in Models/, Support/, or a service class — not in the controller. Constructor injection with readonly is the house style.

Routes: one resource line

Always check modules/{Module}/routes/route.php before adding anything, and match the existing style. New admin resources default to:

Route::adminResource('products', ProductController::class);

This is what wires the route into the permission system and the dynamic frontend menu. A route declared with the plain Laravel Route::resource will work in Postman and then fail authorization in the browser — a classic time sink.

Frontend: compose, don't build tables by hand

List pages use catch-table; forms reuse the CURD composables. The conventions that matter:

  • Page lives at web/src/views/{module}/index.vue, written as <script setup lang="ts"> with explicit interface types (TS is strict).
  • Dialog form: {resource}/form/create.vue. Full-page form: {resource}/create.vue.
  • Primary key is passed to form components as the primary prop — not id, not row.
  • Prefer useCreate / useShow over ad-hoc axios calls; they already handle the response envelope, validation errors, and refresh.
  • Admin components in web/src/components/admin/ are auto-imported — do not add manual imports for them.

Cross-cutting features you get for free

Token auth, RBAC/permissions, dynamic routing, pagination wrapper, attachments/uploads, data recycle bin, and Excel import/export are already in the framework. The pattern is to use them: put import classes in modules/{Module}/Excel/Import/, export classes in Excel/Export/, and let the resource-permission layer guard the controller. Building a parallel implementation is the main way developers accidentally fork the framework.

Where shared code goes

Promote to web/src/components, web/src/composables, or web/src/support only when a capability is reused by two or more modules. Single-module helpers stay inside the module.

Gotchas and Tips

Timestamps will bite you. CatchAdmin uses unsignedInteger Unix timestamps and an integer deleted_at, not Laravel's timestamps() / softDeletes(). The standard block on every table:

$table->unsignedInteger('creator_id')->default(0);
$table->unsignedInteger('created_at')->default(0);
$table->unsignedInteger('updated_at')->default(0);
$table->unsignedInteger('deleted_at')->default(0);

Run Laravel's $table->timestamps() instead and date filtering, sorting, and the recycle bin silently misbehave — usually with no error, just empty result sets.

Enums start at 1, and status is fixed. 1 = Enabled, 2 = Disabled. Starting enums at 0 collides with "unset" defaults and breaks generator-produced filters. Index foreign keys ($table->unsignedBigInteger('category_id')->default(0)->index()); the framework does not add them for you.

Module registration is three steps, not one. A new module needs its Installer.php, a service provider under Providers/, and an install run (catch:module:install --all). Miss the install step and routes exist but 404 in the admin UI, or migrations never run because nobody called catch:migrate {Module}.

Generated files are a starting point, not a contract. Generate first, then edit in place — regenerate over a hand-modified controller and your changes are gone. This is why the guidance is "generate then make small modifications" rather than "hand-write from scratch." Keep a mental list of which files you've customised.

Stale assets and stale routes. If a change isn't visible, check php artisan catch:build before debugging Vue. If a new route 404s, check route caching — a cached route table hides new route.php entries.

Permissions are attached to routes, not controllers. Adding a method to an existing controller without a corresponding route entry (or menu via catch:make:menu) means the permission record never exists, and the UI shows a forbidden error for the admin account too. Menu and permission creation is part of "done."

Frontend/backend naming drift is the common failure. {Module} is PascalCase in modules/, {module} is lowerCamelCase in web/src/views/, {resources} is kebab-case plural in routes. A mismatch here produces a menu that points at a nonexistent component — a silent, annoying bug. Derive all three from the same source name.

Version pinning deserves scrutiny. The stack is on the aggressive edge (PHP 8.3+, Laravel 13+, Vue 3.5+, Vite 8+, Pinia 3). Before adopting, confirm your hosting/tooling supports those floors; the Apache-2.0 licence is friendly but the dependency surface is bleeding-edge. With ~940 stars and effectively zero downstream dependents, expect to read source rather than find Stack Overflow answers, and budget for the upgrade treadmill.

Documentation lookup is part of the workflow. The project steers toward Context7/MCP for CatchAdmin-specific questions and Laravel Boost MCP (or official Laravel docs) for framework questions. Internalise that split: CatchAdmin conventions are only reliably documented in-project, so read modules/Permissions and modules/System before assuming standard Laravel behaviour.

Extension points worth knowing. Installer.php (module lifecycle), CatchModel properties (query/form behaviour), $formRelations (pivot sync), Excel/Import|Export classes, and the CURD composables on the frontend. These five cover the large majority of "I need to customise how this behaves" cases without patching the framework.

Style enforcement is mechanical. Every PHP file needs declare(strict_types=1);, typed parameters and returns, PSR-12. Set this up in your editor on day one — retrofitting it across a module is tedious and makes diffs noisy for reviewers.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast