visiosoft/singlefile-field_type
A Laravel field type from Visiosoft for uploading and managing a single file in Streams/PyroCMS forms. Provides a clean admin input, file selection, and storage integration for models that need one attachment per entry.
visiosoft/singlefile-field_type is a Streams Platform field type (PyroCMS / OpenClassify lineage), not a standalone Laravel package. That distinction drives everything about how you use it: it plugs into the Streams field type registry and is consumed through entry forms and stream assignments rather than being called directly from your application code.
Minimum steps:
anomaly/streams-platform (or the pyrocms/pyrocms / openclassify/* distribution) installed. If you're building a plain Laravel 10/11 app, this package will not be usable — it predates modern Laravel and its composer.json constraints will fight with a current framework.composer require visiosoft/singlefile-field_type
php artisan addon:install visiosoft.field_type.singlefile
If addon:install isn't available in your distribution, place it under vendor/ and register the service provider manually.src/SinglefileFieldType.php — the field type class; this is the whole contract (getInputView(), getValue(), setValue(), config accessors, validation).resources/views/input.blade.php — the upload widget rendered inside Streams forms.resources/config/ and resources/lang/en/addon.php — the config keys and human-readable labels available on the field.src/SinglefileFieldTypeServiceProvider.php — how routes, views, and translations are bound.singlefile field where the core file field type would otherwise be used, when you want exactly one file reference per entry rather than a one-to-many relationship to the Files module. Typical targets: a product datasheet, a hero image, an avatar, an invoice PDF.php artisan make:stream Products products
# then assign via the admin Field assignment screen, choosing "Single File" as the type
Assigning the field programmatically. Streams field assignment is normally done in a migration or a seeder via the FieldRepository / stream's assignField. The field type slug is singlefile; config is passed as the third argument:
$stream->assignField([
'slug' => 'spec_sheet',
'type' => 'singlefile',
'name' => 'Spec Sheet',
], [
'folder' => 'documents',
'allowed_types' => 'pdf,doc,docx',
'max_size' => 20,
]);
Always verify the exact config keys against src/SinglefileFieldType.php and resources/config/ in the installed version — field type config has drifted across Streams releases and keys from the sibling file field type are not guaranteed to map 1:1.
Rendering the input. You never wire the widget yourself. The Streams form builder (Streams\Form\Forms\StreamFormBuilder) picks up getInputView() automatically when the field is part of the stream's assignment. If you have a hand-rolled form, render it with the form builder rather than copying the Blade view:
{!! form('products')->fields('spec_sheet') !!}
Reading the value in application code. Single-file field types typically resolve to a Files module File model (or its identifier, depending on how the adapter persists). Treat the accessor as the source of truth and null-check defensively:
$file = $entry->spec_sheet; // may be a FileModel or ID depending on version
if ($file) {
$url = $file->make()->url(); // Files module presenter
$path = $file->make()->path();
}
Rendering in Antlers/Twig templates. Because the value is a Files model, use the Files presenter and guard on emptiness:
{% if entry.spec_sheet.id %}
<a href="{{ entry.spec_sheet.make.url }}">{{ entry.spec_sheet.name }}</a>
{% endif %}
Storage integration. Uploads flow through the Files module's configured disks (local, S3 via Flysystem). You don't point the field type at a disk directly — configure the disk on the Files module and use the field's folder config to namespace uploads. That means a single bucket/disk can serve both file and singlefile fields.
Validation. Field type config (allowed_types, max_size) is enforced by the Streams validator when the entry form is submitted. For API or importer paths that write to the stream bypassing the form, add your own Validator rules in the controller/service layer — the field type will happily accept a bad reference if you hand it one.
Replacing an existing field. When migrating a stream from file to singlefile, use FieldRepository::update() or a fresh migration that touches the field type slug. Because the database column shape can differ between the two types, always check getDatabaseColumn() on the field type class before changing the type on a live stream.
This package is a compatibility risk, not a feature risk. Last release was Feb 2022; zero stars and zero dependents mean it has almost no real-world exercise and no maintenance signal. Before adopting, check the composer.json for illuminate/* and anomaly/streams-platform constraints and confirm they resolve against your app. If they don't, either pin the framework or vendor the source and adjust the constraints locally — do not expect upstream fixes.
"Singlefile" is a field type, not a field. The addon registers a type; each stream entry still needs its own field assignment. A surprising number of issues come from installing the addon and expecting a singlefile column to appear on an existing stream.
Orphaned files are your responsibility. Single-file fields persist a reference; deleting or replacing the entry value does not necessarily garbage-collect the underlying Files record. If storage cost matters, add cleanup in a stream entry observer or a deleted/updated hook — otherwise replacements accumulate in the Files module.
Config keys are the most common failure point. An unknown folder or an allowed_types string with whitespace (pdf, docx vs pdf,docx) will either silently permit everything or reject every upload. Validate config at assignment time and log it during debugging.
Cache the platform aggressively. Streams caches field types, stream schemas, and addon manifests. After editing the field type source, a config change, or an addon install, run:
php artisan addon:list
php artisan view:clear
php artisan cache:clear
Stale field type registry entries produce "field type not found" errors on already-correct code with depressing regularity.
Debug at the field type boundary. When uploads fail, isolate which layer broke:
getInputView() and view namespace binding.setValue() in src/SinglefileFieldType.php; this is where the request payload is normalized into the stored reference.Extension points are the real value here. The package is small enough to read in one sitting, which makes it an excellent template for building your own field type. Fork the src/SinglefileFieldType.php class, change the type slug in src/*ServiceProvider.php and the manifest, and you have a custom field type that behaves exactly as you need — including multi-file, versioned, and per-locale variants. Overriding the Blade view under resources/views/ is the cheapest customization and survives a composer update better than editing vendor code, but a fork is safer given the release timeline.
Don't build critical paths on it without a fork. With no maintainers and no dependents, treat the upstream repo as a reference implementation rather than a dependency. Vendor it into addons/ under your own namespace, or extract the ~200 lines of field type logic into an internal addon you control.
How can I help you explore Laravel packages today?