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

Twill Laravel Package

area17/twill

Twill is an open source Laravel package for building custom, beautiful CMS admin consoles fast. It standardizes common publishing features with pre-built Vue.js UI components, no lock-in, and full flexibility.

View on GitHub
Deep Wiki
Context7

Getting Started

Twill is a Laravel package, not a separate app — you install it into a Laravel project and it layers an admin console on top of your own models.

  1. Install and scaffold:

    composer require area17/twill
    php artisan twill:install     # config/twill.php, config/twill-navigation.php, routes/twill.php, migrations, assets
    php artisan migrate
    php artisan storage:link      # required for media/file library URLs
    php artisan twill:superadmin  # creates the first admin in twill_users
    

    Confirm the PHP/Laravel constraints before you start (Twill 3 targets Laravel 9/10 era, PHP 8+); pin a 3.x minor in composer.json since releases land frequently.

  2. Where to look first:

    • config/twill.php — features on/off (enabled), locales, media/file config, block editor, auth guard.
    • config/twill-navigation.php — the admin sidebar; this is the map of your CMS.
    • routes/twill.php — module routes auto-registered under the Twill middleware group.
    • app/Models, app/Repositories, app/Http/Controllers/Admin, resources/views/admin.
    • Docs at twillcms.com/docs, specifically CRUD modules, Form fields, Block editor, Relations.
  3. First use case — one module end to end: generate an Article module, migrate, add a couple of form fields, register it in navigation, and create a record. Then do the same on an existing model (Twill explicitly supports hooking existing tables via traits) — that second pass is the real "does this fit?" test.

Implementation Patterns

The generated module triad. Twill generates a Model + Repository + Controller + Request + migration + Blade form. Day-to-day work happens almost entirely in:

  • the model: traits (HasMedias, HasFiles, HasBlocks, HasSlug, HasRevisions, HasPosition, HasTranslation, HasRelated, HasNestedItems), plus $fillable, $translatedAttributes, $mediasParams.
  • the repository (extends A17\Twill\Repositories\ModuleRepository): declare $model, then use beforeSave/afterSave hooks and behavior traits (Sluggable, Mediable, Blockable, Fileable, Revisionable, Sortable, Related, Translation, Nestable) for persistence logic.
  • the controller: module setup, table columns, and submit/publish options — not business logic.

Keep custom code in repositories and model observers so the admin UI stays declarative.

Form builder, not hand-written HTML. Use Blade components inside the generated form view:

@extends('twill::layouts.form')

@section('contentFields')
    <x-twill::input name="title" label="Title" :required="true" />
    <x-twill::wysiwyg name="body" label="Body" />
    <x-twill::medias name="hero" label="Hero image" />
    <x-twill::block-editor />
@endsection

Older @formInput-style directives still render (they’re converted to components internally), but write components for new work — the directives are on a deprecation path. Sidebar fields go in sideFieldsets/fieldsets.

Media with named crops. Define roles and crops once in $mediasParams and treat those crop names as the contract with your front end:

public $mediasParams = [
    'hero' => [
        'desktop' => [['name' => 'default', 'ratio' => 16/9]],
        'mobile'  => [['name' => 'mobile', 'ratio' => 1]],
    ],
];

Then $article->image('hero', 'desktop') (or imageAsArray for srcset) in Blade/API responses. Regenerate crops when you add ratios.

Structured content: blocks vs repeaters. Blocks for editorial flexible content (JSON in content), repeaters for smaller repeated field groups, real related models when you need to query/query-count them. Render with $model->renderBlocks() and one Blade view per block type; renderBlocks(false) returns the structured array if you want to transform it (e.g., for an API) instead of rendering.

Listing screens via the table builder. Override the controller’s index table columns/filters with OOP classes rather than fighting views: Text::make()->field('title')->linkToEdit(), image/relation columns, and filter classes. This keeps sorting, searching, and permission-aware actions consistent.

Workflow features you get for free — wire them deliberately:

  • publishing: published flag + publish date windows; use scopes in your front-end queries.
  • revisions & drafts: enable revisions in module options; draft revisions (on top of published) are ideal for editorial approval flows.
  • browsers/relations: morphable browser fields for picking existing records; HasRelated for curated, reorderable lists.
  • nested modules: add depth config plus parent_id/_lft/_rgt; navigation config gets nested.
  • singletons: homepage/settings-style single-record modules via Route::singleton(...) — no index, no create.

Front-end integration. Twill makes no front-end assumptions: use standard Eloquent in your own controllers, or serialize models into an API. Two things worth doing early: (1) hook the preview URL so editors can see drafts, and (2) invalidate your page/response cache in a model observer on save/publish — Twill won’t do it for you.

Admin users are a separate guard/table (twill_users). Don’t conflate CMS editors with your application’s end users; wire role/permission checks (Twill 3 has first-class permission management) against the twill guard.

Gotchas and Tips

  • Version and upgrade surface. Twill 3 renamed namespaces, prefixed tables with twill_, and made datetimes fully timezone-aware (UTC storage, browser-local display). If you’re carrying a 2.x app or copied snippets, read UPGRADE.md rather than guessing. Remove any old timezone workarounds.
  • Trait order and model config cause silent breakage. Missing $fillable entries, mismatched $translatedAttributes, or trait ordering issues produce saves that appear to work but drop fields. When a field “doesn’t save,” diff the form field name, the migration column, and $fillable/translated attributes first.
  • Slugs live in twill_slugs, not your table. Override slug generation explicitly (source field / generateSlug()), and remember slugs are per-locale — changing a title shouldn’t silently rewrite URLs for published content.
  • Assets are compiled and cached. If you customize admin Vue components/CSS you must rebuild admin assets (the twill:build flow, requiring Node) — editing published files in public/ is a dead end. Stale references usually mean: rebuild, php artisan view:clear, config:clear, and use the manifest-flush command Twill 3 ships for exactly this.
  • Media 404s are usually plumbing: storage:link, APP_URL/glide.base_url correctness, and whether the crop ratio you asked for exists in $mediasParams. Missing crop definitions return nothing rather than erroring loudly.
  • JSON columns matter. Blocks, repeaters, and media metadata expect MySQL 5.7+/MariaDB/Postgres JSON support. Legacy MySQL 5.6 setups fail at migration or on first save.
  • 403s are permissions/guard, not routes. Verify with php artisan route:list --path=admin, check the user’s role, and confirm the module is registered in config/twill-navigation.php — an unregistered module can be routable but invisible/permission-denied.
  • Disable what you don’t use. config/twill.php enabled toggles (media, files, blocks, etc.) remove UI and migrations you don’t need; trimming early avoids dead tables and confusing fields later.
  • Extension points to prefer: repository hooks and model observers for logic, table column/filter classes for listings, getSubmitOptions() for publish/save button behavior, setBreadcrumbs() for context, controller setup methods for option defaults, and capsules/packages for modules reused across projects.
  • Attribution is a license term. The code is Apache-2.0 but the UI is CC-BY 4.0 and requires the “Made with Twill” footer link in the admin console — plan for it (or get permission) instead of stripping it.
  • Debugging playbook: reproduce with APP_DEBUG=true, watch storage/logs/laravel.log and the browser network tab (admin is Vue-driven; failed form saves often surface as a JSON validation response), and confirm a fresh php artisan twill:module:make module works before blaming your customization. When in doubt, diff your generated module against a clean one — most issues are a missing trait, config key, or migration column.
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.
mydnic/volet-feature-board-filament-plugin
wmde/email-address
refinery29/test-util
dees040/laravel-api-responses
teamzac/geocoder
ideatica/config-writer
keaton-php/pipe-dream
jacksonwebservices/laravel-user-suite
maksimru/conditional-laravel-debugbar
gismatul/nova-support-chat
gismatul/laravel-support-chat
networkrailbusinesssystems/oracle-api
alphabalex/messaging-made-easy
rawphp/warp
shootkiran/merobug
narakode/fineauth
numencode/wn-syncops-plugin
ibnnajjaar/mib-global-pay
refinery29/php-cs-fixer-config
parsnick/steak