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

Decoy Laravel Package

bkwld/decoy

Decoy is a Laravel package for building custom admin interfaces and CMSs. It provides model-driven admin controllers, form and listing builders, localization, and a clean Vue-powered UI, so you can manage content without a separate CMS.

View on GitHub
Deep Wiki
Context7

Getting Started

Decoy is a Laravel CMS where the model is the source of truth. Before touching any UI you should understand three layers: model metadata ($admin), a thin admin controller extending Bkwld\Decoy\Controllers\Base, and the published config in config/decoy.php.

Minimal setup:

composer require bkwld/decoy
php artisan vendor:publish --provider="Bkwld\Decoy\DecoyServiceProvider"
php artisan migrate

Publishing gives you config/decoy.php, Decoy migrations, lang files, and the admin assets under public/. Register Bkwld\Decoy\DecoyServiceProvider and the Decoy facade if you are on a pre-auto-discovery Laravel. Most versions also ship an installer (e.g. php artisan decoy:install) that scaffolds your admin user, routes/admin.php, and an app/Http/Controllers/Admin directory — run it if available, then read the generated files before editing anything.

Where to look first, in order:

  1. config/decoy.php — routes prefix (default /admin), locales, permissions, middleware, upload paths.
  2. Your existing User model — Decoy expects roles/permissions there; add the suggested trait/columns.
  3. routes/admin.php — this is where controllers are registered and the admin nav is composed.
  4. A generated sample controller and model — they show the exact $admin / $columns shape your version expects.

First real use case: a single Article model with title, slug, body, and a hero image.

// app/Article.php
class Article extends Bkwld\Decoy\Models\Base
{
    protected $admin = [
        'title'       => 'title',
        'description' => 'summary',
        'columns'     => ['title', 'slug', 'updated_at'],
        'search'      => ['title', 'body'],
        'rules'       => ['title' => 'required', 'body' => 'required'],
        'images'      => ['hero' => ['width' => 1200, 'height' => 630, 'method' => 'crop']],
    ];
}
// app/Http/Controllers/Admin/ArticlesController.php
class ArticlesController extends Bkwld\Decoy\Controllers\Base
{
    protected $title = 'Articles';
    protected $description = 'News and posts';
}

Register the controller in routes/admin.php (typically Decoy::routes([...]) or a controller list), then visit /admin and log in. If the listing appears but no form does, Decoy is auto-generating fields from the DB schema — that is expected for a first pass.

Implementation Patterns

Keep controllers thin, models fat. Day-to-day Decoy work is mostly editing $admin on models. Listing columns, search fields, default sort, validation, upload handling, and relationship metadata belong on the model. Controllers should only carry $title, $description, custom $filters, and one-off form customization.

Form customization with Former. Decoy builds forms from DB introspection, but the idiomatic override is a form() method on the controller returning a Former form. Use Decoy's field classes rather than raw Former where possible: Image, File, Checkbox, Select, Redactor, Wysiwyg, Video. They wire up Croppa paths, previews, and asset cleanup automatically.

protected function form($model)
{
    return parent::form($model)
        ->withUpload('hero')          // uses the images config on the model
        ->redactor('body')
        ->text('title')->required();
}

Relationships. Has-many and belongs-to-many are declared in $admin['many'] (or equivalent on your version) so Decoy renders a multiselect/attached-records editor. Always pair this with a Cloner-aware duplicate() so cloning a parent does not orphan children. Decoy uses bkwld/cloner; add cloneable to the model's admin metadata and override duplicate() only when relationships need special handling.

Ordering and drag-drop. Set 'sortable' => true and include the trait (Bkwld\Decoy\Models\Traits\Orderable or the $admin['ordering'] key, depending on version). This requires an integer position column and the position value to be persisted on reorder — verify after your first drag.

Validation scopes. Put shared rules on the model ($rules or $admin['rules']) and context-specific rules (create vs update) on the controller or a $admin['validation'] array. This keeps API and admin validation aligned.

Permissions. Decoy's auth is role/permission based via config/decoy.php. Add the role and permission columns to your User model, seed a developer/super-admin role, and then gate controllers. Nav items you don't want shown should be conditionally registered in routes/admin.php rather than hidden with CSS.

Localization. If decoy.locales has more than one entry, Decoy renders a locale switcher and expects translated columns (typically a translations table or JSON columns, depending on the trait you enable). Turning locales on after shipping data means writing a backfill migration — do it before launch.

Navigation and extension points. Customize the sidebar in routes/admin.php when registering controllers; add external links or grouped sections there. For deeper extension, use Former macros, Decoy middleware, and the Decoy facade for helpers inside views (Decoy::isLocalized(), URL helpers). Keep custom Blade overrides in resources/views/vendor/decoy so vendor:publish upgrades don't clobber them.

Gotchas and Tips

The package is from the Laravel 5.x era (last release 2018). Before anything else, confirm your Laravel and PHP versions. On Laravel 9+/PHP 8 you will hit Illuminate\Foundation and container signature changes; expect to fork the package or pin the app to Laravel 5.5–5.8. This is the single biggest decision in any Decoy assessment — budget for it explicitly.

Re-publish after upgrades. vendor:publish --provider="Bkwld\Decoy\DecoyServiceProvider" --force overwrites admin assets and config. Diff config/decoy.php before committing; teams routinely lose customizations this way.

Croppa has its own cache directory. Uploads write to public/uploads (or your configured path) but Croppa generates thumbnails on demand in a cache folder outside git. On deploy, either warm the cache or ensure permissions; run php artisan croppa:prune periodically. Store the path in the database, not an absolute URL, or environment changes will break every image.

$admin cache and route cache. Decoy's metadata is inexpensive but the model file is loaded on every admin request. If you use php artisan route:cache and see stale nav or 404s after adding a controller, clear both config and route caches — the admin routes are dynamically registered, so a cached route table can miss new controllers.

Naming conventions drive everything. ArticlesController maps to Article and route admin/articles. When you deviate, set $model explicitly. Silent failures to resolve a model appear as blank listings, not exceptions.

Slugs and unique columns. Decoy will happily let you save a duplicate slug if your rules don't cover it. Add a Rule::unique scoped to the model and handle soft-deleted records.

Sortable requires a real column. Adding 'sortable' => true without a position integer column throws on the listing page. Add the migration first.

Cloning and translations are explicit. Decoy won't copy relations or translations for you. Test duplication on a record with attached media and children before declaring it done.

Debug the generated form. If a field is missing, dump the Former instance in form() and inspect. Most "Decoy is broken" reports are a column that isn't in $fillable, a cast mismatch, or a field name that doesn't match the DB column.

Permission defaults are permissive. Seed and enforce a super-admin role in staging; otherwise a fresh install lets every authenticated user into the admin.

Don't fight the conventions. When you need something Decoy doesn't model well (workflow states, audit logs), add it as a normal Laravel layer behind the controller rather than trying to force it into $admin metadata.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager