open-transfer

Open Transfer logo

Open Transfer

AirDrop for every device. Your Windows PC, Mac, Android phone and Samsung tablet find each other on the same Wi‑Fi — pick one, several or all of them and send. Files go straight from device to device. No account, no cloud. Devices without the app can join from any browser by scanning a QR code.

Website · Download · Docs

CI Security License: MIT Python 3.10+ Platforms

Demo: a Mac drops two files, picks a Pixel phone and a Galaxy tablet, the phone taps Accept, and both show Delivered

▶ Watch the 20-second demo with sound · recorded from the real app: four devices, one real transfer


Contents

Why Open Transfer

  Open Transfer AirDrop Cloud drives
Windows ↔ Mac ↔ Android ↔ tablets, in any direction ✅ Apple only ✅
Finds nearby devices automatically, sends to the ones you pick ✅ ✅ ❌
Device to device — files never leave your network ✅ ✅ ❌
Works on devices without the app (any browser, via QR code) ✅ ❌ ✅
No account, no size caps, no subscription ✅ ✅ ❌
Open source, self-hostable, scriptable (curl) ✅ ❌ ❌

Quick start

Option 1 — Download the app (no Python needed)

Install it on each device from the latest release. Open it on two devices on the same Wi‑Fi and they appear in each other’s Nearby devices.

Device File First launch
Windows 10/11 (x64) open-transfer-windows-x64.exe SmartScreen may warn about an unknown publisher → More info → Run anyway. Allow Private networks when the firewall asks (needed to be found).
macOS 11+ Apple silicon open-transfer-macos-arm64.dmg Open the .dmg, drag Open Transfer to Applications. The app isn’t notarised yet: the first time, right-click → Open (or System Settings → Privacy & Security → Open Anyway). Allow local network access when asked.
macOS 11+ Intel open-transfer-macos-x64.dmg Same as above.
Android 10+ phones & tablets (incl. Samsung Galaxy Tab) open-transfer-android.apk Allow installing from your browser/Files app when Android asks (“Install unknown apps”). Allow notifications so you see incoming files. 64-bit devices only.
Linux (x64, command line) open-transfer-linux-x64.tar.gz tar -xzf open-transfer-linux-x64.tar.gz && ./open-transfer — opens in your browser

Received files go to Downloads/Open Transfer on every platform.

iPhone / iPad, or a device you can’t install on? Open the link or scan the QR code shown under Add device on any device that runs the app. The browser joins the group and can send and receive (through that device).

Build the apps yourself (Python 3.10+, on the OS you want it for):

python3 scripts/build_app.py --desktop   # Windows: dist/Open Transfer.exe · macOS: dist/open-transfer-macos-<arch>.dmg
python3 scripts/build_app.py             # the single-file command-line build
gradle -p android assembleRelease        # Android (needs the Android SDK; see android/README.md)

CI builds every platform on each run (downloadable from the run’s Artifacts) and smoke-tests them: the desktop apps open their real window, and the Android app receives a real transfer in an emulator.

Option 2 — Run from source

You need Python 3.10+ (download). Everything else is installed for you in a private .venv folder on first run.

macOS / Linux

git clone https://github.com/itsonu/open-transfer.git
cd open-transfer
./run.sh

Windows (PowerShell)

git clone https://github.com/itsonu/open-transfer.git
cd open-transfer
.\run.ps1

Your browser opens and the terminal shows the address and a QR code:

  Open Transfer  v3.0.0

  This device        Studio-Mac  · visible to nearby devices
  On this computer   http://localhost:5000
  On your network    http://192.168.1.24:5000
  Saving files to    /Users/you/open-transfer/uploads

  Scan with your phone's camera:
  ▄▄▄▄▄▄▄ ▄ ▄▄ ▄▄▄▄▄▄▄
  █ ▄▄▄ █ ▀█▄▀ █ ▄▄▄ █   …
Other ways to run it — pipx, Docker, standalone binary
Method Command
pipx / uv (installs the open-transfer command) pipx install git+https://github.com/itsonu/open-transfer or uv tool install git+https://github.com/itsonu/open-transfer
Docker docker compose up -d (see Self-hosting)
Standalone app (no Python needed) See Option 1, or build it with python3 scripts/build_app.py
From source, manually python -m venv .venv && .venv/bin/pip install -e . && .venv/bin/open-transfer

Common recipes

./run.sh ~/Desktop/Inbox          # save received files somewhere else
./run.sh --name "Studio Mac"      # the name other devices see
./run.sh --paired-only            # only accept files from paired devices
./run.sh --peer 192.168.1.30:5000 # connect directly where discovery is blocked
./run.sh --pin                    # browsers joining by link need a PIN
./run.sh ~/Share --share-folder   # classic mode: anyone with the link sees the folder

Put the folder before --pin — --pin takes an optional value, so --pin ~/Share would treat the path as the PIN.

How it works for your users

  1. Open Open Transfer on your devices. Each one shows the others under Nearby devices within a second or two.
  2. Pick who gets the files — tap one device, several, or Select all — and choose or drop files. (Or drop files straight onto a device.) Sending to everyone asks you to confirm first.
  3. Accept on the other side: “Gaming PC wants to send you 3 files”. Paired devices skip this step.

Any device can send to any other — there’s no host or server role. A phone without the app scans the QR code under Add device and joins from its browser.

Pairing (optional): on one device open Add device to see a QR code and a 6-digit code; on the other choose Enter a code (or Scan its QR code in the Android app). Paired devices accept each other’s files automatically and stay listed when they’re away.

Features

Nearby devices

Transfers

Experience

Operating it

Screenshots

Nearby devices Choose who gets it Per-device progress
This device and three nearby devices: a Galaxy Tab, a Windows PC and a Pixel phone Two devices selected, with the files and recipients shown before sending Transfer to two devices: one accepted, one waiting
Accept on the tablet Add a device (QR + code) Phone, dark mode
Galaxy Tab asked to accept two files from a MacBook Add a device sheet with a QR code and 6-digit pairing code Phone layout in dark mode

Screenshots are generated from the real app by scripts/screenshots.py, the demo video at the top by scripts/demo/build.py.

Configuration

Every option works as a command-line flag or an environment variable (flags win).

Flag Environment variable Default What it does
DIRECTORY OPEN_TRANSFER_DIR ./uploads (apps: ~/Downloads/Open Transfer) Where received files are saved
--name OPEN_TRANSFER_NAME host name Name other devices see (can also be changed in the app)
--form OPEN_TRANSFER_FORM computer computer, phone or tablet — the icon others see
--peer HOST:PORT OPEN_TRANSFER_PEERS (comma-separated) — Connect to an app directly (networks that block discovery)
--no-discovery OPEN_TRANSFER_NO_DISCOVERY=1 off Don’t announce this device or look for others
--paired-only OPEN_TRANSFER_PAIRED_ONLY=1 off Only accept files from paired devices
--auto-accept OPEN_TRANSFER_AUTO_ACCEPT=1 off Accept incoming files without asking (servers)
--share-folder OPEN_TRANSFER_SHARE_FOLDER=1 off Classic mode: anyone who opens the link can browse the folder
— OPEN_TRANSFER_DISCOVERY_PORT 47823 UDP port for discovery
-p, --port OPEN_TRANSFER_PORT 5000 Port (the next free one is used if it’s busy)
--host OPEN_TRANSFER_HOST 0.0.0.0 Interface to listen on; 127.0.0.1 = this computer only
--pin [PIN] OPEN_TRANSFER_PIN off Require a PIN (4–32 letters/digits). No value = random 4 digits
--max-size OPEN_TRANSFER_MAX_SIZE unlimited Largest single upload, e.g. 500M, 4G, 2GiB
--receive-only OPEN_TRANSFER_RECEIVE_ONLY=1 off Shared-folder mode: visitors can send but not see files
--read-only OPEN_TRANSFER_READ_ONLY=1 off Visitors can’t send (shared-folder mode: download only)
--no-delete OPEN_TRANSFER_NO_DELETE=1 off Shared-folder mode: visitors can’t delete files
--public-url OPEN_TRANSFER_PUBLIC_URL auto Address shown in the QR code (Docker, proxies, DNS names)
--allow-host NAME OPEN_TRANSFER_ALLOWED_HOSTS (comma-separated) — Extra host names to accept (see DNS rebinding); * disables the check
--behind-proxy OPEN_TRANSFER_BEHIND_PROXY=1 off Trust X-Forwarded-* from one reverse proxy
--no-browser OPEN_TRANSFER_NO_BROWSER=1 off Don’t open a browser on start
--no-qr OPEN_TRANSFER_NO_QR=1 off Don’t print the QR code
-v, --verbose — off Log every request

Security & privacy

Open Transfer is designed for trusted local networks (home, office, a hotspot you control).

What it does for you

What you should know

Found a vulnerability? Please report it privately — see SECURITY.md.

Self-hosting & Docker

docker compose up -d        # builds the image and serves ./uploads on port 5000

Or use the image published with each release, without cloning:

docker run -d --name open-transfer -p 5000:5000 -v "$PWD/uploads:/data" \
  -e OPEN_TRANSFER_PUBLIC_URL=http://192.168.1.24:5000 ghcr.io/itsonu/open-transfer

The image acts as an always-on hub: browsers that open it see the shared folder, and files apps send to it are accepted automatically (OPEN_TRANSFER_SHARE_FOLDER=1, OPEN_TRANSFER_AUTO_ACCEPT=1). Discovery needs the host network (network_mode: host, the compose default; Linux). Otherwise set OPEN_TRANSFER_PUBLIC_URL so the QR code points to the right place, and add the server in your apps by address. The image runs as a non-root user and has a built-in health check.

HTTPS with Caddy or nginx, running as a systemd service, and NAS tips: docs/self-hosting.md.

Architecture

Every device runs the same small Python program: an HTTP server, discovery, and the web UI. Apps talk to each other directly; browsers without the app join through the device whose link they opened.

flowchart LR
    subgraph LAN["Your Wi‑Fi"]
        W["Windows app"] <-->|"offer · accept · stream"| M["Mac app"]
        W <--> A["Android app"]
        M <--> A
        A -. "multicast announce" .- W
        P(("iPhone browser")) -- "opened Android's QR" --> A
    end
Module Responsibility
node.py / cli.py / desktop.py / android.py Start a device: CLI, native desktop window, or the Android app’s Python side
discovery.py Multicast announce / reply / find / bye
mesh.py Nearby devices, visitors, offers, one-to-many streaming, pairing
mesh_api.py App↔app protocol (/api/p2p/v1) and the UI’s device endpoints
devices.py Device identity, paired devices, signatures, pairing proofs
app.py, storage.py, security.py Flask app, safe streaming storage, host/origin/PIN guards
static/, templates/ The web UI: vanilla ES modules + CSS, no build step
android/ Kotlin shell: WebView, file picker, QR scanner, foreground service

Details: docs/architecture.md · wire format: docs/protocol.md · real-device checklist: docs/device-testing.md.

Development

make setup     # venv + dev tools + git hooks + Playwright Chromium
make dev       # run on a scratch folder with request logging
make check     # everything CI runs: lint, types, unit + browser tests
Command What it does
make test Unit & API tests (pytest)
make e2e Real-browser tests with Playwright against a live server
make lint / make fmt Ruff lint + format, JS syntax check
make typecheck mypy (strict)
make audit Dependency vulnerability scan (pip-audit)
make cov Coverage report
make docker / make app Container image / standalone app (scripts/build_app.py)

The front end is plain HTML, CSS and ES modules in src/open_transfer/static/ — edit and reload, no bundler. To try several devices on one machine, start more copies with their own folders: open-transfer /tmp/a --name A and open-transfer /tmp/b --name B --port 5001 find each other. CI runs on Linux, macOS and Windows across Python 3.10–3.14, plus browser tests, a Docker smoke test, CodeQL and dependency audits.

Troubleshooting

Devices don't see each other
My phone can't open the link
"Port 5000 is busy, using 5001 instead"

On macOS, AirPlay Receiver uses port 5000. Open Transfer picks the next free port automatically — use the address it prints, or choose one with --port 8080.

"Requests for '…' are not accepted"

You’re reaching the server by a host name it doesn’t recognise (DNS-rebinding protection). Start it with --allow-host that.name or use the IP address.

Uploads fail behind nginx

nginx limits request bodies to 1 MB by default. Set client_max_body_size 0; and proxy_request_buffering off; — see docs/self-hosting.md.

Roadmap

Have an idea? Open a feature request.

Contributing

Contributions of all sizes are welcome — bug reports, docs, design polish and code. Start with CONTRIBUTING.md; it takes about two minutes to get a dev environment running (make setup). Please follow our Code of Conduct.

License

MIT © Open Transfer contributors · Maintained by Chandrabhushan Prakash