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 triggersEduLazaro\Laracrate\Exceptions\CollectionNotAllowedForModel. - The per-model override is merged over the base with
array_replace_recursive, so nested structures (likevariants) merge key by key. - The
modelskey 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 byCreateFileAction. It is not configurable through apathkey. - 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:
config('laracrate.collections.{name}.placeholder')config('laracrate.placeholders.{type}')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.