Pho: a Flutter photo gallery that writes plain files to SMB, WebDAV or NFS
A serverless application for viewing and synchronizing photos to cloud storage
At a glance
- What is it?
- Pho is a serverless mobile photo viewer and uploader whose open source build covers local browsing, incremental sync and three network storage protocols. The interesting part is the storage layout, and the awkward part is the split between the GPL-3.0 repository and the paid Pro app.
- Who is it for?
- Adopt Pho if you already run SMB, WebDAV or NFS on a NAS and want your phone gallery to write plain files into a date-based tree you can read with any tool. Do not adopt it if you need iOS, AES encryption, parallel upload tuning or Baidu Netdisk, because those live in the paid Pro build, not this repository.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 27 days ago.
- What is it written in?
- Mainly Dart, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem Pho solves is the gap between a phone gallery and your own storage
Phone galleries are good at browsing and bad at getting files off the device. Syncing usually means a vendor cloud account, a desktop client, or a NAS app that wants to index everything into its own database. Pho takes the position that the phone should be the client and the network share should be the destination, with nothing in between. The README states the goal directly: replace the built-in gallery app and sync photos to network storage. It also states the scope just as plainly, calling the feature set simple and limited to viewing photos and syncing them to network storage.
The audience is the homelab or NAS owner who already has a share mounted somewhere. The supported storage list in the README is Samba, WebDAV and NFS, all checked. Alibaba Drive, OneDrive, Google Drive and Google Photos are listed as unchecked. That list is the clearest statement of who this is for: people whose storage is a box on their own network, not a cloud account. If your photos live in Google Photos today, Pho is not a migration path in the open source build.
No database and no server: the storage tree is the state
The design decision that shapes everything else is stated in the feature list as no database and no server. There is no index to rebuild and no daemon to keep alive. The cloud side of the gallery is derived from the directory tree on the share, and the local side is the phone's own media store. Incremental sync means comparing what is on the device against what is already on the share and uploading the difference.
The layout is documented with a directory diagram. Source files keep their original filenames and are filed by date, so a photo taken on 2 July 2022 lands at 2022/07/02/20220702_100940.JPG. Thumbnails go into a .thumbnail directory at the root of the share, mirroring the same year, month and day structure. The README explains the reasoning: you can use the uploaded photos in other ways at any time without depending on the app. That is the strongest argument in the whole repository. A backup you can browse with a file manager and read with any photo tool is worth more than a backup locked inside an application's private format.
The cost of that choice is visible in the same diagram. Filenames are preserved, so two devices producing the same filename in the same day folder collide. There is no content hash in the layout, so deduplication across devices is not something the storage format can express. The README does not describe collision handling, and I cannot confirm from the supplied material what happens when it occurs.
Dart on top, Go underneath, wired together with gRPC
The repository is mostly Dart, which is the Flutter application, but the topics list includes Go and gRPC, and the build section explains why. The network storage logic runs as an embedded Go server. The build targets produce a platform artifact for each one: server-aar for Android into android/app/libs/server.aar, server-ios producing ios/Frameworks/RUN.xcframework, server-linux producing linux/lib/run.so, and server-windows producing windows/lib/run.dll. The Go side is compiled into the app rather than shipped as a separate process, which is what makes the no-server claim hold: there is a server, but it is a library inside the client.
The two halves talk over protobuf-defined services. The prebuild step installs protoc-gen-go at v1.27.1, protoc-gen-go-grpc at v1.1.0, and protoc_plugin at 21.1.2 for Dart, then generates Go and Dart stubs. The pinned plugin versions matter here. Code generated by a mismatched protoc-gen-go-grpc will not compile against the runtime the project expects, and the README pins those versions for a reason.
This architecture is the source of the project's main build friction. Because the Go artifact is a binary blob per platform, you cannot simply clone the repository and run the Flutter app. The README warns about this explicitly: flutter run does not automatically build android/app/libs/server.aar, you must run make server-aar first, otherwise the Go server will not start. That single sentence explains most first-run failures.
Toolchain versions are pinned tightly enough to matter
The environment requirements are specific: Flutter 3.41.4 stable with Dart 3.11.1, Go 1.25 with toolchain go1.25.4, JDK 17, Android compileSdk 36, and the Android NDK for the gomobile bind step. The toolchain go1.25.4 line means the Go module declares a toolchain, so a machine with an older Go will try to download the right one rather than fail outright, assuming network access.
The build sequence in the README is four stages. make prebuild installs the protoc plugins, make protobuf generates the Go and Dart stubs, one of the server-* targets builds the embedded Go server for your platform, and make apk or make ipa builds the Flutter application. Testing is a separate target, make test, and the README notes it needs Docker because the SMB, WebDAV and NFS tests run against containers. That is a heavier test dependency than most mobile projects carry, and it follows from the protocol work being real rather than mocked.
Nothing in the material suggests a prebuilt development binary for desktop. The roadmap lists desktop support as unchecked, so the Linux and Windows server targets exist for a client that is not finished. Building run.so is not the same as having a desktop app to load it.
The open source build is Android-only and the Pro build is where the features are
This is the part a reader needs to understand before investing time. The README separates two products. The open source edition is an Android APK available from the releases page and contains only SMB, WebDAV and NFS. The Pro edition is distributed through the App Store and Google Play and adds AES encryption, parallel upload tuning, file filters, configurable directory structure, theme colour customisation and Baidu Netdisk support. The README states that these features are not open sourced.
The directory structure item deserves attention. The open source build files photos as year/month/day. The Pro build lets you choose between YYYY/MM/DD and YYYYMMDD. If you want the flat date format, that is a paid feature, and it changes the on-disk layout of your archive, so switching later means reorganising files yourself.
The encryption gap is the more consequential one. AES-128-CFB and AES-256-GCM uploads, with Range playback for encrypted video, are Pro only. If your threat model includes the storage provider reading your photos, the open source build does not address it. Encryption is also a one-way decision in practice: enabling it later means re-uploading or re-encrypting an existing archive, and the README does not describe a migration path for an archive that was synced unencrypted.
iOS users have no open source option at all. The README says the open source repository provides only an APK download and directs iOS users to the App Store. The ios/Frameworks/RUN.xcframework build target exists, so the code paths are there, but the distribution is not.
Where Pho is the wrong tool, and what to use instead
Pho assumes the storage endpoint speaks SMB, WebDAV or NFS and is reachable from the phone. If your photos live in Google Photos, OneDrive or Google Drive, the open source build cannot talk to them; those integrations are unchecked on the support list, and Baidu Netdisk is Pro only. If you need a web interface you can open from a laptop browser, Pho does not provide one. It is a mobile client, and the cloud view is a view of the share from inside the app.
A concrete alternative in the same problem space is Immich, which takes the opposite architectural position. Immich runs a server with a database and a web UI, and the mobile app talks to that server. The difference is not cosmetic. Immich can do server-side machine learning for search, shared albums and multi-user access, because there is a server to run that work on. Pho cannot, because the README's design explicitly rules out a database and a server. In exchange, Pho has no service to keep running, no database to back up, and no migration when the project changes its schema. Your archive is a directory tree.
Another comparison point is a plain sync tool such as Syncthing or rclone. Those move files reliably and do not try to be a gallery. Pho's value over them is the gallery experience: browsing local and cloud photos in one app, thumbnails generated and cached on the share, and a sync view that shows progress. If you only want files moved and you already have a gallery you like, a sync tool is less machinery. If you want the gallery and the sync in one app, that combination is what Pho is selling.
Licence, maintenance and what the release history shows
The repository is GPL-3.0. That is a copyleft licence, and it applies to the code here. It does not grant you the Pro features, which are distributed as separate paid applications and are not in this repository. If you fork Pho and distribute a modified APK, the GPL-3.0 obligations attach to your distribution. I am not a lawyer and this is not legal advice; if you plan to ship a modified build, read the licence text and get proper advice.
The release history is worth reading as a signal about cadence. v1.4.2 shipped on 5 September 2023, v1.4.5 on 25 September 2023, and then v1.5.0 on 29 July 2026. That is roughly a three-year gap between the 1.4.x line and 1.5.0. The repository was pushed to on 20 August 2026 and is not archived, so work continues, but the gap is real and anyone planning to depend on this should weigh it. The README's Contribute section is candid about the constraint: the maintainer writes that demand for features is considerable but that one person has limited capacity, and invites issues and pull requests.
Upgrade cost is low by design. There is no database schema to migrate and no server version to match, because the storage format is a date tree of original files plus a .thumbnail mirror. Upgrading the app does not require touching the archive. The one upgrade path that does require work is moving from the open source build to Pro, or enabling encryption later, since both change what is written to the share.
Editorial conclusion
Adopt Pho if you already run SMB, WebDAV or NFS on a NAS and want your phone gallery to write plain files into a date-based tree you can read with any tool. Do not adopt it if you need iOS, AES encryption, parallel upload tuning or Baidu Netdisk, because those live in the paid Pro build, not this repository. Before building, verify that gomobile can produce android/app/libs/server.aar on your machine, since flutter run will not build it for you and the Go server will not start without it.
Community notes