← Laracrate 5 / 17

Configuration

Configuration

Configuration

Laracrate is configured entirely through one published file, config/laracrate.php. Publish it during installation (see the Installation section), then shape it to your app. The defaults are safe and runnable as-is: every key below has a working default, so you only override what your app actually needs.

This section walks the file top to bottom. For the deeper behavior that some keys drive (the pipeline, embeddings, watermarks), the relevant H2 section is cross-referenced rather than re-explained here.

php artisan vendor:publish --tag=laracrate-config

Default collection and context

Applied to the schema when a File row is inserted without an explicit collection or context. This happens, for example, when a variant is created and inherits from its parent. Any string is valid; the convention is default. Changing these values updates the column DEFAULT only if you re-run the migration.

'default_collection' => 'default',
'default_context'    => 'default',

Defaults per file type

The defaults block declares the safe-by-default allowlist of MIME types, extensions, max sizes, and processing options for each of the four file types (image, document, audio, video). Any collection that does not declare its own accepted_mime_types or accepted_extensions inherits these.

Some formats are deliberately excluded from the allowlist and must be opted into explicitly per collection:

  • SVG, because it can carry <script>.
  • ICO, because of legacy CVEs.
  • HTML, JS, PHP, EXE, BAT, SH, because they are executable.
  • ZIP, RAR, 7Z, because they are containers with zip-slip and hidden-content vectors.

To allow any of these, override accepted_mime_types and accepted_extensions in the specific collection along with your own validation policy.

Type max_file_size (KB) Notable defaults
image 10240 (10 MB) format: webp, quality: 90, variant_quality: 85, max_width: 1920, max_height: 1080, plus thumbnail (300x300), medium (800x800), large (1600x1600) variants
document 20480 (20 MB) PDF, Word, OpenDocument, RTF, plain text, Markdown, Excel, CSV, PowerPoint, EPUB. No variants (documents use rasterized previews)
audio 5120 (5 MB) MP3, WAV, OGG, M4A, FLAC, AAC, Opus, WebM
video 102400 (100 MB) MP4, MOV, WebM, M4V, MKV, AVI, OGV

The image accepted_extensions are jpeg, jpg, png, gif, webp, heic, heif, bmp, tiff. See the Images, variants and watermarks section for how format, quality, and variants drive processing.

Collections

A collection is the upload policy for one business context (avatars, a gallery, identity documents). Each entry declares where files land, who can read them, and how they are processed. Collections are the central concept of the package, so this is the largest block.

'collections' => [
    'avatar' => [
        'disk'   => 'media',
        'access' => 'public',
        'single' => true,
        'types'  => [
            'image' => [
                'variants' => [
                    'small'  => ['width' => 64,  'height' => 64,  'fit' => true],
                    'medium' => ['width' => 128, 'height' => 128, 'fit' => true],
                    'large'  => ['width' => 256, 'height' => 256, 'fit' => true],
                ],
            ],
        ],
    ],
],

The package ships four example collections (avatar, gallery, documents, identity) so you can see the shape. Replace them with your own.

Anatomy of a collection entry

Key Type Purpose
disk string The Storage::disk() name from your config/filesystems.php. The package never duplicates credentials; it resolves this disk. There is no default, a missing disk is intentionally an error.
access string public (CDN-direct), signed (temporary signed URL), or stream (controller with audit and viewer bind). See the Upload modes and Access control sections.
single bool true keeps one file per owner. Replacing the file removes the previous one.
sensitive bool Binds access to the authenticated viewer and re-validates on each request. See the Sensitive content section.
encrypt bool Encrypts the binary before it is stored on the backend. See the Sensitive content and encryption section.
ttl_hours int Files in the collection expire after this many hours. The laracrate:purge-expired command (run hourly) deletes them, cascading to variants and the backend binary. Useful for temp_uploads, expiring exports, or unpromoted drafts.
quota_bytes int Storage limit your app can check with UsageReporter before accepting more uploads. The package does not enforce quotas itself. See the Multi-tenancy, buckets and usage section.
track_usage bool Maintain live per-owner usage counters (file count and total bytes) for this collection in the laracrate_folderables table, updated by the file observer on create and delete, and rebuildable with laracrate:recompute-usage. See the Multi-tenancy, buckets and usage section.
component string Blade component used for default rendering.
placeholder string Fallback asset when the file does not exist (highest priority in placeholder resolution, below).
types array Per-type processing config (image, document, audio, video), keyed by type. Each type block can carry accepted_mime_types, accepted_extensions, max_file_size, variants, preview, and so on, overriding the global defaults.
variants array Image variant definitions: ['name' => ['width' => ..., 'height' => ..., 'fit' => bool, 'watermark' => bool]]. See the Images, variants and watermarks section.
preview array Rasterized preview config for documents and videos. Documents accept page, width, engine, and nested variants. Videos accept frame_at and nested variants. See the Video and PDF previews section.
extract / extract_text bool or array Whether to extract text from the file (extract_text is a legacy alias for extract). See the per-type extraction note below and the Text extraction section.
embed bool or array Whether to generate embeddings. See the Text extraction, embeddings and search section.
actions array Custom actions to attach to the collection.
models array Per-model scoping (covered next).

The models block: per-model scoping

By default any model using HasFiles can write to a collection with the same config. Declaring models restricts the collection to specific owner types and merges a per-model override on top of the base config. Resolution lives in EduLazaro\Laracrate\Support\CollectionConfig::resolve().

'documents' => [
    'disk'   => 'documents',
    'access' => 'signed',
    'models' => [
        // Cases get the stricter, viewer-bound stream access.
        'case'         => ['access' => 'stream', 'sensitive' => true],
        // Organizations keep signed access but skip PDF previews.
        'organization' => ['types' => ['document' => ['preview' => false]]],
    ],
],

Semantics when models is present:

  • Only the listed keys may use the collection. Keys are matched against the morph alias or the fully qualified class name, normalized through Relation::morphMap(). A model not listed triggers EduLazaro\Laracrate\Exceptions\CollectionNotAllowedForModel.
  • The per-model override is merged over the base with array_replace_recursive, so nested structures (like variants) merge key by key.
  • The models key itself is stripped from the resolved config.
  • A per-model override cannot relocate the binary. The object key is always /{id}/{collection}/{file} (tenant-prefixed when the file has a tenant), built by CreateFileAction. It is not configurable through a path key.
  • Resolving with no model (CollectionConfig::resolve($collection) with a null second argument) returns the base config without merging, which is what tooling that iterates collections without a model context receives.

You can check whether a collection is scoped with CollectionConfig::isRestricted($collection).

Per-type extract and embed

The extract and embed keys accept either a boolean or an array, resolved by EduLazaro\Laracrate\Support\ExtractionResolver:

'extract' => true,                 // all types in the collection
'extract' => ['document', 'image'] // only these file types
'extract' => ['video.visual']      // an opt-in extra, matched by prefix

When the value is an array, it matches the file's type by exact value or by the type. prefix (so video matches video.visual and vice versa). The legacy boolean key extract_text is still honored for backward compatibility. Apps can register a per-file override with ExtractionResolver::setOverrideResolver(callable), where the callable receives the File and returns an override array (or null). See the Text extraction section.

Placeholders

The fallback chain when a file or variant does not exist or is not the type the render expects. Override these in your published config.

'placeholders' => [
    'default'  => '/img/laracrate/file.svg',
    'image'    => '/img/laracrate/image.svg',
    'video'    => '/img/laracrate/video.svg',
    'audio'    => '/img/laracrate/audio.svg',
    'document' => '/img/laracrate/document.svg',
],

Resolution runs from most specific to most general:

  1. config('laracrate.collections.{name}.placeholder')
  2. config('laracrate.placeholders.{type}')
  3. config('laracrate.placeholders.default')

Dynamic placeholders (initials avatars, generated SVGs)

A placeholder can be a callable instead of a string. When it is, fileLink() and fileRender() invoke it with (string $collection, string $type, Model $model) and use the returned string as the URL. This is how you render a generated fallback (an initials avatar, a ui-avatars URL, a per-model SVG) when a model has no file.

Use a callable array ([Class::class, 'method']), not a Closure. Closures are not serializable, so a Closure placeholder breaks php artisan config:cache in production.

// config/laracrate.php
'collections' => [
    'avatar' => [
        'disk'        => 'media',
        'access'      => 'public',
        'single'      => true,
        'placeholder' => [\App\Support\InitialsAvatar::class, 'placeholderFor'],
    ],
],
// app/Support/InitialsAvatar.php
namespace App\Support;

class InitialsAvatar
{
    // The signature the package calls: (collection, type, model).
    public static function placeholderFor($collection, $type, $model): ?string
    {
        return self::dataUri($model?->name);
    }

    public static function dataUri(?string $name, int $size = 200): ?string
    {
        $name = trim((string) $name);
        if ($name === '') {
            return null;
        }

        $initials = strtoupper(mb_substr($name, 0, 1));      // 1 to 2 letters from the name
        $bg       = '#1E40AF';                               // derive a color from the name if you want variety
        $font     = (int) round($size * 0.42);

        $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' . $size . ' ' . $size . '">'
             . '<rect width="100%" height="100%" fill="' . $bg . '"/>'
             . '<text x="50%" y="50%" fill="#fff" font-size="' . $font . '" font-family="sans-serif"'
             . ' text-anchor="middle" dominant-baseline="central">'
             . htmlspecialchars($initials, ENT_QUOTES | ENT_XML1) . '</text></svg>';

        return 'data:image/svg+xml;base64,' . base64_encode($svg);
    }
}

Now $user->fileLink('avatar', 'small') returns the uploaded avatar when it exists, or the inline initials SVG when it does not, with no null checks in your views. A callable placeholder needs the model, so it resolves only through fileLink() and fileRender() (which carry it), not through the bare $file->placeholderFor(). String placeholders work everywhere.

URL strategy

TTLs and cache windows for the URL accessors. See the Displaying files and Access control sections for how each mode uses them.

'urls' => [
    'signed_ttl'             => 5,
    'signed_cache_ttl'       => 4,
    'sensitive_redirect_ttl' => 10,
    'route_signed_ttl'       => 15,
    'bind_to_user'           => true,
],
Key Default Meaning
signed_ttl 5 Minutes a backend signed URL stays valid (for access: signed).
signed_cache_ttl 4 Minutes the server-side cache of a signed URL is kept.
sensitive_redirect_ttl 10 Seconds for the ultra-short signed URL issued after validation in the stream controller.
route_signed_ttl 15 Minutes the route HMAC for /laracrate/files/{slug}/stream stays valid.
bind_to_user true Binds stream URLs to the user identity, re-validating on demand.

Policies

The package uses PolicyRegistry as the canonical place to declare authorization per fileable_type. When register_gate is true (default), the service provider also binds FilePolicy to the Laravel Gate so you can use the native ergonomics.

'policies' => [
    'register_gate' => true,
],

With the bridge on, the Gate abilities view, update, and delete map to the registry methods canView, canEdit, and canDelete, so @can('view', $file), $user->can('update', $file), and Route::middleware('can:view,file') all work. Set register_gate => false if your app already registers its own FilePolicy. See the Access control and authorization section.

Streaming

Routing and audit options for the controller that serves access: stream files.

'stream' => [
    'route_prefix'        => 'laracrate/files',
    'route_name_prefix'   => 'laracrate.files',
    'middleware'          => ['web', 'auth'],
    'increment_downloads' => true,
    'log_access'          => true,
],

The laracrate/files prefix avoids collisions with an existing FileController under /files/..., which is common. increment_downloads bumps the file download counter, and log_access audits each download. See the HTTP endpoints section.

Status polling

Endpoints to poll processing status after an async upload.

'status' => [
    'route_prefix' => 'laracrate/files',
    'middleware'   => ['web', 'auth'],
],

GET /laracrate/files/{slug}/status returns one file, POST /laracrate/files/status accepts a batch of slugs. See the HTTP endpoints section.

Uploads

Routing and a disk allowlist for the direct presigned upload endpoints. The multipart block inherits this middleware when its own is null.

'uploads' => [
    'route_prefix'  => 'laracrate/uploads',
    'middleware'    => ['web', 'auth'],
    'allowed_disks' => [],
],
Key Default Meaning
route_prefix laracrate/uploads URL prefix for the presign and cancel endpoints.
middleware ['web', 'auth'] Middleware for the upload route group. Authorization is your app's responsibility.
allowed_disks [] Allowlist of disks a client may upload to directly, enforced in the presign and multipart init endpoints. Empty means no restriction.

These are the defaults; override them in your published config to restrict allowed_disks or change the prefix or middleware. See the HTTP endpoints and Upload modes sections.

Multipart upload

Tuning for large uploads to S3/R2. The server does not force multipart; the frontend chooses based on file.size. These values are the recommended thresholds.

'multipart' => [
    'threshold'       => 100 * 1024 * 1024,  // 100 MB
    'part_size'       => 10  * 1024 * 1024,  // 10 MB
    'expire_minutes'  => 60,
    'url_ttl_minutes' => 60,
    'route_prefix'    => 'laracrate/multipart',
    'middleware'      => null,
],
Key Default Meaning
threshold 100 MB Below this the client should use a single presigned PUT, at or above it multipart.
part_size 10 MB Bytes per part (S3 minimum is 5 MB). 10 MB means 100 parts for 1 GB, 800 for 8 GB.
expire_minutes 60 TTL of the multipart session. After it, laracrate:abort-stale-multipart aborts it.
url_ttl_minutes 60 TTL of the per-part presigned URLs.
route_prefix laracrate/multipart URL prefix for the multipart endpoints.
middleware null Middleware for the route group. null inherits from the uploads block.

See the Upload modes and HTTP endpoints sections.

Image

Image processing options used by the optimize and variant steps. Do not confuse image.driver (used for variants and optimization) with pdf_preview_engine (used for PDF rasterization).

'image' => [
    'driver'             => 'imagick',
    'optimize_originals' => false,
    'max_width'          => 1920,
    'max_height'         => 1920,
    'quality'            => 85,
],

driver is imagick (recommended) or gd. When optimize_originals is true, the original is re-encoded to webp within max_width/max_height at quality. See the Images, variants and watermarks section.

PDF preview engine

Selects the engine that rasterizes a PDF page into a PNG for the preview variant.

'pdf_preview_engine' => 'auto',
Value Requirements Notes
pdftoppm poppler-utils (apt install poppler-utils) Does not need Ghostscript or any change to ImageMagick policy.xml.
imagick PHP imagick extension, Ghostscript (gs), and the PDF coder enabled in ImageMagick policy.xml Heavier setup.
auto tries pdftoppm, falls back to imagick Default.

You can override the engine per collection inside the preview block:

'preview' => ['page' => 1, 'width' => 600, 'engine' => 'pdftoppm'],

See the Video and PDF previews section.

Video

Defaults for ffmpeg-based transcoding when a collection does not override them.

'video' => [
    'max_width'    => 1920,
    'max_height'   => 1920,
    'bitrate_kbps' => 2500,
],

See the Video and PDF previews section.

Encryption

Driver used to encrypt the binary for collections with encrypt: true.

'encryption' => [
    'driver' => 'laravel',
],

laravel uses the framework Crypt facade. See the Sensitive content and encryption section.

Embeddings

Opt-in text extraction and vector embeddings. enabled is the master switch: when false, nothing is embedded even if a collection asks for it.

'embeddings' => [
    'enabled'           => false,
    'provider'          => 'openai',
    'api_key'           => env('LARACRATE_EMBEDDINGS_API_KEY'),
    'model'             => env('LARACRATE_EMBEDDINGS_MODEL', 'text-embedding-3-small'),
    'dimensions'        => 1536,
    'chunk_size'        => 1000,
    'chunk_overlap'     => 100,
    'batch_size'        => 16,
    'extractors'        => [],
    'min_text_per_file' => 100,
],
Key Default Meaning
enabled false Master switch for the whole feature.
provider openai Provider implementing EmbeddingProvider. The package ships an OpenAI provider; the real binding is done in LaracrateServiceProvider.
api_key env LARACRATE_EMBEDDINGS_API_KEY If null, the OpenAI provider falls back to OPENAI_API_KEY.
model text-embedding-3-small Provider model, overridable per environment.
dimensions 1536 Vector dimensions. Fixed by the model, change only when you change the model.
chunk_size 1000 Approximate tokens per chunk. 0 disables chunking (one row per file).
chunk_overlap 100 Token overlap between consecutive chunks.
batch_size 16 Chunks per request to the provider.
extractors [] Ordered chain of text extractors. Empty means the built-in defaults.
min_text_per_file 100 Minimum characters an extractor must produce to count as successful. Below this, the next extractor in the chain is tried.

The extractors chain runs in order; if one returns less than min_text_per_file characters, the next is tried. A typical scanned-PDF chain is PdfTextExtractor (fast, native PDFs) then OcrPdfTextExtractor (LLM OCR) then PlainTextExtractor. See the Text extraction, embeddings and search (RAG) section for the full behavior.

Chunks

Selects the ChunkStore backend that persists and searches text chunks.

'chunks' => [
    'driver' => env('LARACRATE_CHUNKS_DRIVER', 'mysql'),
],
Driver Storage Notes
mysql laracrate_file_chunks (SQL LIKE keyword match plus cosine similarity in PHP) No external dependencies. Scales well up to roughly 5K chunks per scope.
meilisearch A Meilisearch index with user-provided embeddings Native hybrid search (BM25 plus vector) with semanticRatio server-side. Requires meilisearch/meilisearch-php and a Meilisearch\Client binding in your app.

Custom backends (Qdrant, pgvector) can bind ChunkStore directly. See the Text extraction, embeddings and search (RAG) section.

Meilisearch

Applies only when chunks.driver is meilisearch.

'meilisearch' => [
    'index'    => env('LARACRATE_MEILISEARCH_INDEX', 'laracrate_file_chunks'),
    'embedder' => env('LARACRATE_MEILISEARCH_EMBEDDER', 'default'),
],

OCR

Config for OcrPdfTextExtractor, the fallback for scanned PDFs. The provider is selectable via env, and each provider's API key falls back to the generic key for that provider.

'ocr' => [
    'provider'  => env('LARACRATE_OCR_PROVIDER', 'anthropic'),

    // Fallback language for the auto-generated image description when the
    // image has no visible text to infer the language from (image OCR only).
    'locale'    => 'en',

    'anthropic' => [
        'api_key' => env('LARACRATE_ANTHROPIC_API_KEY') ?: env('ANTHROPIC_API_KEY'),
        'model'   => env('LARACRATE_OCR_ANTHROPIC_MODEL', env('LARACRATE_OCR_MODEL', 'claude-haiku-4-5')),
    ],
    'openai' => [
        'api_key' => env('LARACRATE_OPENAI_API_KEY') ?: env('OPENAI_API_KEY'),
        'model'   => env('LARACRATE_OCR_OPENAI_MODEL', env('LARACRATE_OCR_MODEL', 'gpt-4o-mini')),
    ],
],

provider is anthropic (default, model claude-haiku-4-5) or openai (model gpt-4o-mini). locale only affects image OCR (OcrImageTextExtractor): the image description follows the visible text's language, falling back to this locale when the image has no text. See the Text extraction section.

Watermark

Settings for the watermark embedded into specific variants. The original (master) is never watermarked; only variants that declare 'watermark' => true are. The defaults here are global, applied wherever a variant opts in.

'watermark' => [
    'image_path' => env('LARACRATE_WATERMARK_IMAGE', null),
    'size'       => 0.40,
    'opacity'    => 30,
    'position'   => 'center',
    'text'       => [
        'content'         => null,
        'font_size_ratio' => 0.0195,
        'color'           => 'rgba(255, 255, 255, 0.60)',
        'position'        => 'bottom-left',
        'padding'         => 20,
        'font_path'       => null,
    ],
],
Key Default Meaning
image_path env LARACRATE_WATERMARK_IMAGE, else null Absolute path or path relative to public_path() of the PNG to overlay. null applies no image.
size 0.40 Watermark width as a fraction of the variant width (0.0 to 1.0).
opacity 30 Overlay opacity (0 to 100).
position center One of center, top-left, top-right, bottom-left, bottom-right.
text.content null Optional auxiliary text: null, a fixed string, or a closure(File): ?string for dynamic text (set via a provider or published config, not env).
text.font_size_ratio 0.0195 Font size as a fraction of the image width.
text.color rgba(255, 255, 255, 0.60) CSS rgba color.
text.position bottom-left One of bottom-left, bottom-right, top-left, top-right.
text.padding 20 Padding from the edge, in pixels.
text.font_path null Path to a .ttf font, or null for the system font.

See the Images, variants and watermarks section for the mechanics.

UI

Default theme for the <livewire:laracrate-uploader> component when no theme= prop is passed.

'ui' => [
    'default_theme' => env('LARACRATE_THEME', 'default'),
],

The built-in themes are default, brutalist, material, ios, glassmorphism, neon, minimal, neumorphism, chatgpt, claude, and studio. For a custom theme, publish the views with vendor:publish --tag=laracrate-views and add your blade under resources/views/vendor/laracrate/uploader/themes/. See the Livewire components and themes section.

Queue

Routing for the package jobs (variants, previews, embeddings) dispatched by ProcessFileJob.

'queue' => [
    'connection' => env('LARACRATE_QUEUE_CONNECTION', null),
    'name'       => env('LARACRATE_QUEUE_NAME', 'default'),
],

connection of null uses your default queue connection. All processing runs on the queue by design, so the user's upload stays instant. See the Processing pipeline section.