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

Rich Text Laravel Laravel Package

tonysm/rich-text-laravel

Rich Text Laravel brings Action Text-style rich content to Laravel. It integrates the Trix editor, stores HTML with attachments, and provides helpers for sanitizing, rendering, and managing rich text fields on Eloquent models.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and scaffold (about 5 minutes).

composer require tonysm/rich-text-laravel
php artisan vendor:publish --tag="rich-text-laravel-config"
php artisan vendor:publish --tag="rich-text-laravel-migrations"
php artisan migrate
  • Look at the migration first. Rich text is stored in a polymorphic rich_texts table (record_type, record_id, name, body), not in a column on your model. That single fact drives everything else: eager loading, cleanup on delete, querying, and why adding another rich text field needs no schema change.
  • Look at the config next. config/rich-text-laravel.php controls the attachment disk, model/table overrides, and any URL-signing or sanitation options.
  • Add the trait to the model. use Tonysm\RichTextLaravel\RichText; on Post, Page, Comment, etc.
  • Include Trix in your layout. Either the package's Blade asset directives/components or the package's JS entry imported in resources/js/app.js (whichever the current README specifies). Trix must be available as window.Trix before the input initializes.
  • php artisan storage:link if you're using the local/public disk for attachments.

First use case: a Post with a content field.

Form:

<x-trix-input id="content" name="content" :value="old('content', $post->richText('content'))" />

Controller:

$post = Post::create($validated);
$post->richText('content')->update(['body' => $request->input('content')]);

Show page:

<div class="trix-content">
    {!! $post->richText('content')->toHtml() !!}
</div>

The .trix-content wrapper is not optional styling vanity — it's where Trix's own CSS (and your prose overrides) apply, and it's the class the package's styles target.

Implementation Patterns

  • Form round-trip. Trix keeps a hidden input in sync with the editor; whatever is in name is what lands in $request->input(). Validate it as an ordinary string, then persist to the rich text body. On validation errors, re-render the component with :value="old('content', $post->richText('content'))".
  • Multiple fields per model. richText('content'), richText('summary'), richText('notes') are separate rows keyed by name. Adding a field is a form change, not a migration.
  • Rendering safely. toHtml() outputs stored HTML. Treat it as untrusted: Trix lets users paste arbitrary markup. Confirm whether your version sanitizes on storage or render, and if not, add mews/purifier or Symfony's HtmlSanitizer before {!! !!}.
  • Plain text for excerpts. For cards, search indexes, and meta descriptions, strip the tags or use a plain-text accessor rather than rendering HTML into a <meta> tag.
  • Attachments pipeline. Point the config disk at real storage (public locally, S3 in production). The Trix component posts files to the package's attachment endpoint, which stores the file, creates a backing record, and returns a signed URL that gets rehydrated inside the body at render time. Keep that upload route behind auth, and make sure your layout has <meta name="csrf-token" content="{{ csrf_token() }}"> — Trix's XHR uploads need it.
  • Empty editor semantics. Trix submits "" for empty content, not null. required works, but min:/max: rules behave oddly because of wrapper <div>s and <br>s in the HTML. Normalize (strip tags, trim) before measuring length.
  • Livewire. Wrap the editor in wire:ignore, bind the hidden input with wire:model, and relay trix-change into Livewire. Without wire:ignore, Livewire's DOM diffing will detach Trix from its element.
  • Inertia / Vue / React. The Blade component won't help you; mount Trix directly and point its attachment callback at the same upload route the package exposes so attachments still work.
  • Backfilling an existing column. Write a one-off command: read posts.body, write it via richText('content'), keep the old column for one release as a fallback, then drop it.
  • Testing. Use RefreshDatabase. Assert rows in rich_texts with assertDatabaseHas, and assert on visible fragments of HTML (assertSee('...', false)) rather than exact markup — Trix normalizes whitespace and wrappers.
  • Cleanup. Add a deleting observer on models that own rich text to delete rich_texts rows and their files. Morph relations can't use DB-level cascadeOnDelete, so this is on you.

Gotchas and Tips

  • XSS is your responsibility. A WYSIWYG is a form field, not a trust boundary. Sanitize on output (or at least on save), and never skip it because "only admins use the editor."
  • Signed attachment URLs expire. Page caching, emailed HTML, and generated PDFs will show broken inline images after the URL TTL. Lengthen the TTL, re-sign at render time, or keep those specific attachments on a public disk.
  • Cached config is the #1 "it's broken" cause. After editing rich-text-laravel.php run php artisan config:clear. A missing storage:link produces the same 404 symptom on a different layer.
  • Trix reads value only at init. Setting input.value later won't update the editor — call trix.loadHTML() or re-render the component. This is the usual cause of "old input doesn't repopulate."
  • Duplicate IDs break multi-field forms. Each <x-trix-input> needs a unique id; duplicates confuse Trix's toolbar and input element association.
  • N+1 on index pages. Rendering richText('content') in a loop queries per row. Eager load the rich text relation (or select a precomputed excerpt column) when listing.
  • Upgrades need manual diffing. Config and migrations are published copies, not vendor-managed. Re-publish, diff against your edited versions, and read the changelog — the trait name, directive names, and method names have evolved across releases.
  • Extension points. If your version resolves the rich text model through config/container, subclass Tonysm\RichTextLaravel\Models\RichText and register it — that's the clean place for custom sanitization, observers, or attachment handling.
  • Don't assert on HTML strings in tests. Trix emits <div> vs <p> vs <br> differences that vary by paste path. Assert visible text and DB state.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin