File Upload Processing#
Dify's backend file upload pipeline handles incoming files through FileService.upload_file in api/services/file_service.py. This is the primary entry point for all uploads — knowledge base documents, workflow inputs, and batch CSV runs. The service enforces validation, sanitizes filenames, stores the file bytes in the configured storage backend (local filesystem via OpenDAL, S3, MinIO, etc.), and writes an UploadFile metadata row to PostgreSQL. Keeping these two stores in sync is a recurring concern; see File Storage Synchronization for details.
Filename Validation and Sanitization#
upload_file applies these checks in order:
| Check | Behavior |
|---|---|
Path separators (/, \) | Raises ValueError("Filename contains invalid characters") |
| Length > 200 chars | Truncates stem to 200 chars, preserves extension |
| Extension blacklist | Rejects files on the configured deny-list |
| File-type restriction (datasets) | Restricts to document extensions for knowledge base uploads |
| File size | Enforces per-type size limits |
Before PR #31412 , filenames with other special characters like @ and | caused the upload to fail. The fix replaced rejection with automatic sanitization — invalid characters are replaced with underscores — so the upload succeeds with a cleaned filename.
For ZIP archive entries, the separate _sanitize_zip_entry_name method strips directory components (os.path.basename) and replaces any residual / or \\ with underscores. This prevents path traversal in exported or imported ZIP payloads.
The storage key itself is always UUID-based, so the original (or sanitized) filename is only metadata; it never influences where the bytes land on disk.
Encoding Detection#
Dify migrated from chardet to charset_normalizer (PR #29022, ) for more accurate multi-language encoding detection. The library is now used in three places:
-
RAG file extraction —
detect_file_encodingsinapi/core/rag/extractor/helpers.pycallscharset_normalizer.from_path(...).best()inside aThreadPoolExecutorwith a 5-second timeout. TheFileEncodingnamedtuple carriesencoding,confidence, andlanguage. -
CSV and text extractors — The CSV extractor and text extractor both attempt to open files with the specified encoding first; on
UnicodeDecodeErrorwithautodetect_encoding=True, they calldetect_file_encodings()and retry through the returned candidates in confidence order. -
Web content reader —
web_reader_tool.pyusescharset_normalizer.from_bytes(...).best()on HTTP response bodies, falling back toutf-8orresponse.textif detection fails.
Bug history:
- PR #29022 introduced the
charset_normalizerswitch but the initial implementation returned raw results as dict-like objects. PR #29595 fixed a resultingTypeError: tuple indices must be integers or slices, not strindetect_file_encodingsby switching from dict-key access (encoding["encoding"]) to attribute access (encoding.encoding). - PR #31204 added frontend encoding detection for batch CSV uploads, using
jschardetin the browser to decode GBK / GB18030 / BIG5 files before sending them to the backend, since the RAG backend path alone did not cover the batch-run CSV input flow.
Database–Storage Synchronization#
Every uploaded file has two representations that must stay in sync:
- PostgreSQL —
UploadFilerow with path key, tenant, MIME type, and size - Storage backend — actual bytes addressed by a UUID-based key
A DB row with no physical file causes a FileNotFoundError in opendal_storage.load_stream , which surfaces as 502 errors and 404 responses on /file-preview endpoints.
Normal deletion paths maintain sync:
FileService.delete_filewrapsstorage.delete()andsession.delete()in a single transaction.clean_document_taskcollects file keys, commits DB deletions, then removes physical files with per-filetry/exceptblocks.
Common causes of desync:
- Out-of-band cron/script deleting files from
./volumes/app/storage/upload_files/directly - Incomplete volume copy during a version upgrade
storage.save()succeeds but the subsequentdb.commit()fails (orphaned file, no row)
Remediation CLI commands in api/commands/storage.py:
# Remove DB records whose files are missing from storage
docker compose exec api flask clear-orphaned-file-records
# Remove storage files that have no DB record
docker compose exec api flask remove-orphaned-files-on-storage
Key Source Files#
| File | Purpose |
|---|---|
api/services/file_service.py | Main upload entry point: validation, sanitization, storage write, DB row creation |
api/core/rag/extractor/helpers.py | detect_file_encodings — threaded charset detection via charset_normalizer |
api/core/rag/extractor/csv_extractor.py | CSV extraction with encoding fallback |
api/core/rag/extractor/text_extractor.py | Text extraction with encoding fallback |
api/core/tools/utils/web_reader_tool.py | HTTP response body decoding via charset_normalizer |
api/commands/storage.py | CLI reconciliation commands for DB–storage sync |
web/app/components/share/text-generation/run-batch/csv-reader/index.tsx | Frontend CSV decoding for batch runs (GBK/GB18030/BIG5 support) |