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

Laravel Tinymce Simple Imageupload Laravel Package

petehouston/laravel-tinymce-simple-imageupload

Integrates TinyMCE's image upload with Laravel: adds a configurable upload endpoint that handles image files from the editor, validates and stores them on your disks, and returns the URL to TinyMCE for immediate insertion.

View on GitHub
Deep Wiki
Context7

Getting Started

A thin package: it registers a single POST route that accepts a TinyMCE image payload, moves the file into public/, and returns the {"location": "<url>"} JSON shape TinyMCE expects. That's the whole contract — treat it as "the endpoint exists," not "the feature is done."

Minimal path:

  1. Install

    composer require petehouston/laravel-tinymce-simple-imageupload
    

    On Laravel 5.5+ auto-discovery should register Petehouston\Tinymce\TinymceServiceProvider. On older versions, add it to config/app.php under providers — this is the first thing to check if routes 404 after composer dump-autoload.

  2. Publish the config

    php artisan vendor:publish --provider="Petehouston\Tinymce\TinymceServiceProvider"
    

    You get config/tinymce.php (or similarly named — confirm the tag). This is where you set the upload directory, allowed extensions, and any size cap. Read the file top to bottom before touching TinyMCE; the defaults usually write into public/uploads or public/tinymce.

  3. Confirm the route

    php artisan route:list | grep -i tinymce
    

    You'll see something like POST /tinymce/upload. Note the exact URI and any middleware the provider applies — you'll reference this URL in JavaScript and possibly add auth middleware to it.

  4. Point TinyMCE at it

    tinymce.init({
      selector: 'textarea',
      plugins: 'image',
      toolbar: 'image',
      images_upload_url: '/tinymce/upload',
      automatic_uploads: true,
    });
    

    The moment this works is your first use case: pasting an image into the editor results in a file in public/ and an <img src> pointing at it.

The "where to look first" answer is always the service provider + the controller it references. Everything the package does lives in ~50 lines across those two files; skim them once and you'll know the package fully.

Implementation Patterns

CSRF-safe upload handler (recommended baseline). The package route sits behind Laravel's web middleware group, which means CSRF applies. TinyMCE's default images_upload_url uploader does not send X-CSRF-TOKEN, so plain usage gives you a 419. Two options:

Option A — bypass for just this route in app/Http/Middleware/VerifyCsrfToken.php:

protected $except = ['tinymce/upload'];

Fast, but now anyone can POST files to your endpoint (see Gotchas). Fine for a low-traffic admin editor on a private network.

Option B — keep CSRF, override the uploader:

tinymce.init({
  selector: 'textarea',
  plugins: 'image',
  images_upload_url: '/tinymce/upload',
  images_upload_handler: (blobInfo) => new Promise((resolve, reject) => {
    const data = new FormData();
    data.append('file', blobInfo.blob(), blobInfo.filename());
    fetch('/tinymce/upload', {
      method: 'POST',
      headers: {
        'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
        'Accept': 'application/json',
      },
      body: data,
    })
      .then(r => r.ok ? r.json() : Promise.reject(r.statusText))
      .then(({ location }) => resolve(location))
      .catch(reject);
  }),
});

This is the pattern you'll actually ship. It also works for Sanctum/API auth by swapping the token header.

Locking down the route. Add auth to the endpoint in RouteServiceProvider:

Route::middleware(['web', 'auth'])->group(function () {
    // route is already registered by the package; re-register or add middleware via the provider
});

If the package doesn't expose the route name cleanly, the pragmatic move is to disable the package route (comment out or filter in the provider) and define your own Route::post('tinymce/upload', [\Petehouston\Tinymce\Http\Controllers\TinymceController::class, 'upload'])->middleware('auth'). You get auth and control of the URL.

Per-user upload directories. The package writes to one configured directory. If you need uploads/{user_id}/, either extend the controller or subclass and override upload(). The common override:

$file = $request->file('file');
$path = 'tinymce/' . auth()->id();
$name = uniqid() . '.' . $file->getClientOriginalExtension();
$file->move(public_path($path), $name);
return response()->json(['location' => asset("$path/$name")]);

Framework frontend integration. In Vue/React/Livewire, initialize TinyMCE in onMounted/useEffect and pass the CSRF token from a meta tag or Inertia page props. Never hardcode the URL — read it from a config blob injected by Blade:

<meta name="tinymce-upload" content="{{ route('tinymce.upload') }}">

This survives domain changes and subdirectory installs.

Rich content pipeline. Once images upload, the editor output is a normal HTML string with src pointing at public/. Treat it as trusted only after you sanitize HTML with something like mews/purifier — TinyMCE content is not inherently safe if other plugins are enabled.

Gotchas and Tips

  • TinyMCE 6 changed the uploader contract. The 4/5 signature is (blobInfo, success, failure, progress); in 6 it's (blobInfo, progress) => Promise. Copy-pasting old tutorials into a modern project silently breaks the handler. Match the version in your tinymce.init.

  • The response shape is non-negotiable. TinyMCE expects { "location": "..." }. If your override returns { "url": ... } or a 200 with an HTML body, the upload "succeeds" but the image inserts as a broken icon with no console error explaining why. Check the network tab.

  • Public path only. The package writes into public/, not storage/. If you're on a container or read-only filesystem, public/ may not be writable or may not survive deploys. Either mount the directory as a volume or swap to Storage::disk('public') and storage:link in your override.

  • Field name is likely file. TinyMCE's default uploader posts the blob under file. If your overridden images_upload_handler appends it as image, the controller's $request->file('file') is null and you get a silent 422/500. Always append as file unless you also changed the controller.

  • PHP upload limits win. upload_max_filesize, post_max_size, and memory_limit in php.ini (or public/.htaccess php_value on Apache) cap uploads before the package's own size check runs. Large screenshots from modern monitors routinely exceed 2 MB defaults — set at least 8–10 MB for a photo-heavy editor.

  • SVG is an XSS vector. The config's allowed extensions often include everything the browser accepts. Strip svg from the allowed list unless you're serving images with Content-Disposition: attachment or a strict CSP. TinyMCE can render inline SVGs; stored XSS follows.

  • No orphan cleanup. Uploading then deleting the image in the editor leaves the file on disk forever. If this matters, add a queued job that reconciles files against saved content, or write through a dedicated disk and manage via Storage::delete on post updates.

  • Route caching. php artisan route:cache captures the package route as-is. If you add middleware by re-declaring the route, do it in a RouteServiceProvider (not a controller or web.php conditionally) — routes defined outside routes/*.php won't be cached and you'll get inconsistent 404s between local and production.

  • Config cache. php artisan config:cache in production means edits to config/tinymce.php need a config:clear + config:cache cycle. Publish-then-edit-then-forget is a classic 2 a.m. debugging trap.

  • Auth middleware is on you. By default the route is public. A public "upload arbitrary image" endpoint is a free image-hosting service and a disk-fill DoS. Add auth (and ideally throttle:10,1) before this touches production.

  • Extension points are thin. The package is ~50 lines. If you need thumbnails, EXIF stripping, virus scanning, or S3, you're not "extending" it — you're replacing the controller. The fastest path is to vendor-publish (if possible) or copy TinymceController::upload() into app/Http/Controllers/ImageUploadController.php and wire your own route. Don't fight the package for behaviors it was never designed for.

  • Maintenance signal. 66 stars and no recent activity is a red flag for Laravel upgrades — the provider may not support newer framework versions or PHP 8.3+ strictness. If you keep it, pin the version in composer.json and write one integration test that hits the endpoint end-to-end ($this->post('/tinymce/upload', ['file' => UploadedFile::fake()->image('a.png')])->assertJsonStructure(['location'])). That test is your upgrade canary.

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.
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk