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

Mimemap Laravel Package

fileeye/mimemap

A PHP library for mapping MIME types to file extensions and back, using Apache and custom MIME data. Useful for file type detection, extension lookup, and consistent MIME handling in PHP or Laravel projects.

View on GitHub
Deep Wiki
Context7

Getting Started

fileeye/mimemap is a pure-data mapping between MIME types and file extensions, built from Apache's mime.types and shipped as a generated PHP array. It does not sniff file contents — that is finfo/Symfony's job. You reach for it whenever you have a MIME string and need an extension (or the reverse) without touching the filesystem.

Install and poke at it in 60 seconds:

composer require fileeye/mimemap
php artisan tinker
use FileEye\MimeMap\MimeMap;

MimeMap::getMimeType('webp');          // 'image/webp'
MimeMap::getMimeTypes('jpg');          // ['image/jpeg']
MimeMap::getExtension('image/jpeg');   // 'jpg'  (preferred extension)
MimeMap::getExtensions('image/jpeg');  // ['jpg', 'jpeg', 'jpe', ...]

Where to look first:

  1. FileEye\MimeMap\MimeMap — the entire static API you will use 95% of the time.
  2. The bundled generated map (data/ in the vendor directory) — inspect it when a lookup surprises you.
  3. FileEye\MimeMap\MimeMappingBuilder — the extension point for adding vendor types or compiling your own map.

First real use case in a Laravel app: normalizing uploaded file names. Browsers send image/jpeg with filenames like photo.JPEG, photo.jfif, or no extension at all. Resolve a canonical extension from the server-detected MIME type:

$mime = $file->getMimeType();                       // finfo, server-side
$ext  = MimeMap::getExtension($mime) ?? 'bin';      // canonical, lower-case
$name = Str::slug(pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME))
      . '.' . $ext;
$path = $file->storeAs('uploads', $name);

That is a five-line improvement over trusting getClientOriginalExtension(), which is attacker-controlled.

Implementation Patterns

1. A validation rule for “extension matches content”

Laravel's mimes:jpg,png rule already guesses the extension from the detected MIME, but it fails silently in confusing ways (.jfif, .jpeg, .jpg are all image/jpeg). Being explicit makes the error message useful:

namespace App\Rules;

use Closure;
use FileEye\MimeMap\MimeMap;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Http\UploadedFile;

class ContentMatchesExtension implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (! $value instanceof UploadedFile) {
            $fail('The :attribute must be an uploaded file.');
            return;
        }

        $ext     = strtolower($value->getClientOriginalExtension());
        $allowed = array_map('strtolower', MimeMap::getMimeTypes($ext));

        if ($allowed === [] || ! in_array(strtolower($value->getMimeType()), $allowed, true)) {
            $fail('The :attribute does not contain the type of data its extension claims.');
        }
    }
}

Note the order of trust: content first (getMimeType() reads the temp file with finfo), map second, client-supplied name last.

2. Binding the map in a service provider (for testability and Octane)

The static API is convenient but hard to fake. Wrap it:

// app/Support/MimeResolver.php
class MimeResolver
{
    public function extensionFor(string $mime): ?string
    {
        $mime = strtolower(trim(explode(';', $mime)[0]));
        return MimeMap::getExtension($mime);
    }

    public function mimeFor(string $extension): ?string
    {
        return MimeMap::getMimeType(strtolower(ltrim($extension, '.')));
    }
}
// AppServiceProvider::register()
$this->app->singleton(MimeResolver::class);

Now tests can $this->mock(MimeResolver::class) and you avoid scattering static calls through controllers. If you need a custom map, compile it once and hand it to the library at boot, not per request:

// AppServiceProvider::boot()
MimeMap::setInstance(new MimeMap(storage_path('app/mimemap.php')));

(Check the installed version's constructor/static-instance signature; the pattern is “set it once at boot”.)

3. Compiling a custom map with the builder

The shipped map will not know your product's proprietary formats, and occasionally lags on newer IANA types. Build an overlay:

use FileEye\MimeMap\MimeMappingBuilder;

$builder = MimeMappingBuilder::create();
$builder->add('application/vnd.acme.widget', 'widget');
$builder->add('application/vnd.acme.widget', 'wdgt');
// optionally merge a system mime.types you control
// $builder->addFromFile(base_path('resources/mime.types'));
$builder->save(storage_path('app/mimemap.php'));

Run it from an artisan command or a deploy step, commit the source (the additions file), and treat the compiled PHP as a build artifact. Never let production behaviour depend on the host's /etc/mime.types.

4. Inbound integrations: MIME → extension for stored payloads

A classic webhook problem — the provider sends Content-Type but no filename:

$mime = strtolower(trim(explode(';', $payload['content_type'])[0]));
$ext  = (new MimeResolver)->extensionFor($mime) ?? 'bin';

$filename = Str::uuid() . '.' . $ext;
Storage::disk('s3')->put("attachments/{$filename}", $body);

Same shape applies to email attachments, multipart/form-data parts, and archive extraction.

5. Serving files with an authoritative Content-Type

Storage::response() and response()->file() guess from the path, which breaks for extensionless files or unusual extensions:

$ext  = pathinfo($path, PATHINFO_EXTENSION);
$mime = MimeMap::getMimeType($ext) ?? 'application/octet-stream';

return response()->file($absolutePath, [
    'Content-Type'        => $mime,
    'X-Content-Type-Options' => 'nosniff',
]);

X-Content-Type-Options: nosniff pairs well here: you are asserting a type, so tell the browser not to second-guess you.

6. Bulk/migration work

Mapping 200k legacy rows in a queued job is fine — lookups are array reads. If you are doing millions, chunk and consider memoizing per-process (static array $cache = [] keyed by extension) rather than re-normalizing strings repeatedly.

Gotchas and Tips

Both directions are one-to-many; the singular methods pick a winner.

  • getExtension('image/jpeg') returns one preferred extension among several. Do not treat it as the answer if the extension is user-visible — that is a product decision, not a library one.
  • getMimeTypes('mp4'), ts, dat, bin, and similar extensions are genuinely ambiguous. For validation, accept the set; do not compare against a single MIME.

Always sanitize the input string before lookup. MIME is case-insensitive and routinely arrives with parameters:

$mime = strtolower(trim(explode(';', $mime)[0]));
$ext  = strtolower(ltrim($ext, '.'));

Skipping this produces intermittent nulls that look like missing map entries.

Handle null/empty returns explicitly. Unknown MIME or extension yields null (or an empty array). Default to application/octet-stream / bin at the boundary, and make that decision in one place.

This is a map, not a detector. Never use it to validate that a file is what it claims. MimeMap::getMimeType('jpg') says “files named .jpg are conventionally image/jpeg” — it does not say your upload is a JPEG. Combine with $file->getMimeType() (finfo) or mime_content_type().

The static instance is global state. If you call MimeMap::setInstance(...) in a test or a job, it leaks into subsequent tests and — under Octane/queue workers — subsequent requests. Set it once in a service provider; never mutate it per request.

Custom maps and Octane. Re-instantiating a MimeMap from a path inside a request means re-parsing a large PHP array on every request. Compile and bind at boot; the map is opcache-friendly because it is a plain array.

Multi-dot filenames. pathinfo('archive.tar.gz', PATHINFO_EXTENSION) is gz, which is correct. But photo.jpg.php is a different conversation — always re-validate the stored name, and never construct storage paths directly from client input even after mapping.

Watch for vendor/legacy oddities. application/x-* types, .jfif, .webp, image/avif, and modern Office types (application/vnd.openxmlformats-officedocument.*) occasionally differ between the library's map, Symfony's MimeTypes, and what a browser sends. Pick one source of truth per code path rather than mixing MimeMap, MimeTypeGuesser, and mimes: rule in the same flow.

Updating. The map is generated at release time. If a new format matters to your product, add it via MimeMappingBuilder rather than waiting on a release — and pin the package version so your compiled map and the library stay in sync.

Licence. LGPL-3.0 is fine for a normal Composer dependency in a closed-source SaaS. It becomes a consideration only if you (a) ship modified library files or (b) statically embed it in a distributed binary — at which point the relinking obligation needs a legal read, not a Stack Overflow one. Using the API and committing your own additions file is unremarkable.

Debugging checklist when a lookup returns null:

  1. Did you lowercase and strip ; charset=...?
  2. Did you strip the leading dot from the extension?
  3. Is it in the installed version's map? Grep the vendor data/ file directly.
  4. Are you on a custom instance that lacks the entry? Check what was passed to setInstance.
  5. Is a test leaking a different instance? Run the suite with --order-by=random to expose it.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core