name: frappe-core-files description: > Use when handling file uploads, attachments, private/public file access, or S3 storage configuration. Prevents broken file URLs, permission leaks on private files, and failed uploads from incorrect MIME handling. Covers File DocType, frappe.get_file, upload API, private vs public directories, S3 integration, file URL patterns, attach field types. Keywords: file, upload, attachment, File DocType, private, public, S3, file_url, get_file, attach, upload not working, file missing, broken file link, download file, image not showing, attachment error.. license: MIT compatibility: "Claude Code, Claude.ai Projects, Claude API. Frappe v14-v16." metadata: author: OpenAEC-Foundation version: "2.0"
Frappe File Management
Quick Reference
| Action | Method | Notes |
|---|---|---|
| Save file from bytes | save_file(fname, content, dt, dn) | Returns File doc |
| Save file from URL | save_url(file_url, fname, dt, dn) | Creates File doc from URL |
| Read file content | frappe.get_file(fname) | Returns [filename, content] |
| Get file path | get_file_path(file_name) | Resolves to absolute path |
| Upload via HTTP | POST /api/method/upload_file | Multipart form upload |
| Delete file | frappe.delete_doc("File", name) | Removes doc + filesystem file |
| Attach print | frappe.attach_print(dt, dn, print_format) | Returns {"fname", "fcontent"} |
| Get cached doc | frappe.get_cached_doc("File", name) | Read-only, cached |
Decision Tree
What file operation do you need?
│
├─ Upload a file from user input?
│ ├─ Via web form → Attach field type (auto-handles upload)
│ └─ Via API → POST /api/method/upload_file
│
├─ Create a file programmatically?
│ ├─ From bytes/content → save_file(fname, content, dt, dn)
│ ├─ From external URL → save_url(file_url, fname, dt, dn)
│ └─ Full control → frappe.get_doc({"doctype": "File", ...}).insert()
│
├─ Read file content?
│ ├─ By filename → frappe.get_file(fname)
│ └─ By File doc → file_doc.get_content()
│
├─ Public or private?
│ ├─ Public (anyone with link) → is_private=0, URL: /files/fname
│ └─ Private (permission-based) → is_private=1, URL: /private/files/fname
│
└─ Generate PDF attachment?
└─ frappe.attach_print(doctype, name, print_format)
File DocType: Core Fields
| Field | Type | Description |
|---|---|---|
file_name | Data | Filename without path |
file_url | Data | URL path (e.g., /files/report.pdf) |
file_type | Data | Extension (PDF, PNG, DOCX, etc.) |
is_private | Check | 0 = public, 1 = private |
is_folder | Check | True for folder entries |
folder | Link → File | Parent folder |
attached_to_doctype | Link → DocType | Parent document type |
attached_to_name | Data | Parent document name |
attached_to_field | Data | Field name on parent |
content_hash | Data | SHA-256 for deduplication |
file_size | Int | Size in bytes |
File URL Patterns
| Type | URL Pattern | Filesystem Path |
|---|---|---|
| Public | /files/{filename} | {site}/public/files/{filename} |
| Private | /private/files/{filename} | {site}/private/files/{filename} |
| Remote | https://... | Not stored locally |
| API | /api/method/{path} | Generated dynamically |
Valid URL prefixes: http://, https://, /api/method/, /files/, /private/files/.
ALWAYS use /private/files/ for sensitive documents. Public files are accessible to anyone with the URL, including unauthenticated users.
Permission Model
Frappe files use a three-tier permission model:
- Administrator — unrestricted access to all files
- Public files (
is_private=0) — readable by anyone with the URL (no authentication required for read) - Private files (
is_private=1) — access requires:- User is the file owner, OR
- User has explicit share on the file, OR
- User has read permission on the
attached_to_doctype/attached_to_namedocument
NEVER store sensitive data as public files. ALWAYS set is_private=1 for documents containing personal data, financial records, or confidential information.
Programmatic File Operations
Save File from Content
from frappe.utils.file_manager import save_file
# Save a generated CSV
csv_content = "Name,Amount\nACME,1000\nGlobex,2000"
file_doc = save_file(
fname="report.csv",
content=csv_content.encode("utf-8"),
dt="Sales Invoice", # attach to this DocType
dn="SINV-00001", # attach to this document
folder="Home/Attachments", # optional folder
is_private=1, # private file
)
# file_doc.file_url → "/private/files/report.csv"
Save File from URL
from frappe.utils.file_manager import save_url
file_doc = save_url(
file_url="https://example.com/logo.png",
filename="company-logo.png",
dt="Company",
dn="My Company",
folder="Home",
is_private=0,
)
Read File Content
# By filename
filename, content = frappe.get_file("report.csv")
# By File document
file_doc = frappe.get_doc("File", {"file_name": "report.csv"})
content_bytes = file_doc.get_content()
Create File Document Directly
file_doc = frappe.get_doc({
"doctype": "File",
"file_name": "generated-report.pdf",
"attached_to_doctype": "Sales Invoice",
"attached_to_name": "SINV-00001",
"is_private": 1,
"content": pdf_bytes, # raw bytes — written to disk on insert
}).insert(ignore_permissions=True)
Generate and Attach PDF
# Create PDF attachment dict (for use with sendmail)
pdf_attachment = frappe.attach_print(
"Sales Invoice",
"SINV-00001",
print_format="Standard",
)
# Returns: {"fname": "Sales Invoice - SINV-00001.pdf", "fcontent": <bytes>}
# Save PDF as file attachment
from frappe.utils.file_manager import save_file
pdf = frappe.get_print("Sales Invoice", "SINV-00001", print_format="Standard", as_pdf=True)
save_file("invoice.pdf", pdf, "Sales Invoice", "SINV-00001", is_private=1)
File Upload via REST API
# Upload file attached to a document
curl -X POST https://site.example.com/api/method/upload_file \
-H "Authorization: token api_key:api_secret" \
-F "file=@/path/to/document.pdf" \
-F "doctype=Sales Invoice" \
-F "docname=SINV-00001" \
-F "is_private=1"
Response:
{
"message": {
"name": "FILE-00001",
"file_name": "document.pdf",
"file_url": "/private/files/document.pdf",
"is_private": 1
}
}
File Size and Extension Limits
Default max file size: 10 MB per attachment.
Override in site_config.json:
{
"max_file_size": 20971520
}
Max attachments per document: Set via Customize Form → Max Attachments field on the DocType.
Check file size programmatically:
from frappe.utils.file_manager import check_max_file_size
check_max_file_size(content) # raises MaxFileSizeReachedError if too large
Attach Field Types
| Field Type | Stores | UI |
|---|---|---|
Attach | Single file URL | File picker + upload button |
Attach Image | Single image URL | Image preview + upload |
Both store the file_url string in the field value. The File DocType record is created separately with attached_to_field set.
S3 / Cloud Storage Integration
Frappe supports custom file storage via the delete_file_data_content hook and custom upload handlers.
S3 via frappe-s3-attachment or similar app
# In hooks.py of custom app
delete_file_data_content = "my_app.storage.delete_from_s3"
ALWAYS test file deletion when using custom storage backends — the default delete_file_from_filesystem only handles local files.
Configuration Pattern
# site_config.json for S3-compatible storage
{
"s3_bucket": "my-frappe-files",
"s3_region": "eu-west-1",
"s3_access_key": "AKIA...",
"s3_secret_key": "...",
}
Version Differences
| Feature | v14 | v15 | v16 |
|---|---|---|---|
| File DocType | Available | Available | Available |
content_hash dedup | Available | Available | Available |
| Image optimization | Manual | Auto (1920x1080, 85%) | Auto |
| Import/Export Zip | Not available | Available | Available |
See Also
- references/examples.md — File operation code examples
- references/anti-patterns.md — Common file handling mistakes
frappe-core-permissions— Permission model for file accessfrappe-core-database— Database operations for File queries