Editor image ingest
The MediaMarkdownEditor field lets writers paste or drop images straight into a markdown body. Images never touch the public disk. Every drop routes through EditorImageStore, which writes a Spatie media row on the s3 disk and returns the media URL for the editor to insert.
The invariant
Never pass a Livewire TemporaryUploadedFile into the Spatie FileAdder.
TemporaryUploadedFile extends Symfony UploadedFile, but its parent path is an empty tmpfile() handle — not the bytes on the Livewire temp disk. Feeding that object to addMedia(), addMediaFromDisk(), or addMediaFromString() writes an empty blob, or throws DiskCannotBeAccessed on MinIO and Bunny S3. EditorImageStore::copyLivewireToLocalTemp() reads the bytes via readStream() and writes them to a real file under sys_get_temp_dir() before Spatie ever sees them.
Flow
EditorImageStore::store($file, $record = null, $collection = null) runs:
- Resolve the target model — the
$recordif it implementsSpatie\MediaLibrary\HasMediaand exists, otherwise the authenticated user (who must implementHasMedia). - Pick the collection —
bodywhen the model registers it, elseuploads. An explicit$collectionargument wins. - Copy a
TemporaryUploadedFileto a real local temp file; pass a normalUploadedFilestraight through. $model->addMedia($source)->usingFileName(...)->withCustomProperties(['user_id' => ...])->toMediaCollection($collection, MediaDriveCatalog::DISK).- Return the
Mediainstance.EditorImageStore::url($media)returns$media->getUrl()— the CDN host when the diskurl(orAWS_URL) is set, otherwise the direct disk URL.
MediaMarkdownEditor wires saveUploadedFileAttachmentUsing to EditorImageStore::store and getFileAttachmentUrlUsing to EditorImageStore::url, so the markdown body receives the final CDN URL.
Why it matters on multi-replica
On Bunny Magic Containers the Livewire temp disk is s3 (LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK=s3). A temp upload lives at a remote key the Spatie FileAdder cannot read through the Livewire object's empty local path. The copy step is what makes ingest work across replicas — bytes are materialised on the pod, then re-uploaded as Spatie media on the same s3 disk.
sequenceDiagram
participant Editor
participant Livewire
participant Store as EditorImageStore
participant Spatie
participant S3 as s3 disk
Editor->>Livewire: drop image
Livewire->>Livewire: temp upload (s3)
Livewire->>Store: TemporaryUploadedFile
Store->>Store: copyLivewireToLocalTemp()
Store->>Spatie: addMedia(local path)
Spatie->>S3: write media file
Spatie-->>Store: Media model
Store-->>Editor: $media->getUrl()
Editor->>Editor: insert URL into markdown body