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

Mimey Laravel Package

ralouphie/mimey

Mimey is a lightweight PHP library that maps file extensions to MIME types and back. It ships with a comprehensive built-in mapping, requires no extensions, and offers simple methods like getMimeTypeFromExtension and getExtensionFromMimeType for reliable content-type detection in Laravel or any PHP...

Deep Wiki
Context7

Getting Started

What it is: a zero-dependency PHP class (~330KB of static mapping table) that converts file extensions to MIME types and back. One class, four methods, no facade, no service provider, no config file.

You probably already have it. laravel/framework depends on mimey/mimey because the mimes and mimetypes validation rules use it under the hood. Confirm with:

composer show mimey/mimey

For a non-Laravel or bare install: composer require ralouphie/mimey.

First use — extension → MIME:

use Mimey\MimeTypes;

$mimes = new MimeTypes;

$mimes->getMimeType('json');     // "application/json"
$mimes->getMimeType('unknown');  // null
$mimes->getExtension('image/jpeg'); // "jpg"  (first/preferred match)

The full API you'll use 95% of the time:

Method Returns
getMimeType($ext) MIME string or null
getExtension($mime) extension string or null
getAllMimeTypes($ext) array of MIMEs (empty if unknown)
getAllExtensions($mime) array of extensions

Where to look first:

  1. vendor/ralouphie/mimey/src/MimeTypes.php — the built-in table is a static array near the top; skim it to see coverage for your domain.
  2. Illuminate\Validation\Concerns\ValidatesAttributes — grep for MimeTypes to see how Laravel itself wires it up (it resolves MimeTypes from the container).

First real use case: deriving a browser accept attribute and a validation rule from one config list of allowed extensions.

<input type="file" name="file" accept="{{ app(MimeTypes::class)->getMimeType('pdf') }}">

Implementation Patterns

1. Inject it — no binding needed

The constructor's only argument is optional, so the container resolves it automatically:

public function __construct(private \Mimey\MimeTypes $mimes) {}

2. Extend the table at boot

Custom formats (.geojson, .heic, internal extensions) go through the constructor. Pass a mapping and it augments the built-in table:

// AppServiceProvider::register()
$this->app->singleton(\Mimey\MimeTypes::class, fn () => new \Mimey\MimeTypes([
    'geojson' => ['application/geo+json'],
    'heic'    => ['image/heic', 'image/heif'],
]));

Registering it as a singleton with a constructor mapping keeps the extension isolated and testable.

3. One config drives accept + validation

The anti-pattern is two lists that drift. Derive both from config:

// config/uploads.php
return ['allowed' => ['jpg', 'jpeg', 'png', 'pdf', 'geojson']];
<input type="file" name="file"
  accept="{{ collect(config('uploads.allowed'))
      ->map(fn ($ext) => app(\Mimey\MimeTypes::class)->getMimeType($ext))
      ->filter()
      ->unique()
      ->implode(',') }}">
// FormRequest
public function rules(): array
{
    return [
        'file' => ['required', 'file', 'mimes:'.implode(',', config('uploads.allowed'))],
    ];
}

4. Custom validation rule — extension vs. detected content

Mimey alone cannot validate an upload. Combine it with $file->getMimeType() (Symfony finfo) to confirm the extension is consistent with the bytes:

class AllowedUpload implements ValidationRule
{
    public function __construct(private array $extensions) {}

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $mimes = app(\Mimey\MimeTypes::class);
        $ext   = strtolower($value->getClientOriginalExtension());

        if (! in_array($ext, $this->extensions, true)) {
            $fail("The {$attribute} extension is not allowed.");
            return;
        }

        $allowed = $mimes->getAllMimeTypes($ext);   // [] if unknown
        $actual  = $value->getMimeType();           // from file contents

        if (! in_array($actual, $allowed, true)) {
            $fail("The {$attribute} contents do not match a .{$ext} file.");
        }
    }
}

5. Correct Content-Type on manual streams

When you serve a file outside Storage::download():

return response()->streamDownload(
    fn () => print file_get_contents($path),
    basename($path),
    ['Content-Type' => $mimes->getMimeType(pathinfo($path, PATHINFO_EXTENSION))
        ?? 'application/octet-stream'],
);

6. Persist metadata at upload time

Write mime_type into your uploads row when the file lands, so listing screens and API responses don't need to touch disk to render a badge or build a link. Conversely, use getExtension() when an external system hands you a MIME (email attachments, webhook payloads, S3 Content-Type).

Gotchas and Tips

  • It is not content detection. Mimey is a lookup table keyed by extension. Never use it as the sole security gate on an upload — pair it with finfo/$file->getMimeType(), or just use Laravel's mimes/mimetypes rules, which already do the right thing.
  • Both methods return null. Type-hint ?string and always coalesce at the boundary (?? 'application/octet-stream'). getAllMimeTypes() returns [], not null, for unknown extensions — that's a second, different failure shape to handle.
  • Mappings are many-to-many. .jpg and .jpeg both map to image/jpeg; image/jpeg maps back to jpg (the first/preferred entry). Round-tripping an extension is lossy. Use getAllMimeTypes()/getAllExtensions() when you need the complete set.
  • Case-sensitive. getMimeType('JPG') returns null. Normalize with strtolower() before every call.
  • Strip MIME parameters. A stored value like text/plain; charset=utf-8 will miss. Split on ; and trim() first.
  • Custom mappings are not validated. A typo in your constructor mapping creates an entry nothing ever queries, silently. Add a feature test asserting each custom extension resolves to the MIME you expect.
  • add() mutates. MimeTypes is mutable. Calling add() on a container singleton leaks the mapping across the whole request/app. Prefer the constructor mapping in your binding.
  • No facade. Don't look for Mimey::getMimeType(...) — it doesn't exist. If you like the sugar, register a tiny facade in app/Facades; the container already binds MimeTypes.
  • mimes vs. mimetypes debugging. mimes:jpg validates the client-supplied extension, mimetypes:image/jpeg validates detected content. Mimey underpins the former. If a validation "should" pass and doesn't, check which rule you actually used — they read different columns.
  • Coverage is broad but not exhaustive. Proprietary CAD formats and some legacy types are missing. Extend through the constructor mapping; never patch vendor/.
  • Hot loops. For batch jobs, build one MimeTypes instance (or memoize a reverse index from getAllMimeTypes()) instead of constructing inside the loop — the constructor is cheap, but repeated getAllExtensions() calls on the same 50k-row import add up.
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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log