← Laracrate 4 / 17

Core concepts

Core concepts

Core concepts

Before you reach for the reference sections, here is the vocabulary Laracrate uses. Every file is a row in laracrate_files (an Eloquent EduLazaro\Laracrate\Models\File) decorated with the concepts below. Read this once and the rest of the docs will click.

Collection. The business grouping a file belongs to, stored in the collection column (for example avatar, documents, lawsuit-document). A collection is declared in config('laracrate.collections.*') and decides the disk, the access mode, the accepted types, the variants to generate, and whether text extraction or embeddings run. When you attach a file to one of your models you always name its collection (see the Working with files from your models section).

Type. A coarse media class stored in the type column and cast to the EduLazaro\Laracrate\Enums\FileType enum: IMAGE, VIDEO, AUDIO, or DOCUMENT. Laracrate derives it from the MIME with FileType::fromMime() (anything that is not image/*, video/*, or audio/* becomes DOCUMENT). The pipeline picks which steps run per type.

File vs variant. A top-level file has parent_id = null. A variant is a derived file (a thumbnail, an optimized copy, a video or PDF preview) linked to its parent by parent_id and named by the variant column. Variants are real File rows, created with $file->createVariant($name, $overrides), and they inherit the parent scope (fileable, creator, tenant, disk, collection, access). You navigate them with dot notation: $file->variant('preview.thumbnail') walks the tree and falls back to the nearest existing ancestor instead of returning null, while $file->variantOrFail('preview.thumbnail') throws if any link is missing. Helpers: $file->isTopLevel(), $file->isVariant(). See the Images, variants and watermarks section.

The four polymorphic morphs. A file carries four independent morphTo relations. They are orthogonal, not a hierarchy, and you set only the ones your app needs.

Morph Columns Meaning
fileable fileable_type, fileable_id What the file belongs to (a Property, User, Service).
creator creator_type, creator_id Who created it. Null for system-generated files.
owner owner_type, owner_id The semantic owner when it differs from the creator (uploaded on behalf of someone). Null when it matches the creator.
tenant tenant_type, tenant_id The multi-tenant scope. Leave null in single-tenant apps.

Use $file->effectiveOwner() to get the explicit owner or fall back to the creator. See the Multi-tenancy, buckets and usage section.

path is the full object key. The path column stores the complete object key on the disk (directories, filename, and extension together), for example user/1/avatar/01J...webp. It is not just the directory, and you never concatenate it with name to build a key. Always read the key through the accessor $file->key, which trims a stray leading slash. Derived keys come from $file->siblingKey($name) (same directory) and $file->variantKey($name) (sibling variants/ subdirectory). The name column is just basename($path).

Access mode. The access column, cast to EduLazaro\Laracrate\Enums\FileAccess, decides how a file is served:

Case How it is served
PUBLIC Direct CDN URL via Storage::url(). No signature, no audit.
SIGNED Temporary signed URL via Storage::temporaryUrl(), cached server-side.
STREAM Served through the package controller: per-request authorization, audit, optional viewer binding, encryption, and watermark.

Each collection declares its access mode. See the Upload modes and Access control and authorization sections.

Visibility. A separate axis from access, stored in the visibility column and cast to EduLazaro\Laracrate\Enums\FileVisibility: OWNER, GROUP, TENANT, or WORLD. Where access governs the transport, visibility expresses the intended audience for your policies to enforce.

Processing status. The lifecycle of the async pipeline, in the processing_status column and cast to EduLazaro\Laracrate\Enums\ProcessingStatus: PENDING (just created, queued) goes to PROCESSING (steps running) and ends in either COMPLETED (all applicable steps ran) or FAILED (a step threw, with the message in processing_error). The enum offers isTerminal() and isInProgress(). This applies only to top-level files; variants are born COMPLETED. See the Processing pipeline section.

Chunk. A piece of extracted text, modeled by EduLazaro\Laracrate\Models\FileChunk and accessed via $file->chunks() (ordered by chunk_index) or $file->chunk() (the first chunk). Each chunk carries chunk_index, text, tokens, metadata, and an optional context. The lightweight chunk row lives in laracrate_file_chunks; the heavy payload (text plus embedding) lives in laracrate_file_chunk_data. Chunks are the unit of RAG search. See the Text extraction, embeddings and search section.

ChunkStore. The contract (EduLazaro\Laracrate\Contracts\ChunkStore) that abstracts where chunks are persisted and searched. Its methods are store(File $file, array $chunks): int, getByFile(File $file): Collection, search(string $query, array $filters = [], array $options = []): Collection, deleteByFile(File $file): void, and driverName(): string. The shipped drivers are MysqlChunkStore (LIKE plus cosine similarity in PHP) and MeilisearchChunkStore (native hybrid BM25 plus vector). Bind your own implementation (Qdrant, pgvector) to swap backends.

Embedding. A numeric vector that captures the meaning of a chunk (1536 dimensions for text-embedding-3-small), produced by an EmbeddingProvider and used for semantic similarity. Use $file->hasEmbeddings() to check whether every chunk has one.

semantic_ratio. The 0-1 weight of semantic ranking versus keyword ranking in ChunkStore::search(), passed in the options array (default 0.7). A value of 0 is keyword only and embeds nothing (no embedding API cost), 1 is fully semantic, and anything above 0 embeds the query.

Slot. A named placeholder a user fills in (for example "ID front", "ID back"), modeled by EduLazaro\Laracrate\Models\FileSlot with extension and type restrictions and quotas. Files attach to slots through the laracrate_file_slot_pivot table, reachable via $file->slots(). See the File slots section.

Folder. An optional logical grouping under a fileable, modeled by EduLazaro\Laracrate\Models\Folder and referenced by the folder_id column (null means the fileable root). Move a file with $file->moveToFolder($folder); the binary key on the backend never changes, only folder_id. See the Folders section.

Multipart. The protocol for large uploads (at or above the configured multipart.threshold, default 100 MB). The binary is split into parts of at least 5 MB, uploaded in parallel with per-part retries and reassembled by ETags. The session is tracked in laracrate_multipart_uploads. See the Upload modes section.

Presigned URL. A cryptographically signed, time-limited URL that authorizes a direct operation against the storage backend (PUT to upload, GET to download) without exposing your credentials. The browser uploads straight to R2 or S3 and your application server never touches the bytes. See the Upload modes and HTTP endpoints sections.

Data model

Laracrate ships eight tables, all prefixed with laracrate_. The prefix avoids collisions with the legacy files table that already exists in many Laravel apps. The class names do not repeat the prefix (File, not LaracrateFile), because the EduLazaro\Laracrate\Models namespace already disambiguates. This follows the Cashier and Media Library convention.

The schema started with three tables and grew. Two renames matter if you are upgrading: laracrate_file_contents was renamed to laracrate_file_chunks, and an intermediate laracrate_file_chunk_data table existed for a few migrations before being folded back into laracrate_file_chunks (its text and embedding columns now live directly on the chunk row). The migrations are idempotent, so a fresh install lands on the final shape below.

Tables

Table Model Purpose
laracrate_files File One row per file (and per variant). The central table.
laracrate_file_chunks FileChunk Extracted text split into chunks, with embeddings for search. One row per chunk.
laracrate_multipart_uploads MultipartUpload Active and historical S3/R2 multipart upload sessions.
laracrate_file_slots FileSlot Named upload slots with per-slot rules (allowed types, count limits).
laracrate_file_slot_pivot (pivot, via File::slots()) Many-to-many link between files and slots.
laracrate_tenant_buckets TenantBucket Per-tenant dedicated bucket overrides for a base disk.
laracrate_folders Folder Folder tree (parent/child plus denormalized path) for organizing files.
laracrate_folderables Folderable Aggregated storage usage counter per (owner, collection).

The laracrate_files.folder_id column links a file to a folder. There is no separate file/folder pivot: a file belongs to at most one folder.

laracrate_files

The File model (EduLazaro\Laracrate\Models\File) wraps this table. Its route key is slug. It uses soft deletes. Columns, grouped by concern:

Identity and hierarchy

Column Type Notes
id bigint Primary key.
slug ulid Unique. Used as the public route key (never expose id).
parent_id bigint, nullable Points at the parent file. Null means top-level. Set means this row is a variant. Cascades on delete.
variant string(50), nullable Variant name (thumbnail, preview, small, ...). Unique together with parent_id.
folder_id bigint, nullable Folder this file lives in. Null means the root of its fileable. Nulls on folder delete.

The four morphs

Each file carries four independent polymorphic relations (see the Core concepts section for why they stay orthogonal):

Columns Relation Meaning
fileable_type, fileable_id fileable() What the file belongs to (a User, Property, Service).
creator_type, creator_id creator() Who or what created the row. Null for system-generated.
owner_type, owner_id owner() Semantic owner when it differs from the creator. Falls back to the creator via effectiveOwner().
tenant_type, tenant_id tenant() Multi-tenant scope (Organization, Workspace). Null for single-tenant apps.

Storage key

Column Type Notes
disk string The Laravel disk name, or a tb:{id} token for a dedicated tenant bucket.
path string The full object key in the disk (directories, filename, extension). Read it through $file->key, never by hand.
name string basename($path) denormalized.
original_name string The filename as uploaded.
extension string(10) Lowercase extension.
mime_type string(100) Detected MIME type.
size unsignedBigInteger Bytes.
digest string(80), nullable Content hash for dedupe or integrity.

Classification

Column Type Notes
context string Defaults to laracrate.default_context. Indexed.
collection string Defaults to laracrate.default_collection. Indexed.
type enum image, video, audio, document. Cast to FileType. Indexed.
category string, nullable Free-form app category. Indexed.

Access flags

Column Type Notes
access enum public, signed, stream. Defaults to signed. Cast to FileAccess. Indexed.
visibility string, nullable Free-form visibility label. The FileVisibility enum (owner, group, tenant, world) is available if you want to use its values. Indexed.
sensitive boolean Defaults to false. Indexed.
is_encrypted boolean Defaults to false.

Metadata and presentation

Column Type Notes
title string, nullable Display title.
description text, nullable Display description.
label string(100), nullable Short label.
default boolean Marks the default file in its (fileable + collection) group.
position unsignedInteger Sort order. Defaults to 0.
published boolean Defaults to true. Indexed.
is_verified boolean Defaults to false. Indexed.
metadata json, nullable Free-form bag. Cast to array.

Media metadata

Column Type Notes
duration unsignedInteger, nullable Seconds, for video and audio.
width, height unsignedInteger, nullable Pixels, for image and video.
bitrate unsignedInteger, nullable For video and audio.
sample_rate unsignedInteger, nullable For audio.

Processing and audit

Column Type Notes
processing_status enum, nullable pending, processing, completed, failed. Cast to ProcessingStatus.
processing_error text, nullable Error message when a step throws.
processing_started_at timestamp, nullable When the pipeline began.
processing_extractor string(255), nullable Class of the text extractor used.
processing_provider string(50), nullable Embedding or extraction provider.
processing_model string(100), nullable Model used for embedding or extraction.
summary text, nullable Optional LLM-distilled summary of the extracted content.
downloads_count unsignedInteger Defaults to 0.
last_downloaded_at timestamp, nullable Updated on each served download.

Indexing trackers

These three timestamps record where a file's chunks have been indexed, so you can re-index incrementally and migrate between search backends safely (see the Text extraction, embeddings and search section).

Column Type Notes
mysql_indexed_at timestamp, nullable Chunks ready in MySQL (LIKE keyword plus cosine in PHP). Indexed.
meili_indexed_at timestamp, nullable Chunks pushed to Meilisearch. Indexed.
storage_indexed_at timestamp, nullable .chunks.jsonl backup written.

Timestamps: created_at, updated_at, plus deleted_at (soft deletes).

laracrate_file_chunks

Wrapped by FileChunk. One row per chunk of extracted text. A collection with no chunking stores everything in a single row at chunk_index 0. The foreign key to laracrate_files cascades on delete.

Column Type Notes
id bigint Primary key.
file_id bigint Parent file. Cascades on delete.
chunk_index unsignedInteger Position within the file. Defaults to 0. Unique together with file_id.
context string(30), nullable Discriminator for multi-section extractions (text for OCR verbatim, description for a generated visual description, or any opaque label). Indexed with file_id.
text longText, nullable Chunk text. The column has a FULLTEXT index, though the MySQL chunk store currently keyword-matches with SQL LIKE.
embedding json, nullable Embedding vector. Cast to array. Cosine similarity is computed in PHP.
tokens unsignedInteger, nullable Token count for the chunk.
metadata json, nullable Free-form bag (page numbers, etc). Cast to array.

File::chunks() returns these ordered by chunk_index, and File::chunk() returns the single chunk_index 0 row. The older contents() and content() relations are kept as deprecated aliases for apps migrating from the previous table name.

laracrate_multipart_uploads

Wrapped by MultipartUpload. One row per multipart upload session against an S3-compatible disk. Small files use a single PUT and never touch this table. Completed and aborted rows are kept as an audit trail rather than deleted (see the Upload modes section).

Key columns: upload_id (unique, the provider's id), disk, key, mime_type, expected_size, part_size, total_parts, and status (enum active, completed, aborted, expired, cast to MultipartUploadStatus). It mirrors the file morphs with creator_*, tenant_*, and fileable_* columns plus collection, and links to the resulting row via file_id (nulls on file delete). Lifecycle timestamps: expires_at, completed_at, aborted_at, plus an error column.

laracrate_file_slots and laracrate_file_slot_pivot

FileSlot wraps laracrate_file_slots: named upload slots with rules (see the File slots section). Columns: tenant_type/tenant_id and context_type/context_id for scoping, name, description, color, allowed_extensions (json array), allowed_types (json array of FileType values), max_files_per_creator, max_files_total, and position.

laracrate_file_slot_pivot is the many-to-many link, with file_id and file_slot_id (both cascade on delete, unique together). It has no dedicated model: reach it through File::slots() or FileSlot::files().

laracrate_tenant_buckets

TenantBucket wraps this table: one row overrides a single config disk (the base_disk) with a dedicated bucket for one tenant (see the Multi-tenancy, buckets and usage section). Columns: tenant_type/tenant_id, base_disk, bucket, public_url (nullable), credentials (longText, cast to encrypted:array for bring-your-own-account setups), is_active, and label. Unique on (tenant_type, tenant_id, base_disk). toDiskConfig() merges the base disk config with the override.

laracrate_folders

Folder wraps this table: a parent/child folder tree with a denormalized path kept in sync by an observer (see the Folders section). It uses soft deletes. Columns: a folderable_* morph (the tree owner), parent_id (cascades on delete, null means root), name, path (string(500)), a creator_* morph, and a metadata json bag. Unique on (folderable_type, folderable_id, path).

laracrate_folderables

Despite the name, this is not a pivot. Folderable wraps an aggregated usage counter, one row per (folderable_type, folderable_id, collection), maintained in real time by the file observer when a collection has track_usage enabled. Columns: the folderable_* morph, collection, total_size_bytes, files_count, folders_count, and last_recomputed_at. Unique on (folderable_type, folderable_id, collection). The laracrate:recompute-usage command rebuilds it if you suspect drift.