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

Dashed Files Laravel Package

dashed/dashed-files

Dashed Files is a Laravel package for managing files with a clean, expressive API. It provides helpers for storing, retrieving, and organizing files, integrating smoothly with Laravel's filesystem and storage drivers for simple, reliable file handling in your applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Because the package is early-stage with no dependents, treat the repository itself as the documentation. Read these three things before installing:

  1. composer.json — confirm the Laravel/PHP version constraints and whether it pulls in intervention/image, league/flysystem-aws-s3-v3, or a queue driver.
  2. src/DashedFilesServiceProvider.php — lists every publishable tag, config key, migration, and binding. This is the real API surface.
  3. database/migrations/ — tells you the schema (single files table vs. per-model tables, polymorphic columns, ordering columns).

Minimal install:

composer require dashed/dashed-files

php artisan vendor:publish --tag=dashed-files-config
php artisan vendor:publish --tag=dashed-files-migrations
php artisan migrate

php artisan storage:link   # only if using the public disk

Then set your disk and base path in config/dashed-files.php, and make sure the matching disk exists in config/filesystems.php:

// config/dashed-files.php
'disk' => env('DASHED_FILES_DISK', 'public'),
'path' => 'uploads',

First use case: attach an upload to an existing Eloquent model. Add the package trait to the model, then accept the file from a controller or Livewire component and hand it to the package's upload entry point rather than calling Storage::putFile() yourself — going through the package is what creates the DB record, generates the path, and wires up conversions.

Verify the exact trait name and upload method signature from the source before writing code; the README is likely thinner than the implementation.

Implementation Patterns

Trait-based attachment. The dominant pattern in Laravel file packages is a HasFiles-style trait on the owning model, giving you a morph relation plus a helper for the "primary" file. Expect to write:

class Post extends Model
{
    use HasFiles;
}

$post->attachFile($request->file('cover'), collection: 'cover');
$post->file('cover')?->url();

Substitute the package's real method names. The important habit is to always go through the relation/helper so path generation and cleanup stay consistent.

Collections / groups. Most implementations support a named group (cover, gallery, attachments) so a model can hold several independent file sets. Use collections liberally instead of adding nullable columns like cover_path — the latter defeats the point of the package and will drift out of sync with deletions.

Single vs. multiple. For a "one image" field, look for a singleFile/replace variant. If it doesn't exist, enforce it yourself in a form request or an observer, because replacing means deleting the old record and the old blob — two steps that are easy to get half-right.

Disk strategy. Keep private documents on a private disk and public imagery on public/s3-public. If the package exposes a per-collection disk override in config, use it rather than branching in your controllers. Generate URLs through the package (->url(), ->temporaryUrl()), never Storage::url($file->path) — the package may add a prefix or signed-URL logic.

Upload pipeline. Typical flow: validated UploadedFile → package stores blob → package writes DB row → optional queued conversion → model relation. Keep the controller thin:

public function store(StorePostRequest $request)
{
    $post = Post::create($request->validated());
    $post->attachFile($request->file('cover'), 'cover');

    return redirect()->route('posts.edit', $post);
}

Conversions / thumbnails. If conversions exist, they should be queue-driven (ShouldQueue) and idempotent. Render a placeholder in the UI while the conversion is pending rather than blocking the request on image processing.

Cleanup and lifecycle. Wire the package's pruning command into the scheduler and register model event hooks (or rely on the package's own deleted listener) so deleting a Post removes its files and blobs. Test that explicitly — orphaned blobs are the classic failure mode of these packages.

API responses. Wrap file output in a small API resource so the shape is stable if the package's toArray() changes:

return [
    'url' => $file->url(),
    'thumb' => $file->url('thumb'),
    'name' => $file->original_name,
    'size' => $file->size,
];

Testing. Use Storage::fake('public') in the test suite and assert both the DB row and Storage::disk('public')->assertExists($file->path). If the package resolves its disk at container-boot time, a fake registered in setUp() may not take effect — check the service provider for a deferred binding or config() read.

Gotchas and Tips

Zero dependents means zero battle-testing. Nothing in the ecosystem has stress-tested this yet. Before committing, read the migration and model source end to end, run the package's own tests if any exist, and pin the version in composer.json. Budget time for reading source when something misbehaves — there will be no Stack Overflow answers.

The "last release 2026-04-08" date is a red flag worth checking. Confirm on Packagist that the tag exists and matches the branch you're reading. A future-dated or unreleased tag often means the repo's default branch has diverged from what Composer will actually install.

Config caching. php artisan config:cache will freeze your disk and path settings. If uploads suddenly land in the wrong place after a deploy, clear the cache first — this burns nearly everyone at least once.

Disk visibility mismatch. A file written with 'visibility' => 'private' on S3 cannot be served by Storage::url(). If images 403 in production but work locally, it's almost always visibility or a missing storage:link, not the package.

Path collisions and overwrites. If the package derives filenames from the original name, two photo.jpg uploads may collide or silently replace each other. Check whether it appends a UUID/hash. If not, supply a custom path generator (usually a config callable or an interface binding) and use Str::uuid().

Validation is your job. Packages typically store whatever they're given. Enforce mimes: and max: in a FormRequest, and prefer MIME sniffing over extension checks. Don't trust getClientOriginalExtension().

Deletion is not automatic unless proven otherwise. Verify with a test: create a model with a file, delete the model, assert the row and the blob are gone. If the package doesn't cascade, add an observer and a scheduled files:prune for the leftovers.

Queue workers need the same config. If conversions are queued, the worker must see identical filesystems.php and dashed-files.php values, plus credentials for the S3 disk. A conversion that works in sync but fails on the worker is a config-parity problem, not a code problem.

Extension points to look for (in rough order of usefulness):

  • Path/filename generator binding — lets you impose your own naming scheme without forking.
  • Custom file model — swap in a subclass if you need extra columns or scopes.
  • Morph map registration — if the package stores model_type as a FQCN, register a morph map early so refactors don't orphan records.
  • Disk resolver — per-collection or per-model disk selection.
  • Conversion definitions — a method or config array on the model, which you should keep thin and delegate to dedicated classes.

Debugging checklist when an upload "works" but produces nothing:

  1. Is the files row actually written? (->files()->count())
  2. Does Storage::disk(config('dashed-files.disk'))->exists($file->path) return true?
  3. Is the symlink present and pointing at the right disk root?
  4. Did a config:cache from an earlier deploy win?

Escape hatch. Because the package is small and unproven, keep your own calls behind a thin service or repository (FileService::store()) rather than scattering package method calls across controllers. If you later need to swap to Spatie Media Library or plain Flysystem, you change one class instead of forty.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core