# QR Code File Transfer — complete documentation > Free open-source air-gapped file transfer. Turn any file into an animated QR code stream on one screen, point the other device’s camera at it, and the file is reconstructed locally. No network, no server, no upload, no pairing, no app install. Canonical site: https://qrfiletransfer.app Source code: https://github.com/Hitesh-Sisara/qr-code-file-transfer (MIT) Generated from the same content model that renders the HTML pages, so this file cannot drift from what a human reads. --- ## Overview QR Code File Transfer moves a file from one device to another as a live animated QR code stream. The sending device paints codes on its screen; the receiving device reads them with its camera and rebuilds the file. There is no network hop anywhere in that path — no server, no upload, no Bluetooth, no Wi-Fi, no cable, no pairing, no account, no app to install. Both sides are a web page. It works across an **air gap**: the receiving device can have every radio switched off and still receive the file, because the only channel is light between a screen and a lens. That makes it useful in exactly the situations where normal transfer methods fail — a locked-down machine, an isolated network, a phone with no cable that fits, two devices from ecosystems that refuse to talk to each other. --- # Documentation ## Quick start 1. On the **sending** device, open the home page and choose a file. 2. On the **receiving** device, open `/scan` and allow camera access. 3. Press **Start streaming** on the sender. It goes fullscreen and begins cycling QR codes. 4. Point the receiver’s camera at the sending screen, keeping the whole code in frame. 5. Check the six-character match code shown on both devices agrees, then press save when the receiver reports the file is complete. > **Note:** The stream repeats forever until you stop it, so a receiver that starts late still finishes. There is no need to synchronise the two devices or restart the sender. Only the **receiving** side needs a camera. Any pair works: Mac to iPhone, Windows to Android, Linux to iPad, phone to phone. ## What people call this This category has no settled name, which makes it hard to search for. If you arrived looking for any of the following, this is the tool you were looking for: - **QR code file transfer** / **animated QR code file transfer** / **QR code data transfer** - **Air-gapped file transfer**, **air gap data transfer**, **sneakernet replacement** - **Transfer a file between two devices without internet**, **without Wi-Fi**, **without a cable**, **without USB** - **Offline file transfer**, **local file transfer**, **no-server file transfer**, **no-upload file transfer** - **Private** or **privacy-friendly file transfer** — nothing is uploaded, so there is nothing to leak - **Screen-to-camera data transfer**, **optical data transfer**, **visual light data transfer** - **Phone to laptop**, **laptop to phone**, **iPhone to Android**, **PC to mobile** file transfer What it is **not**: a file-sharing service, a cloud drive, a chat app, or a LAN transfer tool. Nothing is stored anywhere, and no two devices ever join the same network. ## How it works A QR code holds at most a couple of kilobytes, so a single code cannot carry a file. The file is therefore split into many small pieces and streamed as a rapid sequence of codes — think of it as a one-way optical link running at up to 30 frames per second. The hard part is that a camera will inevitably miss frames: it blurs, it refocuses, it exposes badly, and its shutter is not synchronised to the sender’s display. A naive "frame 1, frame 2, frame 3" scheme would need the receiver to catch every single frame, and then to ask for retransmission of the ones it missed — which is impossible here, because there is no channel back to the sender. The fix is a **fountain code**. Instead of numbered pieces, the sender emits an endless stream of symbols where *any* sufficiently large subset can reconstruct the file. The receiver does not care WHICH frames it caught, only HOW MANY. Missing a third of the frames costs a little time and nothing else, and a receiver can join the stream halfway through. Specifically it is a **systematic LT code**: the first pass sends the source data itself, so a clean read costs zero coding overhead, and repair symbols (XOR combinations of pseudorandomly chosen pieces) follow for anything the camera missed. Every symbol is derivable from `(seed, blockIndex, symbolIndex)` alone, which is what lets a receiver start late, take frames out of order, and skip any subset without replaying anything. ### Frame format Each QR code carries a binary frame with 19 bytes of overhead, encoded in QR byte mode with no Base45 layer: ```text byte 0 magic 0xA | protocol version byte 1 frameType | laneId | profileId bytes 2-9 transferId uint64 bytes 10-11 blockIndex uint16 bytes 12-14 symbolIndex uint24 bytes 15..n payload last 4 CRC-32 over bytes 0..n ``` The transfer ID is not decoration. CRC-32 alone cannot reject a frame belonging to a **different** transfer — a neighbouring sender, or the same file restarted, produces frames that parse cleanly and would be XORed into the decoder as valid-looking equations, silently corrupting the result. A 120-byte **descriptor** frame is interleaved every 24 frames, carrying what the decoder needs before it can decode anything: symbol count, symbol size, block layout, PRNG seed, file length, and both hashes. It sits deliberately OUTSIDE the fountain-coded object, because putting it inside is circular — you cannot decode the object without first knowing how. ## How the file is verified An optical channel is noisy, so a received file must be **proven** correct rather than assumed. Four gates run in order, and the save button unlocks only when all four pass: 1. **CRC-32 per frame** — rejects blur, glare and partial reads before a frame reaches the decoder. 2. **Transfer ID match** — rejects a well-formed frame from a different transfer. 3. **BLAKE3-256 of the compressed object**, checked *before* decompression, so a corrupt stream can never be fed to the decompressor. 4. **BLAKE3-256 of the original file** — the final gate. The bytes you save are proven identical to the bytes that were sent. CRC-32 is used only where it is genuinely strong: per frame. It is deliberately NOT used for whole-file verification, because a 1-in-4-billion collision chance is far too weak when a single accepted bad symbol corrupts the entire reconstruction. The six-character code shown on both screens identifies **which** transfer you are looking at — useful when two people are streaming in the same room. It is not an integrity proof; the two BLAKE3 hashes are. ## Speed, and what the numbers mean Rates below are **raw optical payload** — the bytes the codes carry, before coding overhead and before compression. Capacities were verified against the ISO/IEC 18004 codeword tables rather than assumed. | Profile | QR version | Error correction | Lanes | fps | Frame payload | Raw rate | Status | | --- | --- | --- | --- | --- | --- | --- | --- | | Safe | V20 | M | 1 | 15 | 647 B | 9.7 KB/s | stable | | Standard | V25 | L | 1 | 30 | 1254 B | 37.6 KB/s | stable | | Fast | V30 | L | 1 | 30 | 1713 B | 51.4 KB/s | stable | | Turbo | V30 | L | 2 | 60 | 1713 B | 102.8 KB/s | experimental | | Lab | V40 | L | 2 | 60 | 2934 B | 176.0 KB/s | experimental | Four numbers get conflated in this space and should not be: **raw payload rate** (the table above), **acquired rate** (raw multiplied by how much the camera actually catches), **fountain goodput** (acquired minus coding overhead), and **file goodput** (adjusted for compression). Only the first is a specification; the rest depend on your screen, your camera and your file. > **Caution:** Turbo and Lab are specified and arithmetically correct but **not validated on real display-to-camera hardware**, and the app labels them as such. `requestAnimationFrame` is not a scanout guarantee, and camera row exposure is unsynchronised with display refresh. Lab on a 1920px-wide display gives each QR module about 5 device pixels, which is thin. Practical expectation: a text document or a small PDF moves in seconds. A photo takes tens of seconds. This is not the right tool for a video file — and the honest recommendation, if you have a cable or a network, is to use it. ## Limits and non-goals - **64 MiB maximum file size, and that number is measured.** Preparing a transfer holds three full-size representations at once — the raw file, the compressed object, and the fountain source block — for a measured 3.0x peak memory multiplier. An earlier draft claimed 512 MiB, which would need roughly 1.5 GB live and is killed by mobile Safari long before it gets there. A test asserts cap × multiplier stays under 256 MB so the claim cannot quietly rot. - **Anyone with line of sight can read the stream.** The bytes never touch a network, but a QR code on a screen is readable by any camera pointed at it. This is privacy through locality, not encryption. - **No sender authentication.** The hashes travel over the same unauthenticated optical channel as the data, so someone controlling the sending display could substitute both. The hashes prove the file arrived intact; they do not prove who sent it. - **One-way, open loop.** There is no receiver-to-sender channel, so there is no calibration and no in-flight rate adaptation. The receiver owns all progress state and tells the user what to do — the human is the feedback channel. - **No telemetry, no database, no server state.** The Worker serves static assets and nothing else. There is no analytics script, no cookie, and no third-party origin in the CSP. ## Privacy and security posture The file is read by the browser, processed in memory, and painted to a canvas. It is never sent anywhere. There is no upload endpoint in the application because there is no server-side application — the deployment is a Cloudflare Worker that serves static files. - **Zero third-party origins.** Every CSP directive is `'self'` or `'none'`. There is no analytics, no font CDN, no error reporter. A test fails if an external host reappears. - **No webfont at all**, so not even a font request leaves the browser. - **Received filenames are treated as hostile**: path separators stripped, control characters removed, never rendered as markup, and the download blob is always `application/octet-stream` regardless of the MIME type the sender claimed. - **Camera access only on `/scan`**, enforced by `Permissions-Policy`, and the stream is stopped on unmount rather than left running. - **Works fully offline** after one visit. You can load the page, turn off every radio, and still complete a transfer — which is the only honest way to claim an air-gapped tool. ## Run it yourself The whole app is static. Clone it, build it, and serve the output from anything — including a laptop on an isolated network with no internet at all. ```bash git clone https://github.com/Hitesh-Sisara/qr-code-file-transfer cd qr-code-file-transfer npm install npm run dev # local dev server npm test # the full suite npm run build # production build npm run preview # serve the production build locally ``` Requires Node 22.13 or newer. Deploying to Cloudflare Workers is `npm run deploy`; any static host works equally well, since there is no server-side code to run. --- # Frequently asked questions ## How do I transfer a file between two devices without internet? Open this page on both devices, pick a file on the sender, and point the receiver’s camera at the sender’s screen. The file travels as an animated QR code stream — light between a screen and a lens — so no internet, Wi-Fi, Bluetooth, or cable is involved at any point. The page itself does need to load once. After that it works fully offline, so you can open it while you still have a connection, disable every radio, and complete the transfer with both devices isolated. ## What is air-gapped file transfer? An air gap is a deliberate physical separation between a device and any network. Air-gapped file transfer means moving data across that separation without bridging it — historically with a USB stick, which is exactly the thing most locked-down environments forbid, because a USB device can carry executable payloads and firmware attacks. An optical QR stream is a strictly one-way, data-only channel: the receiving device gets nothing but pixels through its own camera, and there is no path back to the sender at all. Nothing is plugged in and no network is joined. ## Is QR code file transfer secure and private? Your file is never uploaded, so there is no server copy to leak, subpoena, or breach. It is read by your browser, turned into QR codes, and reconstructed by the other browser. No account, no logging, no telemetry, no third-party requests. The honest caveat: this is **privacy through locality, not encryption**. A QR code on a screen is readable by any camera with line of sight, so do the transfer somewhere you would be comfortable holding the document up. If you need confidentiality against an observer in the room, encrypt the file before sending it. ## What is the maximum file size? 64 MiB, and that limit is measured rather than guessed. Preparing a transfer holds three full-size copies of the data in memory at once, a measured 3.0x peak multiplier, and mobile Safari terminates a tab well before the numbers a larger cap would require. In practice the useful ceiling is lower than the hard one. Documents, PDFs, keys, configs, spreadsheets and photos are comfortable. A video file is technically possible and practically a bad idea — at 37.6 KB/s a 50 MB file takes over twenty minutes of holding a phone steady. ## How long does a transfer take? At the default Standard profile the raw optical rate is 37.6 KB/s, so roughly 40 KB per second in good conditions. A 100 KB document takes a few seconds; a 5 MB photo takes a couple of minutes. Real throughput depends on your screen brightness, camera quality, distance, and how steadily the code is held in frame. Missed frames cost time but never correctness — the fountain code simply needs enough symbols, not any specific ones. ## Which devices and browsers does it work between? Any two devices where one has a screen and the other has a camera, in any combination: iPhone to Windows laptop, Android to Mac, Linux to iPad, phone to phone. Both sides are just a web page, so the two devices do not need to share an ecosystem, an account, or a network. It needs a current browser — Chrome, Edge, Safari or Firefox — with camera access on the receiving side. Camera access requires HTTPS (or localhost), which is a browser rule, not a choice this app makes. ## Do I need to install an app? No. It runs entirely in the browser with nothing to install, no account to create, and no pairing step. You can optionally add it to your home screen as a PWA, which caches it for offline use, but that is a convenience rather than a requirement. ## Why an animated QR code instead of one static code? A single QR code holds about 3 KB at most, which is not enough for a real file. Streaming many codes turns that hard ceiling into a bandwidth figure instead. The reason it is a **fountain-coded** stream rather than a numbered sequence is that cameras drop frames constantly and there is no way to ask for a retransmission over a one-way channel. With a fountain code the receiver only needs *enough* symbols, not specific ones, so dropped frames cost a little time and nothing else. ## What happens if the camera misses frames or I start scanning late? Nothing breaks. The sender loops forever, and every symbol is independently derivable from the stream’s seed, so the receiver can join at any point, take frames out of order, and miss any subset. It simply keeps collecting until it has enough. If the receiver is starved of frames it reports the transfer as **incomplete** and refuses to hand over a partial file. A truncated file that looks successful is the one outcome worse than a failed transfer. ## How is this different from AirDrop, Nearby Share, or a LAN transfer tool? Those all need the two devices to reach each other over a radio or a network — and usually to be in the same ecosystem. AirDrop is Apple-to-Apple, Nearby Share needs Bluetooth and Google services, and LAN tools need both devices on one network with a route between them. This needs none of that. It works when radios are disabled, when the network blocks peer-to-peer traffic, when the two devices are from different vendors, and when the receiving machine is deliberately air-gapped. In exchange it is much slower, which is the trade. ## Does it really work with Wi-Fi and mobile data turned off? Yes, after the page has loaded once. A service worker caches the app shell and the QR decoder, and the app audits that cache rather than assuming — the offline badge reports what is genuinely present, including whether the decoder is cached, because a page that loads without its JavaScript is an empty shell that would fail at the moment you press the button. ## Is it free and open source? Yes. It is MIT-licensed and the full source is on [GitHub](https://github.com/Hitesh-Sisara/qr-code-file-transfer). There is no paid tier, no sign-up, and no telemetry. You can self-host it, audit it, or fork it. ## Can I self-host it or run it on an isolated network? Yes, and that is a first-class use case. The build output is static files with no server-side code, so `npm run build` and any static file server is enough — including one running on a laptop with no internet connection at all. ## What file types can I send? Any of them. The transfer is byte-exact and content-agnostic: documents, images, archives, keys, certificates, configs, binaries. The file is compressed before streaming, so text-shaped files move faster than already-compressed ones, but nothing is ever interpreted or transformed. ## Do both devices need a camera? No, only the receiving device. The sender just needs a screen. That is what makes desktop-to-phone work on a machine with no webcam, and it is why the transfer is one-way — there is no reverse channel from receiver to sender. --- # Terminology This category has no settled name. The tool is variously called: - qr code file transfer - qrcode file transfer - transfer file between two devices without internet - air gapped file transfer - air gap data transfer - offline file transfer - local file transfer - send file without internet - transfer file without wifi - transfer file without cable - transfer file without usb - phone to laptop file transfer - laptop to phone file transfer - pc to mobile file transfer offline - secure file transfer - private file transfer - privacy friendly file transfer - no upload file transfer - no server file transfer - peer to peer file transfer no internet - screen to camera data transfer - animated qr code file transfer - qr code data transfer - send file across air gap - browser file transfer no app - cross platform file transfer - iphone to android file transfer - open source file transfer