How the transfer works
When you send a file, we use WebRTC to open a connection between you and the recipient. Our signaling server introduces the two devices and then steps aside. In most cases, data then flows directly between the two devices, whether each one is a browser, Floe Desktop, or the CLI. When a direct path is not available, a TURN relay (on floe.one, Cloudflare's network) bridges the connection. Even through the relay, files remain encrypted, and the relay only forwards them.
The encryption is WebRTC's own (DTLS), with keys that exist only on the two devices for that one transfer. The codes the two devices use to recognize each other (their certificate fingerprints) pass through our signaling server. A link made in the web app, floe 1.12 or later, or Floe Desktop 1.1 or later also carries a random link key in its fragment, which no server receives. Once the two devices connect, each one that holds the link key proves it to the other, tied to the certificates actually in use, and the receiving device stops the transfer before any file arrives if the sender cannot. So a server that swaps the fingerprints on such a link is caught. A room code, and a link made by an older floe or Floe Desktop, carries no link key, and floe before 1.12 and Floe Desktop before 1.1 do not check the link key when receiving. A sender on a share link does not yet refuse a receiver that cannot show the link key, unless you use floe send --link-only, and it sends a file's name and size before that check has finished, so in those cases you are still trusting that server; the full list of what Floe does not protect against is in Known limitations.
Peer-to-peer means the two devices connect to each other, so on a direct connection each learns the other's IP address, in the same way a video call does. A receiver page joins the room as soon as the link opens, with no click, and your browser begins exchanging its network addresses with the sender at that point. If you would rather not reveal your address to the other person, turn on "Hide my IP address" in Floe Desktop, or use the CLI's --relay-only flag (or FLOE_RELAY_ONLY=1). Your side then goes through the relay, and the other person sees the relay's address instead of yours. The cost: relayed transfers are slower and capped at 2 GB. The web app has no equivalent: its "Network relay fallback" checkbox does the opposite, turning the relay off so only a direct connection is attempted.
A request link works the other way around. You make it in Floe Desktop and send it to someone, and they open it in their browser, or run floe send --to with it, to send files to your PC. The browser page joins nothing until they click Send. From that click, or from the moment the command runs, the two of you learn each other's IP address, before you accept or decline, unless that side hides it: the request link page has its own "Hide my IP (relay only, 2 GB per drop)" checkbox, the CLI has --relay-only, and Floe Desktop's setting covers your side.
Information we collect
- Files
We do not collect or store any files.
- Connection setup
To pair two devices, our signaling server receives the room id and relays the WebRTC connection messages (offers, answers, and ICE candidates, which carry each device's network addresses) between them. It checks each message's size and shape, forwards it from memory without keeping or logging it, and keeps nothing once the room closes. When the sender uses the CLI or Floe Desktop, the server also maps a random three-word code to the room id, and drops that mapping when the receiver joins or the room closes, or after about ten minutes if neither happens.
A request link is the one exception: the server holds its room, with a hash of the link's secret token and never the token itself, while the link is open and for about 10 minutes after Floe Desktop disconnects. When a link that someone has connected through is closed, it keeps the link's room id with a mark saying it was used, for a day. For any other link it keeps the room id and the link's end time until a day after it ends, so a late visitor can be told it ended. All of it is in memory only.
- Metadata
Filenames and sizes travel between the two devices over the encrypted data channel so the receiver can see what is arriving. File names never reach our signaling server, and the only size it is ever told is the byte total in the next row. On a relayed transfer they pass through the relay in the same encrypted stream as the file data, which the relay cannot read.
- Byte total
When a transfer completes, the receiving side reports only the number of bytes it received. A request link drop that stops after it was accepted reports the bytes of the files it saved, once, unless you turned off the switch below. We add this to one shared, all-time counter of total bytes transferred, shown on our homepage and stored as a single number in a hosted Redis database (Upstash). The sender never reports. We do not store file names, file contents, or any link between this number and you.
You can opt out: uncheck "Contribute to global stats" on the receiver view in the browser or in Floe Desktop's Settings, or use
--no-report(or setFLOE_NO_STATS=1) with the CLI. This switch covers the public counter and nothing else: on floe.one the site analytics described under Usage analytics record a transfer's outcome separately, and the desktop app and the CLI send no such event.- IP addresses
Our signaling server holds your IP address in memory while your app is connected to it and, to rate-limit abuse, for at most about two minutes after your last request, and the server itself writes no log of it. Like all web servers, the reverse proxy in front of it and the providers that host us may log connection request IP addresses for security and abuse prevention. We do not link any of this to your identity.
While a room made by a share link or a code is open, the server also keeps a scrambled marker for each network in it, never the address itself, so that a device on a third network cannot take a seat. The marker is a keyed hash of the network address (an IPv4 address in full, an IPv6 address by its /64 prefix) of each device that has sent connection messages through that room, kept in memory only and deleted with the room.
To hold each network to 20 new request links a day, the server keeps the times of new links for 24 hours under a keyed hash of the address, never the address itself, and under the same hash it counts the links each network has open, to hold it to 10 at a time. Who else sees your IP address, and how to hide it from the other person, is under How the transfer works.
Third-party services
Floe uses third-party infrastructure providers for hosting and connection setup. The web app is hosted on Vercel and the signaling server runs on Microsoft Azure. Connection setup uses Cloudflare on every transfer, direct or relayed, and relayed file data passes through Cloudflare's TURN network; Relay server below says exactly what it sees. Your app may not get relay credentials from our signaling server: the server can be unreachable, can have rate limited you, or can have none to hand out at that moment. It then falls back to Google's public STUN servers (stun.l.google.com and stun1.l.google.com; STUN is a service that tells a device its own public address) to learn its own public address. Google then sees your IP address and nothing about the transfer; STUN carries no file data.
The running total behind the homepage counter is stored in a hosted Redis database (Upstash); the only thing ever written there is that single number, by our server, never by your device. Floe Desktop and the CLI contact GitHub only to check for or download updates: Floe Desktop's GitHub build at most once a day after a check succeeds (a failed check is tried again at the next start) unless you turn the check off (Microsoft Store builds never check), and the CLI only when you run floe version, floe update, or floe update --check. Both are described in their own sections below. The documentation at floe.one/docs is served by Mintlify, a third-party documentation host: requests to those pages reach Mintlify, its pages load Mintlify's own scripts, and Mintlify may collect page-view data there.
For usage analytics we use only Umami, which is cookieless and does not track you across sites, and for error monitoring we use Sentry. Both apply to the floe.one website only and are described below; the desktop app and the CLI have neither, and a self-hosted copy of Floe ships with both turned off.
The link you share carries its room id, and on a link made in the web app, floe 1.12 or later, or Floe Desktop 1.1 or later, a link key, in the URL fragment (the part after the #). Browsers never include the fragment in HTTP requests, so it stays out of hosting logs and referrer headers, and our analytics script is configured to drop it as well; what our error monitoring receives is described under Error monitoring. Links from the web app and Floe Desktop also carry a short random marker before the # (the ?s= part); it exists only so each link opens as a distinct page, it says nothing about you or the room, and links printed by the CLI do not carry one. Our signaling server receives the room id when your app joins the room to be paired with your peer, and, for a transfer started from the CLI or Floe Desktop, when it registers the three-word code that stands in for the link. Both are held in memory only: the room is dropped when the last device leaves it, and a code's mapping to its room id is dropped when the receiver joins or the room closes, and about ten minutes after the code was made at the latest. Our signaling server does not write either to its log, and the proxy in front of it keeps no request log. Our signaling server never receives the link key, which stays in the link and in the memory of the page that read it.
A request link looks like floe.one/r/<link id>#<room id>. The room id after the # works like a share link's and stays out of logs the same way. The link id before it is part of the page address, so Vercel's request logs see it; it names one link without the room id, so a report or a block can point at that one link, and our signaling server never receives it. A request link that carries a link key holds it after the room id, as #<room id>.<link key>. The page keeps it in memory and never sends it to any server. It proves it to the computer that made the link, and it names no file until that computer has proved it back; if that proof does not come, it sends no file and says so. Our analytics script does not load on a request link's page at all, and our error monitoring removes the link id from the address before anything is sent. A link key check that does not pass on that page sends the one warning described under Error monitoring, and nothing else about the check. Mail security services that rewrite links may see the whole link.
Please refer to each provider's privacy policy regarding data handling: Vercel, Microsoft, Cloudflare, Mintlify, and the Sentry and Umami policies linked in the sections below.
Relay server
When a direct connection cannot be established, file data is routed through Cloudflare's TURN relay network (turn.cloudflare.com). The relay forwards encrypted packets it cannot decrypt: the keys exist only on the two devices. Cloudflare states that it only relays those packets and cannot decrypt or inspect their content. Relay sessions are limited to 2 GB per session, which for a request link means per drop.
Cloudflare is involved before the route is decided, not only when a relay is needed. To find a working path, your app asks Cloudflare's STUN server (stun.cloudflare.com) for its own public address and, unless you turned relay fallback off in the browser or passed --no-relay to the CLI, reserves a spot on the relay at turn.cloudflare.com in case the direct path fails (a standby relay allocation). That happens on every transfer, whichever route wins, so Cloudflare sees your IP address and the timing of the connection attempt on every transfer. File data crosses Cloudflare only when the relay is the route that wins.
Before connecting, your app fetches relay credentials, a password for the relay, from our signaling server. On floe.one the server gets a new set from Cloudflare every few minutes, each valid for 24 hours, and gives the same set to everyone who asks until it gets the next one. They are not tied to you, your device, or an account.
The relay is a hop both devices connect to, so Cloudflare sees both IP addresses and the timing and amount of data it forwards, and may log that for its own security purposes. See Cloudflare's privacy policy for its handling of that data.
Error monitoring
The web app uses Sentry to monitor application errors and a 10% sample of page performance traces. This applies to the floe.one website only; the desktop app and the CLI contain no error monitoring. Sentry is configured to attach no cookies and no IP address to what it sends; each error report and trace does carry the page address, your browser's user agent, and the page that referred you. It may capture:
- Error stack traces and browser metadata (browser version, OS, device type, approximate device memory, language, and time zone)
- Connection type (direct or relay), whether relay fallback was on, transfer progress, file count, and total size, sent with an error report or with a page performance trace that falls in the 10% sample
- A short trail of what happened just before: clicks, page changes, the addresses of network requests, and console lines, with the room link removed from each
- One warning when a link key check does not pass: which side it was, how the check read the certificate, the outcome, and whether the page was set to stop on it, each a fixed label, with no link, no key, and nothing the other device sent
Session replay is not enabled. Floe used to record a sample of browser sessions. A recording reported the page address, and on a receiver page that address contains the room link, so replay was removed rather than kept: the room link is the only thing protecting a transfer.
Sentry does not capture file names or file contents, and Floe sends it nothing that names you: there are no accounts, so there is no name or email address to send. A click is recorded without the label of what you clicked, because the labels of Floe's Remove and Download buttons name a file. The room link, its link key included, is stripped from every error report, breadcrumb, and performance trace before it is sent, and on a request link's page the link id in the address is removed as well. Sentry Privacy Policy.
Usage analytics
The web app uses Umami Cloud, the hosted analytics service run by Umami's makers, to understand how the service is used. This applies to the floe.one website only; the desktop app and the CLI contain no analytics. Umami collects:
- Transfer outcomes: for a completed transfer, the number of files, the total size in bytes, whether you were sending or receiving, and whether the connection was direct or relayed; for a failure, your role, one of ten fixed labels (for example "ice-failed" or "out-of-memory"), and, when you were sending, the file count and total size
- Which download button you clicked on the download page, the homepage, or the navigation bar, and, when Floe opens inside another app's built-in browser (such as Facebook or Instagram), the name of that app and whether it was Android or iOS
- Standard page view data: the page path, referring site, browser, operating system, device type, screen size, and language
- Your approximate location (country, region, and city), derived from your IP address by Umami, which does not store the address
Umami does not use cookies and stores nothing on your device. It does not keep your IP address: the address and your browser signature are hashed with a salt that rotates on a schedule, so Umami can count a returning visitor for a while without keeping the address, and the hash is tied to floe.one, so it cannot follow you to other websites. File names and file contents are never recorded. It is configured to drop the URL fragment and the query string before reporting a page view, so the room link never reaches it, and Floe takes the page title out of everything it sends. Umami does not load on a request link's page at all. If your browser sends the Do Not Track signal, Umami records nothing at all for your visit. Umami Privacy Policy.
The script that does this does not load from Umami. floe.one serves its own copy of it, which we read through before using, and your browser refuses to run that copy if any of its bytes have changed. Its reports still go from your browser to Umami's service.
The web app on your device
The web app keeps a few small things in your browser, and none of them leave it. So that floe.one can open without a network, your browser caches the app's pages and build files, the same files every visitor downloads, and uses those copies only when it cannot reach the network; that cache never holds file data, anything from our API, or a request link's page. Your "Contribute to global stats" choice is remembered in this browser's local storage, and a timestamp is kept for the life of the tab to avoid reload loops after a new version is deployed.
Files you receive stay with your browser until you close or leave the page: in memory, or, for large files in Chrome and Edge, partly in the browser's own temporary files on your disk, which it deletes when the page closes or, if it quits unexpectedly, the next time it starts. Closing the tab discards anything you did not save. Nothing about a transfer's files is written to the storage a website can read back, with one exception: while a request link's page is sending, it keeps two numbers in the tab's own storage, how many files arrived and how many there are, so it can still say how far the drop got if the browser discards the tab. It never stores the link, the room, or a file name, and it clears them when the drop ends; if the browser discarded the tab first, they stay in that tab until it loads again or you close it. While a transfer is running the app asks your browser to keep the screen awake and releases that when the transfer ends. The app writes to your clipboard only when you press Copy and never reads it, and it never asks for notification permission.
The desktop app
Floe Desktop runs the same peer-to-peer engine as the CLI and speaks the same protocol as the web app, as a Windows application. It talks to the same servers the web app does. It contacts our signaling server:
- to pair you with your peer, and to register the three-word code it shows;
- only when you click Make link, to check that the server offers request links and to hold the link's room while the link is open;
- to fetch relay credentials.
It then streams file data directly between devices, or through the relay described above when no direct path exists or when "Hide my IP address" is on. One more request happens only when you ask for it: the Test button in Settings contacts only the server address you typed. If the Microsoft WebView2 runtime that draws the interface is missing, the installer from GitHub downloads it from Microsoft during setup, and the app offers to download it from Microsoft before it starts. It contains no analytics, no error monitoring, and no telemetry.
It makes two optional requests, each with its own off switch: the same anonymous byte total described above, which you can turn off with "Contribute to global stats" in Settings, and an update check, at most once a day once a check has succeeded (a check that fails is not kept, and the app asks again the next time it starts). The update check (GitHub builds 0.2.3 and later) asks GitHub whether a newer release exists; it carries nothing about you beyond what any web request reveals (your IP address and a user agent), nothing about your files or transfers is in it, and nothing downloads or installs by itself. Turn off "Check for updates" in Settings (or set FLOE_NO_UPDATE_CHECK=1, the same variable the CLI honors) and no request is made at all. Microsoft Store builds never check; the Store updates them itself.
It also adds one privacy switch the web app does not have. On a direct connection the other device learns your IP address, as in any peer-to-peer connection. Turn on "Hide my IP address" in Settings and the app uses only the relay, so the other person sees the relay's address instead; relayed transfers are slower and capped at 2 GB.
Everything else it does stays on your device:
- Clipboard
The app reads your clipboard only when you paste (Ctrl+V) to stage copied files or a screenshot for sending. It never reads the clipboard in the background.
- Files
Received files are written to the folder you choose (your Downloads folder by default). A drop through a request link goes into a new folder of its own inside the folder you choose for that link (a Floe folder inside Downloads by default). Nothing is uploaded anywhere.
- History
The app keeps a list of your last 50 transfers on your device: for each one, the direction, the file names (for a received transfer, the names as saved and the folder they went to), the file count, the total size, and the time. A drop through a request link that saved files adds one row too, with your label, how many files matched their SHA-256 or were renamed, whether Auto-accept took the drop, and why it stopped if it did; the link itself is never kept. While a drop arrives, the app also keeps its label, its counts and sizes, its folder, up to 200 saved file names, and when it was last written in a small file beside your settings, so a drop the app was closed during still gets its row; the file is deleted when the drop ends, or read and deleted the next time the app starts. It never leaves your device. Remove one entry with Remove inside its row, or all of them with Clear in the History view. Reset in Settings and uninstalling the app both leave it in place.
- Settings
Your preferences are stored locally in your user profile and are not removed automatically when the app is uninstalled.
- Right-click menu
The GitHub build can add a "Send with Floe" entry to the File Explorer right-click menu. To turn it off, use "Show in right-click menu" in Settings, and the entry is removed. The installer build turns it on by default: each time the app starts and the entry is missing, it writes one per-user registry key (with a command subkey) that holds the app's location, unless you have turned the switch off. The portable zip adds the entry only after you turn the switch on. When it starts, Floe leaves alone an entry that belongs to another copy of the app, and repairs or removes one whose program is gone. Turning the switch on in one copy moves the entry to that copy. The installer's uninstall removes the key too. The portable zip has no uninstaller, so turn the switch off before deleting that build. The Microsoft Store build does not offer this entry.
- Notifications
The app shows standard Windows notifications when a transfer completes or fails and its window is in the background, when someone wants to send you files through your request link, and when a drop starts by itself through a link made with Auto-accept. A notification never shows a file name, your label, or anything the sender chose. "Notifications" in Settings turns them off or makes them silent; a request then still flashes the taskbar button and shows in the window title. On the GitHub build, Windows needs a per-user registry entry (under
HKCU\) and a small icon file in your temp folder to show Floe's name on its notifications; the app writes them, and uninstalling leaves them.Software\ Classes\ AppUserModelId
Everything the app keeps on your PC, with where it is and how long it stays, its update check's last answer and its temporary folders included, is listed in the security and privacy docs.
When installed from the Microsoft Store, installation and automatic updates are handled by Microsoft; see Microsoft's privacy statement for what the Store itself collects.
The CLI
The floe command line tool runs the same peer-to-peer engine as Floe Desktop. floe send and floe receive contact our signaling server (api.floe.one, or the server you name with --server or FLOE_SERVER) to register or look up a short code, fetch relay credentials, and pair you with your peer, and then stream file data directly between devices, or through the relay described above when no direct path exists. The CLI contains no analytics, no error monitoring, and no telemetry, and it keeps no history or log of your transfers. It makes two optional requests, each with its own off switch:
- Byte total
After a transfer completes,
floe receivesends the number of bytes it received to the signaling server it used, so on a self-hosted server the count goes to that server and not to us. Pass--no-report, or setFLOE_NO_STATS=1, and no request is made.floe sendnever reports.- Update check
floe versionasks GitHub whether a newer release exists when you run it. An answer is kept for 24 hours in a small file in your user configuration folder, so once a check has succeeded it asks at most once a day. A check that fails is not kept, and the nextfloe versionasks again. The request carries nothing about you beyond what any web request reveals (your IP address and a generic user agent); nothing about your files or transfers is in it, and nothing downloads or installs by itself. SetFLOE_NO_UPDATE_CHECK=1and no request is made.floe sendandfloe receivenever check, and a build compiled from source never checks.
floe update is you asking for an update: it fetches the latest release, its checksum file, and the archive for your platform from GitHub, verifies the SHA-256 checksum, and replaces the binary. On an install managed by Homebrew, Scoop, or Winget it stops before any request and points you at your package manager instead.
Received files go to the folder you pass with -o (the folder you ran the command in, by default).
--relay-only (or FLOE_RELAY_ONLY=1) routes your side through the relay, so the other device sees the relay's address instead of yours.
Contact & reports
Questions about this policy, bug reports, and abuse reports go to our public issue tracker: github.com/jannskiee/floe/issues. Two things should not go there. If you have found a security vulnerability, report it privately through GitHub's Report a vulnerability form or the email address in our security policy, so the problem is not public before there is a fix. If an abuse report includes personal details about anyone, or copies of the content itself, send it through those same private channels instead of opening a public issue.
Because Floe is peer-to-peer, transferred content never sits on our servers: a direct transfer never touches them, and a relayed one passes through the relay only as encrypted packets it cannot read. We cannot see, store, or remove files that users send to each other. What we can do in response to a report is limited, and we would rather say so plainly. Our signaling server keeps no record of who joined which room once that room has closed, and the relay credentials it hands out are shared by everyone who asks within the same few minutes rather than issued per person, so we cannot identify a particular sender after the fact or cut off their relay access alone. Per-address rate limits and a few fixed caps apply to everyone automatically, and that is the extent of what the service does on its own.
To report a request link, use "Report this link" at the bottom of its page, or write to paredesjancarlo99@gmail.com with the part of the link before the #. Never post a whole request link anywhere public: the part after the # lets anyone use it. What we can do about a reported request link is limited as well: we can turn request links off for everyone, or wait for the link to end on its own, which it does within about 7 days of being made. We still cannot tell who made it or who used it.
If you believe Floe is being used to send you illegal or harmful content, do not open further links or codes from that sender (a transfer only starts when you open one, and the command line asks before it accepts) and report the details as described above. If someone you did not expect tries to send you files through your request link, click Decline (or Cancel drop, on a link made with Auto-accept), close the link, and report it.