Frappe R2 Storage

Store Frappe / ERPNext files in Cloudflare R2 buckets. Assign an R2 bucket per Frappe folder (or "Home" for all files) and files are uploaded, streamed, moved and deleted transparently.

Install Frappe R2 Storage

bench get-app https://github.com/devsohanur653-lab/frappe_r2_storage

Add the Frappe Gems badge to your README

Maintain Frappe R2 Storage? Paste this into your README:

[![Listed on Frappe Gems](https://frappegems.com/api/method/frappe_gems.seo.badge?app=devsohanur653-lab%2Ffrappe_r2_storage)](https://frappegems.com/gems/apps/devsohanur653-lab/frappe_r2_storage)

About Frappe R2 Storage

R2 Storage

Store Frappe / ERPNext files in Cloudflare R2 buckets. Assign an R2 bucket per Frappe folder (or "Home" for all files) and files are uploaded, streamed, moved and deleted transparently.

R2 Storage is a fork of DFP External Storage by Development for People (MIT), focused on Cloudflare R2.

What is different from DFP External Storage

  • Account ID based setup: enter your Cloudflare Account ID and the R2 endpoint (.r2.cloudflarestorage.com) is built for you. TLS and region auto are set automatically.
  • Jurisdictions: Default, EU (.eu.r2.cloudflarestorage.com) and FedRAMP endpoints.
  • Public bucket URL: set your https://pub-xxxx.r2.dev URL or custom domain and public files are redirected straight to Cloudflare (no Frappe proxy, CDN cached). Private files always go through Frappe permission checks.
  • R2 environment variables: R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY are read when keys are left empty (AWS / MinIO variables still work as fallback).
  • Any other S3 compatible service can still be used by choosing type "S3 Compatible" and filling host:port.

Requirements

  • Frappe version 15
  • A Cloudflare account with R2 enabled

Functionalities

  • R2 bucket per folder(s). If "Home" folder is selected, all Frappe / ERPNext files use that bucket.
  • Files accessible with URLs: /file/[File ID]/[file name.extension]
  • Frappe private/public permissions preserved for remote files.
  • Storages can be write disabled; existing files stay readable.
  • Bulk file relocation between buckets or to/from local filesystem, streamed in chunks (no full file in memory).
  • Same file (same hash) reuses the existing object key.
  • Bucket file listing page.
  • Storage can not be deleted while files use it.
  • If the bucket is not reachable on upload, the file is kept on local filesystem.
  • Presigned URLs (optionally by mimetype, e.g. video/ for streaming).
  • Redis cache for small public files, configurable stream buffer size, remote file size for files > 2GB.

Cloudflare R2 setup

  1. Cloudflare dashboard → R2 → Create bucket (keep it private).
  2. R2 → Manage R2 API Tokens → Create API token, permission Object Read & Write, scoped to the bucket.
  3. Copy Account ID, Access Key ID and Secret Access Key.
  4. Optional: bucket → Settings → enable Public Development URL or connect a Custom Domain if you want public files served directly by Cloudflare.

Install

cd ~/frappe-bench
bench get-app https://github.com/devsohanur653-lab/frappe_r2_storage.git
bench --site your-site.localhost install-app r2_storage
bench --site your-site.localhost migrate

> Do not install r2_storage and dfp_external_storage on the same site: both override the File DocType.

Configure

Desk → R2 Storage → New:

Field Value
Type Cloudflare R2
Cloudflare Account ID your account ID
Bucket name your bucket
Access Key / Secret Key from the R2 API token
Folder(s) Home for all files, or specific folders
Public bucket URL optional, https://pub-xxxx.r2.dev or custom domain

On save the connection is checked ("Great! Bucket is accesible").

Flow options

  • No storage assigned to a folder: files stay on the local filesystem.
  • Storage assigned to "Attachments": only files in that folder go to R2.
  • Storage assigned to "Home": all files go to R2 (except folders with their own storage).

File actions

  • File with an R2 Storage, changed to another one: streamed from old bucket to new bucket, then deleted from the old one.
  • File with an R2 Storage, cleared: downloaded to local filesystem, then deleted from the bucket.
  • Local file, R2 Storage assigned: uploaded to the bucket, then deleted from the filesystem.

Python API

Upload from a local file:

file_doc = frappe.get_doc({
    "doctype": "File",
    "is_private": True,
    "file_name": "file name here"
})
file_doc.r2_storage_upload_file(filepath)
file_doc.save()

Download to a local file:

file_doc = frappe.get_doc("File", doc_name)
file_doc.r2_storage_download_to_file("/path/to/local/file")

Read a remote file through a proxy object (only requested bytes are fetched):

file_doc = frappe.get_doc("File", doc_name)
with zipfile.ZipFile(file_doc.r2_storage_file_proxy()) as z:
    for zipinfo in z.infolist():
        print(zipinfo.filename)

Uninstall

bench --site {site} uninstall-app r2_storage --no-backup
bench remove-app r2_storage

Attributions

License

MIT. See LICENSE.md.

Related Integrations apps for Frappe & ERPNext

  • Insights — Open Source Business Intelligence Tool
  • Raven — Simple, open source team messaging platform
  • Frappe Whatsapp — WhatsApp cloud integration for frappe
  • Frappe Assistant Core — Infrastructure that connects LLMs to ERPNext. Frappe Assistant Core works with the Model Context Protocol (MCP) to expose ERPNext functionality to any compatible Language Model
  • Biometric Attendance Sync Tool — A simple tool for syncing Biometric Attendance data with your ERPNext server
  • Frappe React Sdk — React hooks for Frappe
  • Frappe Js Sdk — TypeScript/JavaScript library for Frappe REST API
  • Mcp — Frappe MCP allows Frappe apps to function as MCP servers