I can't believe Ava starts school in September.
Back in January I wrote that I couldn't quite believe it. It's July now, and she recently just left nursery, and I still can't 😩
We've been using Famly, to keep in contact with the nursery, share photos etc, which i'm sure many of you with children will recognise.
As the term was coming to an end, I wanted to export all the photos, and I just assumed there would be a bulk export. However, after talking to the nursery staff, there doesn’t appear to be a way in the backend to enable that, or at least they didn’t know how to.
You can save them one at a time. Right click, save as, repeat several hundred times. What you get is whatever size the page decided to render, not what the camera actually took.
I discovered, when opening the network tab while you click around your own account and you can watch the requests go by: a feed endpoint, a calendar endpoint, a GraphQL query behind the learning journey.
I want to be straight about what that is. Famly does have a documented public API, but it's a paid add-on for the nursery itself — there's nothing a parent can use. This is the internal one their own app uses, called with my own login, reading my own child's records. That's a meaningful difference and it shapes what I was willing to build. The tool only ever reads. Beyond logging in, it never writes, sends, or deletes anything on the account, and it works with credentials you already have.
It might also break tomorrow if they change something, and that's fine — Famly's own docs say as much about the internal API: no backward compatibility, breaking changes without warning, build entirely at your own risk. I needed it to work once, properly.
famly-cli is a command line tool that logs in, reads all the photos, and saves them to disk. It can be run incrementally, so you can run it once, then again later to pick up anything new.
The thing I underestimated was that the photos aren't kept in one place. There's no album. A photo of Ava might arrive as part of a learning journey observation, or a newsfeed post, or attached to a message from her key worker, or a note, or tagged by staff, or as her profile picture. Six different shapes, six different endpoints, six different response formats.
So the tool reads all six, merges them, and dedupes on the photo's id, because the same picture routinely turns up in an observation and again in the newsfeed post about that observation.
One decision I'm glad I made early: if a single source fails, it warns and carries on rather than killing the run. If authentication fails, the whole thing stops. An archive that quietly finishes empty and looks successful is worse than an error message.
The images carry their own dimensions in the URL path. Something like /prefix/800x600/key. The API also hands you the image's native width and height right alongside it.
Swap the numbers in the path for the real ones and you get the original file.
def full_res_url(image_obj: dict) -> str:
p, k = image_obj.get("prefix"), image_obj.get("key")
w, h = image_obj.get("width"), image_obj.get("height")
if p and k and w and h:
return f"{p}/{w}x{h}/{k}"
...That one function is the difference between a folder of 800px web images and a folder of actual photographs. Everything else in the project is plumbing around it.
Several hundred files named by date is technically an archive and practically unusable. So --gallery writes a single self-contained HTML file next to the photos, grouped by month, filterable by where each photo came from, searchable by caption.
No build step, no server, no dependencies. Double click it and it opens. Rebecca can use it, which was the actual test.
famly login
famly photos --incremental --gallery --out ~/Pictures/famly--incremental skips anything already saved, so re-running it just tops up. Which matters, because there are still a couple of weeks of nursery left to collect.
Nobody at the nursery gate is going to install pipx. But every one of those parents is in exactly the same position I was, and most of them have ChatGPT on their phone.
So the README has a prompt you can paste into any assistant. It walks you through checking your Python version, installing the thing, logging in, and getting your photos, one step at a time in plain language. It's explicitly told never to ask for your password in the chat, because the tool prompts for that privately in your own terminal.
pipx install git+https://github.com/shanegriffiths/famly-cli.gitPython 3.11 or newer. There's a demo mode with a bundled mock server if you want to see what it does before pointing it at your own account, so the tour in the video above contains no real data at all. Every name and photo in it is invented.
Honest Caveat
It's unofficial and not affiliated with Famly. Built and used day to day on macOS. CI runs the tests on Linux, macOS, and Windows, so all three are exercised, though I haven't leaned on the other two in anger. Use it with an account you control, within Famly's terms, at your own risk.
Source is on GitHub. Issues, PRs and feedback welcome.