Frappe Responses API in Python (build_response & frappe.response)
The Frappe Responses API controls how server-side Python methods send HTTP responses back to the client. It is used internally by the Router, but you can also leverage it directly when you need custom output such as file downloads, inline previews, or non-standard content types.
What is the Frappe Responses API?
The Frappe Responses API is a set of utilities in frappe.utils.response that build and return HTTP responses based on values you place in frappe.local.response (aliased as frappe.response). The central function is build_response(), which looks at the configured response_type and delegates to a specific handler (JSON, page, file download, etc.). Frappe Docs
At a high level:
- Your code populates frappe.response with keys such as type, filename, filecontent, or display_content_as.
- Frappe’s request lifecycle ends by calling build_response() internally.
- build_response() calls the correct handler (as_json, as_pdf, as_raw, etc.) to construct the final HTTP response. Frappe Docs
How does build_response() work in Frappe v15?
Core behavior
The official documentation shows the implementation pattern of build_response in frappe.utils.response: Frappe Docs
def build_response(response_type=None):
if "docs" in frappe.local.response and not frappe.local.response.docs:
del frappe.local.response["docs"]
response_type_map = {
"csv": as_csv,
"txt": as_txt,
"download": as_raw,
"json": as_json,
"pdf": as_pdf,
"page": as_page,
"redirect": redirect,
"binary": as_binary,
}
return response_type_map[frappe.response.get("type") or response_type]()
Key points for developers:
- frappe.local.response is the working dict; frappe.response is a convenience alias.
- Empty docs responses are cleaned up before sending (avoids sending empty arrays). Frappe Docs
- response_type_map defines how each response type string maps to a handler function:
- “json” → as_json
- “page” → as_page
- “download” → as_raw
- “pdf” → as_pdf
- “csv” → as_csv
- “txt” → as_txt
- “redirect” → redirect
- “binary” → as_binary Frappe Docs
- Effective type is resolved as:
- frappe.response[“type”] if present, otherwise
- the response_type argument passed into build_response().
In normal Frappe v15 request handling, you do not call build_response() yourself. The router calls it internally, based on the current context and what you put in frappe. Response.
How does as_raw() build download responses?
Response handler for type = “download”
The documentation walks through the “download” handler, implemented as as_raw(): Frappe Docs
def as_raw():
response = Response()
response.mimetype = (
frappe.response.get("content_type")
or mimetypes.guess_type(frappe.response["filename"])[0]
or "application/unknown"
)
response.headers["Content-Disposition"] = (
f'{frappe.response.get("display_content_as", "attachment")};'
f' filename="{frappe.response["filename"].replace(" ", "_")}"'
).encode("utf-8")
response.data = frappe.response["filecontent"]
return response
Important behaviors:
- MIME type resolution:
- Uses frappe.response[“content_type”] if you explicitly set it.
- Otherwise uses mimetypes.guess_type(filename)[0].
- Falls back to “application/unknown” if nothing is detected. Frappe Docs
- Content-Disposition header:
- Constructed using display_content_as (default “attachment”) and filename.
- Spaces in filename are replaced with underscores.
- The resulting header is UTF-8 encoded. Frappe Docs
- Payload:
- response.data is set directly from frappe.response[“filecontent”].
How do I create a direct file download endpoint?
Step-by-step pattern
To make an endpoint that immediately downloads a file, you typically:
- Fetch the File document.
- Set frappe.response.filename to the desired filename.
- Set frappe.response.filecontent to the raw bytes (or string) content.
- Set frappe.response.type = “download”.
- Optionally set frappe.response.display_content_as to “attachment” or “inline”.
The official documentation uses an example function like this: Frappe Docs
@frappe.whitelist()
def download(name):
file = frappe.get_doc("File", name)
frappe.response.filename = file.file_name
frappe.response.filecontent = file.get_content()
frappe.response.type = "download"
frappe.response.display_content_as = "attachment"
When this method is called over HTTP (e.g., via /api/method/your_app.module.download?name=…), Frappe will:
- Populate frappe.response with the values you defined.
- Call build_response() internally at the end of the request.
- build_response() will see type = “download” and invoke as_raw().
- The browser will receive a response with Content-Disposition: attachment; filename=”…”, prompting a download dialog. Frappe Docs
What is the difference between attachment and inline?
The Content-Disposition header determines how the browser handles the response: Frappe Docs
attachment (default)
- attachment (default)
- Instructs the browser to download and save the content.
- Common for files such as CSV, ZIP, or exported PDFs.
- inline
- Instructs the browser to display the content directly if supported.
- Good for inline PDF previews, images, or HTML fragments.
- Instructs the browser to display the content directly if supported.
To switch to inline display, set:
frappe.response.display_content_as = "inline"
Use inline when your content can be safely viewed in the browser; use attachment when you want the user to explicitly download and store the file.
What are the common response types in Frappe?
Below is a conceptual overview of the response types from response_type_map. The exact handler names are visible in the official documentation snippet. Frappe Docs
1. JSON responses (type = “json”)
Intent: Return structured JSON data for API consumers or AJAX calls.
Typical usage:
@frappe.whitelist()
def get_data():
frappe.response.type = "json"
frappe.response.update({
"message": "ok",
"data": {"status": "success"}
})
If you simply return a Python dict from a whitelisted method, Frappe will often serialize it as JSON automatically; explicitly setting frappe.response is useful when you want more control.
2. Page responses (type = “page”)
Intent: Render a full HTML page, often via Frappe’s web page rendering system.
Usage is mostly internal to the Router, which decides to use as_page when returning rendered templates. For most app development, you will configure Web Pages or Desk Views rather than manually setting type = “page”.
3. Download / raw responses (type = “download”)
Covered in detail above. Use when you need to return arbitrary file content with a filename and content disposition control.
4. PDF responses (type = “pdf”)
Intent: Return a generated PDF as the HTTP response.
Internally, as_pdf will set appropriate MIME type and data. Common when exporting DocTypes, invoices, or reports as PDF. You usually configure this via report/print APIs rather than manually setting type = “pdf”.
5. CSV and text responses (type = “csv” / “txt”)
Intent: Return CSV or plain text files, typically for exports or logs.
Typical pattern:
@frappe.whitelist()
def export_csv():
frappe.response.type = "csv"
frappe.response.filename = "export.csv"
frappe.response.filecontent = "col1,col2\nval1,val2\n"
When type = “csv”, Frappe will use as_csv to construct the response, but the pattern for populating filename and filecontent is similar to download.
6. Redirect responses (type = “redirect”)
Intent: Redirect the user to another URL.
Internally, redirect handler sets an HTTP Location header. Use when performing server-side redirects after some action (e.g., OAuth callback, form submission).
7. Binary responses (type = “binary”)
Intent: Stream arbitrary binary data, similar to download, but tailored for use-cases where you may not want standard file headers.
as_binary is used for raw binary payloads such as images or generated binary blobs. The pattern of setting frappe.response.filecontent or similar binary data still applies.
How should I use frappe.response in my own code?
Recommended pattern
When you need custom responses (especially non-JSON), use this checklist:
- Set the response type
- frappe.response.type = “download” / “csv” / “txt” / “pdf” / “binary” / “json” …
- Provide payload data
- For downloads: frappe.response.filename and frappe.response.filecontent.
- For JSON: directly update frappe.response with keys you want in the serialized output.
- Optionally specify MIME or display hints
- frappe.response.content_type if you need a custom MIME type.
- frappe.response.display_content_as = “attachment” | “inline” for download behavior.
- Do not return another value from the function if you are fully controlling frappe.response – let Frappe handle the final HTTP response through build_response().
Best practices for Frappe responses
1. Keep a single source of truth
Avoid mixing patterns like return dict and manually populated frappe.response in the same method. Choose one pattern for clarity and predictability.
2. Validate access before sending files
When building download endpoints:
- Always ensure the current user has permission to access the File or underlying resource.
- Never trust arbitrary file paths or names from the client without validation.
3. Set meaningful filenames
Choose filenames that clearly indicate the content, including extensions:
frappe.response.filename = f”sales-invoice-{doc.name}.pdf”
This improves user experience and helps OSs choose correct applications for opening.
4. Prefer JSON for APIs
For programmatic clients (mobile apps, integrations), keep the primary interface JSON-based. Use file downloads only when the content is inherently a file (e.g., reports, backups).
5. Use inline cautiously
Inline rendering is convenient but:
- Ensure the content is safe to render in the browser.
- Be mindful of sensitive data in PDFs or images that could appear in browser history or previews.
Troubleshooting common response issues
Why is my file download not working?
Check the following:
- frappe.response.type is set to “download”.
- frappe.response.filename is set and includes an extension.
- frappe.response.filecontent is not empty.
- Your function is whitelisted and actually being called. Frappe Docs
Why do I get a KeyError in build_response?
This usually happens if:
- frappe.response[“type”] is set to a value not present in response_type_map.
- You passed a response_type argument that does not match any key. Frappe Docs
Fix: Ensure type is one of: “csv”, “txt”, “download”, “json”, “pdf”, “page”, “redirect”, “binary”.
Why is my JSON response empty?
Confirm that:
- You are not overriding frappe.response later in the call stack.
- You are not accidentally clearing frappe.local.response[“docs”] or other keys. The built-in cleanup only removes an empty docs list, but your own code might be modifying frappe.response incorrectly. Frappe Docs
Cross-references and related topics
- Router & Request Lifecycle: For a full picture of how build_response is invoked, see the Router Documentation and Request Lifecycle pages in the Frappe docs. Frappe Docs
- Python API – Routing and Rendering: Explains how routes are resolved and how views/pages are rendered before build_response is called.
- Frappe File DocType: Used in examples for file download endpoints.