Reference
Reference
Artisan commands
Laracrate ships three commands for the maintenance work that keeps storage costs and usage counters honest. None of them run on their own: register the ones you need in your scheduler.
use Illuminate\Support\Facades\Schedule; Schedule::command('laracrate:abort-stale-multipart')->hourly(); Schedule::command('laracrate:purge-expired')->hourly(); Schedule::command('laracrate:recompute-usage')->daily();
laracrate:abort-stale-multipart
Aborts multipart upload sessions that passed their expires_at without completing. This is the important one for production: until a multipart session is aborted, the parts already uploaded to S3/R2 keep occupying storage and billing you indefinitely. The command loads stale sessions via the MultipartUpload::stale() scope and runs AbortMultipartUploadAction on each, marking them MultipartUploadStatus::EXPIRED.
php artisan laracrate:abort-stale-multipart php artisan laracrate:abort-stale-multipart --dry-run --limit=200
| Option | Default | Effect |
|---|---|---|
--dry-run |
off | List the stale sessions, abort nothing. |
--limit= |
500 |
Maximum sessions to process per run. |
See the Multipart uploads coverage in the Upload modes section for how these sessions are created.
laracrate:purge-expired
Deletes files from collections that declare a TTL and have outlived it. A collection opts in by setting ttl_hours in its config/laracrate.php entry. The command walks every configured collection, computes a cutoff of now()->subHours($ttlHours), and force-deletes top-level files (parent_id null) created before it. It uses forceDelete() on purpose so the FileObserver purges the binary and its sidecars from the backend and cascades to variants.
php artisan laracrate:purge-expired php artisan laracrate:purge-expired --collection=temp_uploads --dry-run
| Option | Default | Effect |
|---|---|---|
--collection= |
all | Restrict to one collection. |
--dry-run |
off | List expired files, delete nothing. |
--limit= |
1000 |
Maximum files to process per collection. |
Collections without a positive ttl_hours are skipped, so this command is safe to schedule even if only some collections are temporary. See the Configuration section for ttl_hours.
laracrate:recompute-usage
Recomputes the laracrate_folderables usage counters (files_count, total_size_bytes) from laracrate_files. The per-row counters are kept up to date by the FileObserver, but they can drift if the observer fails, you import rows by hand, or you restore from a backup. This command re-aggregates the truth, updates last_recomputed_at, and resets orphaned rows to 0/0. It is idempotent, so running it repeatedly always converges to the correct state.
It only touches collections with track_usage enabled in config, unless you name one explicitly.
php artisan laracrate:recompute-usage php artisan laracrate:recompute-usage drive php artisan laracrate:recompute-usage --dry-run
| Argument / Option | Default | Effect |
|---|---|---|
collection (argument) |
all tracked | Restrict to one collection. |
--dry-run |
off | Print the per-row deltas without persisting. |
See the Multi-tenancy, buckets and usage section for how track_usage and the usage counters work.
Events
Laracrate fires plain Laravel events around the processing pipeline so your app can react without coupling to the internals: refresh UI, invalidate caches, notify users, or index vectors into your search engine. Every event uses the Illuminate\Foundation\Events\Dispatchable trait, lives under the EduLazaro\Laracrate\Events namespace, and exposes its payload as public constructor-promoted properties.
use EduLazaro\Laracrate\Events\FileProcessed; use Illuminate\Support\Facades\Event; Event::listen(function (FileProcessed $event) { $event->file->fileable->notify(new FileReady($event->file)); });
| Event | When it fires | Payload |
|---|---|---|
FileProcessingStarted |
Just before the pipeline iterates its steps. The file is already marked ProcessingStatus::PROCESSING. |
File $file |
FileProcessed |
The pipeline finished successfully. The file is already marked ProcessingStatus::COMPLETED. |
File $file |
FileProcessingFailed |
A step threw. The file is marked ProcessingStatus::FAILED and processing_error holds the message. The queue retries if the job has tries left. |
File $file, Throwable $exception |
VariantGenerated |
A child file (parent_id set) is persisted: thumbnail, preview, transcoded copy, watermarked variant, and so on. Fired centrally from the observer, independent of which action created it. |
File $variant, ?File $parent |
EmbeddingsReady |
The embedding step finished and produced at least one new vector. | File $file, int $count (chunks that ended up with an embedding in this pass) |
A common use of EmbeddingsReady is to mirror the generated vectors into your search backend (pgvector, Meilisearch, Qdrant) from a queued listener:
namespace App\Listeners; use EduLazaro\Laracrate\Events\EmbeddingsReady; use Illuminate\Contracts\Queue\ShouldQueue; class IndexFileVectors implements ShouldQueue { public function handle(EmbeddingsReady $event): void { // $event->count chunks now carry an embedding MyVectorIndex::sync($event->file); } }
Register listeners the usual way (an Event::listen call in a service provider, an auto-discovered handle method, or a #[AsEventListener] attribute). See the Processing pipeline section for what runs between FileProcessingStarted and FileProcessed, and the Text extraction, embeddings and search (RAG) section for the embedding step behind EmbeddingsReady.
API reference
A compact, signature-level reference for the public surface of Laracrate. Every entry below is copied from source. For narrative usage of each piece, see the section it belongs to (working with files from your models, displaying files, processing pipeline, and so on).
HasFiles trait
Add use EduLazaro\Laracrate\Concerns\HasFiles; to any model. See the Working with files from your models section for usage.
// Relations and lookups public function files(?string $collection = null): MorphMany; // top-level only, ordered by position public function file(string $collection): ?File; // latest, default-first public function getFile(string $collection): ?File; // alias of file() public function defaultFile(string $collection): ?File; public function images(?string $collection = null): MorphMany; // Mutations public function addFile( UploadedFile|\EduLazaro\Laracrate\Support\Binary|FileUpload|string $file, string $collection, array $data = [], array $slots = [], ?Model $creator = null, ?Model $owner = null, ?Folder $folder = null, ): ?File; public function setFile( string $collection, UploadedFile|\EduLazaro\Laracrate\Support\Binary|FileUpload|string|null $file, array $data = [], ?Model $creator = null, ?Model $owner = null, ): ?File; // replaces (force-deletes existing) public function setDefaultFile(File $file): File; public function deleteFile(File $file, bool $forceDelete = false): bool; public function reorderFiles(string $collection, array $orderedIds): void; // Rendering helpers public function fileLink(string $collection, ?string $variant = null, ?string $forceType = null): ?string; public function fileRender(string $collection, ?string $variant = null, array $attrs = []): HtmlString; // Config and tenant resolution public function getCollectionConfig(string $collection): array; public function getDiskFor(string $collection): string; public function resolveFileTenant(): ?Model; // override per app to point at your tenant model
File model
EduLazaro\Laracrate\Models\File. Table laracrate_files. Route key is slug.
// Relations public function fileable(): MorphTo; public function creator(): MorphTo; public function owner(): MorphTo; public function tenant(): MorphTo; public function parent(): BelongsTo; public function children(): HasMany; public function folder(): BelongsTo; public function chunks(): HasMany; // ordered by chunk_index public function chunk(): HasOne; // chunk_index = 0 public function slots(): BelongsToMany; public function contents(): HasMany; // @deprecated alias of chunks() public function content(): HasOne; // @deprecated alias of chunk() public function effectiveOwner(): ?Model; // explicit owner, else creator // Variant navigation (dot notation) public function variant(string $path): self; // falls back to nearest ancestor, never null public function variantOrFail(string $path): self; // throws RuntimeException if a link is missing public function createVariant(string $variantName, array $overrides): self; // Storage key helpers (path stores the full object key) public function getKeyAttribute(): string; // $file->key public function siblingKey(string $newName): string; public function variantKey(string $newName): string; // URLs and rendering public function url(?string $forceType = null): ?string; // public/signed/stream per access public function placeholderFor(string $type): string; public function getLinkAttribute(): ?string; // $file->link, alias of url() public function getPreviewLinkAttribute(): string; // $file->preview_link, thumbnail or placeholder public function streamUrl(): string; // signed package route, TTL config laracrate.urls.route_signed_ttl public function downloadUrl(): string; public function previewUrl(): string; // Folder public function moveToFolder(?Folder $folder): void; // State helpers public function publish(): self; public function unpublish(): self; public function makeDefault(): self; // Type and state predicates public function isVariant(): bool; public function isTopLevel(): bool; public function isSensitive(): bool; public function createdByUser(): bool; public function createdByAgent(): bool; public function createdAutomatically(): bool; public function isMultiTenant(): bool; public function isImage(): bool; public function isVideo(): bool; public function isAudio(): bool; public function isDocument(): bool; public function isPdf(): bool; // Extracted text and chunks (sidecar artifacts on the disk) public function extractedContent(): ?ExtractedContent; public function extractedText(): ?string; public function chunkText(int $chunkIndex): ?string; public function chunksJsonl(): array; public function hasEmbeddings(): bool; // Authorization (delegates to PolicyRegistry) public function canView(?Model $user): bool; public function canEdit(?Model $user): bool; public function canDelete(?Model $user): bool;
Query scopes:
File::topLevel(); // whereNull('parent_id') File::withDescendants($depth = 2); // eager-load children to depth File::withVariants($depth = 3); // eager-load the variant tree File::forTenant($tenant); File::ordered(); // position, then id File::published(); File::unpublished(); File::default();
Scopes, and the state helpers that return self, chain like any Eloquent call:
File::published()->ordered()->forTenant($org)->get(); $file->makeDefault()->publish();
StorageManager service
EduLazaro\Laracrate\Services\StorageManager. The package facade over Storage::disk(). Resolve it with app(StorageManager::class). See the Upload modes and Multi-tenancy, buckets and usage sections for context.
public function urlFor(File $file): ?string; // public/signed/stream based on $file->access public function diskFor(File $file): \Illuminate\Contracts\Filesystem\Filesystem; public function resolveDisk(string $disk): \Illuminate\Contracts\Filesystem\Filesystem; // honors 'tb:{id}' public function configFor(string $disk): array; public function readBinary(File $file): string; public function writeBinary(string $disk, string $key, string $content, ?string $mime = null): bool; public function deleteFromBackend(string $disk, string $key): bool; public function moveServerSide(string $disk, string $fromKey, string $toKey): bool; // S3 copyObject, no PHP round-trip public function batchDelete(string $disk, array $keys): int; public function presignedUpload(string $disk, string $key, string $mime, ?int $maxSize = null, int $minutes = 15): array; public function withLocalCopy(File $file, callable $fn): mixed; // temp file for ffmpeg/Imagick public function getCollectionConfig(string $collection, array $modelOverride = [], ?string $morphAlias = null): array; public function getTypeConfig(string $collection, string $type, ?string $morphAlias = null): array; public function acceptsType(string $collection, string $type, ?string $morphAlias = null): bool; public function s3ClientOf(string $disk): ?\Aws\S3\S3Client; public function driverOf(string $disk): string;
UsageReporter service
EduLazaro\Laracrate\Services\UsageReporter. Storage usage aggregation. Resolve it with app(UsageReporter::class). Each method returns a UsageStats DTO. See the Multi-tenancy, buckets and usage section.
public function forTenant(Model $tenant, bool $excludeTrashed = false): UsageStats; public function forCreator(Model $creator, bool $excludeTrashed = false): UsageStats; public function forCollection(string $collection, ?Model $tenant = null, bool $excludeTrashed = false): UsageStats; public function global(bool $excludeTrashed = false): UsageStats;
Contracts
Bind your own implementation in a service provider to swap any of these. The defaults are documented in their feature sections.
// EduLazaro\Laracrate\Contracts\ChunkStore public function store(File $file, array $chunks): int; public function getByFile(File $file): \Illuminate\Support\Collection; public function search(string $query, array $filters = [], array $options = []): \Illuminate\Support\Collection; public function deleteByFile(File $file): void; public function driverName(): string;
// EduLazaro\Laracrate\Contracts\EmbeddingProvider public function embed(array $texts): array; // one vector per text, same order public function dimensions(): int; public function model(): string; // e.g. "text-embedding-3-small" public function name(): string; // e.g. "openai"
// EduLazaro\Laracrate\Contracts\TextExtractor public function supports(File $file): bool; public function extract(File $file): \EduLazaro\Laracrate\Support\ExtractedContent;
// EduLazaro\Laracrate\Contracts\FileActionInterface public function handle(File $file): void; public function priority(): int; // optional: public function supports(File $file): bool; // assumed true if absent