Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: developing-new-attachables description: Turn Eloquent models into rich text content attachments (e.g., user mentions, embedded resources) using the AttachableContract and Attachable trait.
Developing New Attachables
When to use this skill
Use this skill when turning an Eloquent model into a content attachment for rich text editors (e.g., user mentions, embedded resources), or when creating custom non-model attachables (e.g., OpenGraph embeds).
Making a Model Attachable
1. Implement the Contract and Use the Trait
The model must implement AttachableContract and use the Attachable trait:
@verbatim
use Tonysm\RichTextLaravel\Attachables\AttachableContract;use Tonysm\RichTextLaravel\Attachables\Attachable;class User extends Model implements AttachableContract{use Attachable;// ...}
@endverbatim
2. Implement the richTextRender() Method
This is the only required method. It returns the HTML used when displaying the attachment outside the editor:
@verbatim
public function richTextRender(array $options = []): string{return view('mentions.partials.user', ['user' => $this,])->render();}
@endverbatim
The $options array may contain an in_gallery boolean when the attachment is rendered inside a gallery.
3. Create the Blade Partial
Create a Blade view that renders the attachment's HTML. This is the content end users will see:
@verbatim
{{-- resources/views/mentions/partials/user.blade.php --}}<span class="mention">{{ $user->name }}</span>
@endverbatim
4. Optional: Plain Text and Markdown Representations
For plain text export (toPlainText()), implement:
@verbatim
public function richTextAsPlainText(?string $caption = null): string{return $this->name;}
@endverbatim
For Markdown export (toMarkdown()), implement:
@verbatim
public function richTextAsMarkdown(?string $caption = null): string{return $caption ?: $this->name;}
@endverbatim
If these methods are not implemented and no caption is stored, the attachment won't appear in the respective output.
5. Optional: Override Default Trait Methods
The Attachable trait provides sensible defaults for these methods, but you can override them:
richTextContentType(): string— defaults to'application/octet-stream'richTextFilename(): ?string— defaults tonullrichTextFilesize(): ?int— defaults tonullrichTextPreviewable(): bool— defaults tofalserichTextMetadata(?string $key = null): mixed— defaults tonull
6. Using a Separate Trait for Organization
For better organization, extract the attachable behavior into its own trait:
@verbatim
// app/Models/User/Mentionee.phpnamespace App\Models\User;use Tonysm\RichTextLaravel\Attachables\Attachable;trait Mentionee{use Attachable;public function richTextRender(array $options = []): string{return view('mentions.partials.user', ['user' => $this])->render();}public function richTextAsPlainText(?string $caption = null): string{return e($this->name);}public function richTextAsMarkdown(?string $caption = null): string{return $caption ?: e($this->name);}}
@endverbatim
Then use it on the model:
@verbatim
class User extends Model implements AttachableContract{use User\Mentionee;}
@endverbatim
Custom Attachables Without SGIDs
For attachments that don't need a database record (e.g., OpenGraph embeds), implement AttachableContract directly on a plain class and register a custom resolver:
@verbatim
use Tonysm\RichTextLaravel\RichTextLaravel;// In AppServiceProvider::boot()RichTextLaravel::withCustomAttachables(function (DOMElement $node) {if ($attachable = OpengraphEmbed::fromNode($node)) {return $attachable;}});
@endverbatim
The class must implement toRichTextAttributes(), equalsToAttachable(), and richTextRender(). Use a content-type attribute on the node to identify your custom attachment type:
@verbatim
class OpengraphEmbed implements AttachableContract{const CONTENT_TYPE = 'application/vnd.rich-text-laravel.opengraph-embed';public static function fromNode(DOMElement $node): ?self{if ($node->getAttribute('content-type') === static::CONTENT_TYPE) {return new self(/* attributes from node */);}return null;}public function richTextRender(array $options = []): string{return view('attachables.opengraph-embed', ['attachable' => $this])->render();}public function toRichTextAttributes(array $attributes): array{return ['content_type' => static::CONTENT_TYPE,'previewable' => true,// ... your custom attributes];}public function equalsToAttachable(AttachableContract $attachable): bool{return $this->richTextRender() === $attachable->richTextRender();}}
@endverbatim
How SGIDs Work
The Attachable trait automatically generates a Signed Global ID (SGID) for the model via richTextSgid(). This SGID is stored in the <rich-text-attachment sgid="..."> tag in the canonical HTML. When content is rendered, the AttachableFactory resolves the SGID back to the original model. SGIDs are signed using your APP_KEY and never expire for rich text attachments.
Frontend: Creating Attachments in the Editor
On the frontend, create attachments in the editor when a user selects an attachable. The key data needed from your API endpoint is:
sgid— the model's Signed Global IDcontent— the HTML to display inside the editor
For Trix, create a new Trix.Attachment({ sgid, content, contentType: '...' }) and insert it. For Lexxy or custom editors, the mechanism differs but the concept is the same: embed an attachment node with the sgid attribute.
Retrieving Attachables from Content
After saving, extract all attachables of a specific type:
@verbatim
use Tonysm\RichTextLaravel\Attachment;$post->body->attachments()->filter(fn (Attachment $attachment) => $attachment->attachable instanceof User)->map(fn (Attachment $attachment) => $attachment->attachable)->unique();
@endverbatim