protondrive lets you work with files on Proton Drive from R. Its interface is modelled on googledrive: if you know drive_ls(), drive_upload(), and the dribble, you already know pd_ls(), pd_upload(), and protondrive’s dribble.
This is a third-party application not officially supported by Proton.
How it works
Proton Drive is end-to-end encrypted, so a client has to encrypt, decrypt, and sign everything locally. Proton publishes the Proton Drive SDK for this. However, the SDK does not handle sign-in or session management. Proton’s official Proton Drive CLI (proton-drive), built on the SDK, adds those pieces.
protondrive drives that CLI. Each function runs one CLI command with --json and turns the result into a tibble. So:
- Your password never passes through R. You sign in through Proton’s own web page, and the CLI keeps the session in your operating system’s secret store.
- Encryption, caching, rate limiting, and the
x-pm-appversionheader are handled by Proton’s own code. protondrive itself makes no network requests.
Installation
Install the Proton Drive CLI from https://proton.me/download/drive/cli/index.html and put
proton-driveon yourPATH. If it lives somewhere else, setPROTONDRIVE_CLI_PATH(in.Renviron) oroptions(protondrive.cli_path = ...)to its location.-
Install protondrive from GitHub:
# install.packages("pak") pak::pak("hinkelman/protondrive") -
Sign in once per machine. A browser window opens. The session then persists across R sessions:
Usage
library(protondrive)
# What's in My files?
pd_ls()
# Paths are relative to /my-files unless they start with a section.
pd_ls("reports", pattern = "\\.csv$")
pd_ls("/shared-with-me")
# Upload, then read back.
write.csv(mtcars, "mtcars.csv", row.names = FALSE)
pd_mkdir("data")
f <- pd_upload("mtcars.csv", path = "data")
head(read.csv(text = pd_read_string(f)))
# A dribble holds the full metadata; reveal what you need.
pd_ls("data") |> pd_reveal(c("size", "modified_time"))
# Update the file in place, as a new revision.
pd_put("mtcars.csv", path = "data")
# Organise.
pd_cp(f, name = "mtcars-backup.csv")
pd_mv(f, path = "archive")
# Share.
pd_share(f, "colleague@proton.me", role = "editor")
pd_share_link(f, expiration = Sys.Date() + 7)
# Clean up.
pd_trash(f)Coming from googledrive
| googledrive | protondrive | notes |
|---|---|---|
drive_auth(), drive_deauth()
|
pd_auth(), pd_deauth()
|
browser sign-in through the CLI |
drive_has_token() |
pd_has_auth() |
|
dribble, as_dribble(), is_dribble()
|
dribble, as_dribble(), is_dribble()
|
adds a path column; class pd_dribble
|
as_id() |
as_id() |
|
drive_get() |
pd_get() |
|
drive_ls() |
pd_ls() |
|
drive_reveal() |
pd_reveal() |
|
drive_mkdir() |
pd_mkdir() |
|
drive_upload(), drive_put(), drive_update()
|
pd_upload(), pd_put(), pd_update()
|
updates add revisions |
drive_download() |
pd_download() |
also downloads folders |
drive_read_string(), drive_read_raw()
|
pd_read_string(), pd_read_raw()
|
|
drive_cp(), drive_mv(), drive_rename()
|
pd_cp(), pd_mv(), pd_rename()
|
|
drive_trash(), drive_untrash(), drive_empty_trash()
|
pd_trash(), pd_untrash(), pd_empty_trash()
|
|
drive_rm() |
pd_rm() |
permanent |
drive_share() |
pd_share(), pd_unshare(), pd_sharing()
|
|
drive_share_anyone(), drive_link()
|
pd_share_link(), pd_link()
|
password and expiry supported |
local_drive_quiet(), with_drive_quiet()
|
local_pd_quiet(), with_pd_quiet()
|
|
| — |
pd_size(), pd_invitations(), pd_leave()
|
Proton-specific; pd_size() needs a CLI newer than 0.8.0 |
Not available: drive_find() and full-text search, because the SDK does not offer search yet. Also not available: drive_browse(), and Google-specific features such as shared drives and file conversion.
Differences to keep in mind
-
Names are unique within a folder. Proton Drive rejects duplicates, so functions that create items take an
overwriteargument. Withoverwrite = TRUE, an existing file gets a new revision, or the existing item is moved to the trash, depending on the function. - IDs over paths. A dribble records both the path and the ID of each item. protondrive addresses items by ID wherever the CLI allows, so a dribble keeps working after its items are renamed or moved.
-
The trash is addressed by name. The CLI identifies trashed items by name.
pd_untrash()andpd_rm()check that the name is unambiguous and refers to the item you meant before acting. -
Be gentle with the API. Proton asks third-party clients to avoid frequent recursive traversals. Use
pd_ls(recursive = TRUE)sparingly on large trees.
Status
Both the Proton Drive SDK and the CLI are pre-release, and Proton plans a cryptographic migration around the end of 2026. Keep the CLI up to date (pd_cli_version() tells you whether a newer one exists). protondrive’s interface may also change.
Live testing
The automated tests use a simulated CLI, so they don’t need a Proton account. Separately, protondrive has been tested against a real Proton account with Proton Drive CLI 0.8.0 (SDK js 0.21.0) on Linux, in October 2026:
| area | functions | result |
|---|---|---|
| Sign-in |
pd_auth(), pd_has_auth()
|
✅ works |
| Finding files |
pd_ls() (including recursive = TRUE), pd_get() by path and by ID, pd_reveal()
|
✅ works |
| Upload and download |
pd_upload(), pd_put() (new revision, same ID), pd_download(), pd_read_string()
|
✅ works |
| Organising |
pd_mkdir(), pd_cp(), pd_rename(), pd_mv(), pd_trash(), pd_untrash(), pd_rm()
|
✅ works |
| Public links |
pd_share_link() (with password and expiration), pd_link(), pd_unshare_link()
|
✅ works |
| Sharing with people |
pd_share(), changing a role, pd_unshare(), pd_sharing()
|
✅ works, including invitations to non-Proton addresses |
| Folder size | pd_size() |
⚠️ needs a newer CLI: 0.8.0 has no filesystem size command |
| Invitations from others |
pd_invitations(), pd_accept_invitation(), pd_reject_invitation(), pd_leave()
|
⏳ not yet tested |
| Emptying the trash | pd_empty_trash() |
⏳ not yet tested |
Some limits come from Proton itself:
-
Proton Docs and Sheets can’t be downloaded with
pd_download()or thepd_read_*()functions; the CLI skips them. -
Re-sharing doesn’t resend email. A new invitee gets one invitation email; calling
pd_share()again for them only changes their role. - You can’t invite yourself. Proton rejects addresses that belong to your own account.