You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Core]: Upload all document types (not just images) #371
Hello, thank you for your work on this software :)
I would like to make a recommendation following a specific need, for some of my customers I send them documents and it would be nice if there was the possibility of having an insert with the documents for my customers to download. The little plus would also be to have a user folder where I can put large files directly inside without going through the web interface.
Basically, I'd like to upload documents of all types from the web interface, I have PDF, Word (docx), ZIP, Tar.gz. For PDF and Word, putting them in the interface would be simple, but for ZIP or Tar.gz files, it would be more practical to put them in a local folder bearing the user's name. Not all documents are identical, and yes, there is some personal/secret information.
When customers log on, they should see a document insert, so that when they click on it, they find each document and can click on the download button.
Story: As a company admin, I want to upload any type of document (PDFs, contracts, receipts, images) and attach them to invoices, quotes, clients, or expenses, so that all relevant files are stored alongside the corresponding record without needing an external document manager.
v2 context: v2 has an Upload model in the Core module (Modules/Core/Models/Upload.php). It handles file storage via Laravel's Storage facade. The model needs to support more MIME types and be attachable to all major document models via a polymorphic relationship.
Acceptance Criteria:
The uploads table stores files with a polymorphic uploadable_type / uploadable_id.
Accepted MIME types: PDFs, common image types (JPEG, PNG, GIF, WebP), MS Office (docx, xlsx), plain text, ZIP — configurable via a MIME type allow-list in settings.
File size limit configurable in Settings (default 10 MB).
Uploading is available on: Invoice edit, Quote edit, Client (Relation) edit, Expense edit.
A "Documents" section appears at the bottom of each supported edit page.
Files can be downloaded and deleted by the company admin.
Files are stored in the tenant-scoped disk path (storage/company/{company_id}/uploads/).
Implementation:
Extend the Upload model with uploadable() morphTo relationship.
Add FileUpload::make('documents') section to Invoice/Quote/Client/Expense Filament schemas.
Add an UploadService::attachToModel(UploadedFile, Model) method that validates MIME type and stores the file.
MIME validation: use $file->getMimeType() against a configured allow-list.
changed the title [-][Feature-Request]: Make uploading of all kinds of Documents possible[/-][+][Core]: Upload all document types (not just images)[/+]on Jun 13, 2026
Added to support future implementation of this request. The issue body is unchanged.
This may already be partly solved: the only file-upload field found in the invoice form, FileUpload::make('attachments')->multiple() in Modules/Invoices/Filament/Company/Resources/Invoices/Schemas/InvoiceForm.php, has no ->image() or ->acceptedFileTypes() restriction, so it already accepts any file type today (only the company-branding fields settings.login_logo and settings.invoice_logo in Modules/Core/Filament/Admin/Pages/Settings.php are restricted with ->image()). What's missing: Modules\Core\Models\Upload / UploadDetail exist as a generic polymorphic attachment model but a repo-wide search shows nothing ever calls Upload::create() — there is no UploadService, so the models are dormant and the invoice attachments field just writes to disk via Filament's raw file storage, not through the Upload model. There is also no per-user "drop-in" storage folder feature (the second part of this request) anywhere in the codebase.
Edge cases worth covering: a filename with no extension or a disguised MIME type (server-side MIME sniffing, not just the client-visible file input accept attribute, since that's trivially bypassed); and disk quota per company/user if a large-file drop-in folder is added.
Original request: "for some of my customers I send them documents and it would be nice if there was the possibility of having an insert with the documents for my customers to download. The little plus would also be to have a user folder where I can put large files directly inside without going through the web interface."
Client-facing document downloads: documents attached to an invoice or quote (any allowed type, not just images) are listed on the customer-facing (guest) document view and downloadable there — without an account, gated by the document's guest URL key. This is the v2 port of v1's guest attachment surface, which #510 (admin-side Documents module) does not cover.
v1 behavioral spec:
application/modules/guest/controllers/Get.php — show_files($url_key) returns the attachment list ONLY when the url_key belongs to a guest-visible invoice/quote (is_url_key_guest_visible(), 32-char url_key check); get_file()/attachment() stream the download with path-traversal and filename validation.
application/modules/guest/views/invoices_view.php / quotes_view.php — read-only Dropzone listing the files.
v2 groundwork already present: Modules/Core/Models/Attachment.php has client_visibility (boolean) and url_key columns — unused; Modules/Core/Observers/AttachmentObserver.php is empty stubs.
Specific
Extend the allowed upload types beyond images: at minimum pdf, jpg, jpeg, png, gif, webp (v1 allowlist), plus common documents (doc/docx, xls/xlsx, odt/ods, txt, zip) behind a configurable allowlist.
Each attachment on an invoice/quote has a "visible to client" toggle (persisted to attachments.client_visibility).
The guest/public view of an invoice or quote (public-link feature) lists client-visible attachments with download links.
Download endpoint requires the document's unguessable url_key; returns 404/empty for non-guest-visible documents or attachments with client_visibility = false.
Downloads stream from private storage (no direct public disk URLs), with correct Content-Type and Content-Disposition.
Optional (original request's "little plus", may split out): a per-company server-side drop folder whose files can be linked to a client without going through the web UI.
Measurable
A PDF and a DOCX can be attached to an invoice and marked client-visible.
The guest view lists exactly the client-visible attachments; hidden ones never appear in the listing or via direct URL.
Wrong/forged url_key yields no file listing and no download.
Add client_visibility toggle to the attachment management UI; implement AttachmentObserver file cleanup on delete.
Signed or url_key-gated download controller/route streaming from the private disk.
Relevant
Restores a v1 capability (customers self-serve supporting documents from the invoice/quote link) that reduces email back-and-forth; the client_visibility schema was built for exactly this.
Time-Bound
Implement in the sprint following #510's admin-side upload UI.
Arrange
An invoice with a valid guest url_key; attachment A (pdf, client_visibility=true) and attachment B (png, client_visibility=false).
Act
Open the guest invoice URL as an unauthenticated visitor; request the listing and both files (B via forged direct URL).
Assert
Listing shows attachment A only.
A downloads with correct filename/Content-Type from private storage.
B and any forged/wrong url_key request return 404 (no file disclosure, no path traversal).
Upload of a disallowed extension (e.g. .exe) is rejected with a validation error.
Attachment file is removed from storage when the Attachment record is deleted (observer).
Activity
nielsdrost7 commentedon Dec 3, 2024
@kiuX89 thank you for your feature request.
Can you give a bit more explanation on what you would like with the documents for your customers?
Is it exactly the same document per customer?
Or is there personal information per customer in each document?
Is it Word Documents or PDF Documents?
And when the customers are logged in they need to see their own documents?
Are you uploading the documents per customer?
kiuX89 commentedon Dec 3, 2024
Basically, I'd like to upload documents of all types from the web interface, I have PDF, Word (docx), ZIP, Tar.gz. For PDF and Word, putting them in the interface would be simple, but for ZIP or Tar.gz files, it would be more practical to put them in a local folder bearing the user's name. Not all documents are identical, and yes, there is some personal/secret information.
When customers log on, they should see a document insert, so that when they click on it, they find each document and can click on the download button.
kiuX89 commentedon Dec 11, 2024
Hello, do you have any idea how long it would take to code this feature?
nielsdrost7 commentedon Dec 11, 2024
So... a user has committed lots of code, amongst other things he committed a user documents feature.
I've created a branch and a pull-request from it:
https://github.com/InvoicePlane/InvoicePlane/pull/1137/files
Personally with what you like (with the files stored per user) i really would have liked to have made it in V2 of InvoicePlane
This PR doesn't have files stored per user (I think)
These are the allowed types for now:
odt|ods|pdf|doc|docx|xls|xlsx|jpeg|jpg|png|gif|tiff
I think you wanted some more types, they can be easily added to the code.
So if you want to try out that pull-request (that branch) and let us know what you think?
[-]Documents[/-][+][Feature-Request]: Make uploading of all kinds of Documents possible[/+]nielsdrost7 commentedon Jun 13, 2026
Planning Note — v2 Implementation
Story: As a company admin, I want to upload any type of document (PDFs, contracts, receipts, images) and attach them to invoices, quotes, clients, or expenses, so that all relevant files are stored alongside the corresponding record without needing an external document manager.
v2 context: v2 has an
Uploadmodel in the Core module (Modules/Core/Models/Upload.php). It handles file storage via Laravel'sStoragefacade. The model needs to support more MIME types and be attachable to all major document models via a polymorphic relationship.Acceptance Criteria:
uploadstable stores files with a polymorphicuploadable_type/uploadable_id.storage/company/{company_id}/uploads/).Implementation:
Uploadmodel withuploadable()morphTo relationship.FileUpload::make('documents')section to Invoice/Quote/Client/Expense Filament schemas.UploadService::attachToModel(UploadedFile, Model)method that validates MIME type and stores the file.$file->getMimeType()against a configured allow-list.Test Scenarios (PHPUnit + Livewire):
Generated by Claude Code
[-][Feature-Request]: Make uploading of all kinds of Documents possible[/-][+][Core]: Upload all document types (not just images)[/+]nielsdrost7 commentedon Jul 16, 2026
Test Suggestions
Added to support future implementation of this request. The issue body is unchanged.
This may already be partly solved: the only file-upload field found in the invoice form,
FileUpload::make('attachments')->multiple()inModules/Invoices/Filament/Company/Resources/Invoices/Schemas/InvoiceForm.php, has no->image()or->acceptedFileTypes()restriction, so it already accepts any file type today (only the company-branding fieldssettings.login_logoandsettings.invoice_logoinModules/Core/Filament/Admin/Pages/Settings.phpare restricted with->image()). What's missing:Modules\Core\Models\Upload/UploadDetailexist as a generic polymorphic attachment model but a repo-wide search shows nothing ever callsUpload::create()— there is noUploadService, so the models are dormant and the invoiceattachmentsfield just writes to disk via Filament's raw file storage, not through theUploadmodel. There is also no per-user "drop-in" storage folder feature (the second part of this request) anywhere in the codebase.it_accepts_a_non_image_document_on_the_invoice_attachments_field()it_creates_an_upload_model_record_for_a_client_document()(aspirational — wires the dormantUploadmodel into the flow)it_lists_documents_uploaded_directly_to_a_users_storage_folder()(aspirational)Edge cases worth covering: a filename with no extension or a disguised MIME type (server-side MIME sniffing, not just the client-visible file input
acceptattribute, since that's trivially bypassed); and disk quota per company/user if a large-file drop-in folder is added.nielsdrost7 commentedon Jul 18, 2026
Client-facing document downloads: documents attached to an invoice or quote (any allowed type, not just images) are listed on the customer-facing (guest) document view and downloadable there — without an account, gated by the document's guest URL key. This is the v2 port of v1's guest attachment surface, which #510 (admin-side Documents module) does not cover.
v1 behavioral spec:
application/modules/guest/controllers/Get.php—show_files($url_key)returns the attachment list ONLY when the url_key belongs to a guest-visible invoice/quote (is_url_key_guest_visible(), 32-char url_key check);get_file()/attachment()stream the download with path-traversal and filename validation.application/modules/guest/views/invoices_view.php/quotes_view.php— read-only Dropzone listing the files.application/modules/upload/controllers/Upload.php(extension allowlist jpg/jpeg/png/pdf/gif/webp, MIME validation, EXIF stripping).v2 groundwork already present:
Modules/Core/Models/Attachment.phphasclient_visibility(boolean) andurl_keycolumns — unused;Modules/Core/Observers/AttachmentObserver.phpis empty stubs.Specific
attachments.client_visibility).client_visibility = false.Measurable
Achievable
client_visibilitytoggle to the attachment management UI; implementAttachmentObserverfile cleanup on delete.Relevant
Restores a v1 capability (customers self-serve supporting documents from the invoice/quote link) that reduces email back-and-forth; the
client_visibilityschema was built for exactly this.Time-Bound
Implement in the sprint following #510's admin-side upload UI.
Arrange
Act
Assert