Afilmory's container build copies a Perl WebAssembly binary into two paths
Modern photo gallery for photographers, with S3/GitHub sync, EXIF details, maps, and a WebGL viewer.
At a glance
- What is it?
- Afilmory is a self-hostable photo gallery platform for photographers, written as a pnpm monorepo with a React single page app, a Next.js wrapper for search previews, and Hono services on PostgreSQL and Redis. The build is where its oddest detail lives, in a WebAssembly build of Perl that has to be placed twice.
- Who is it for?
- Afilmory suits a photographer who wants their own gallery with EXIF, a map, Live Photos and a WebGL viewer, and who is willing to run a pnpm monorepo with three frontends and three backend services. Three things to resolve before you commit.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 5, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Three frontends and three services in one pnpm workspace
The architecture section describes a monorepo rather than an application. On the front there are three packages: a single page app built with Vite and React Router, a Next.js server-rendering wrapper whose stated purpose is search and social previews, and a documentation site on VitePress. Behind them sit three services, a core API server, a dashboard backend for administration, and a separate gateway whose only stated job is authentication, plus shared packages for the web framework, the database schemas and the cache client. The frontend stack names React 19 with its compiler, Jotai for state, TanStack Query for fetching and caching, Tailwind and Radix for the interface, and i18next for languages. The backend is Hono with an ORM on PostgreSQL, Redis for caching and pub/sub, and WebSockets for live updates.
Storage is an adapter, and four of them ship in the box
Where the photographs live is the decision the design pushes onto you. The adapter list has four entries. The first covers anything S3-compatible, and the readme names four services under that heading: Amazon's own object storage, a self-hosted alternative, a European bucket provider and an Alibaba Cloud offering. The second uses a GitHub repository as the store, which turns a commit history into a photo library. The third imports from the Eagle application, so an existing desktop library can be the source. The fourth is the local file system, stated as being for development and testing. On top of that, tags are generated automatically from the directory structure, so how you name folders is how your photographs get labelled, and syncing is incremental, processing only new or modified files.
EXIF extraction runs Perl compiled to WebAssembly, in the browser
The container build contains the most surprising thing in the repository, and it is explained in a comment. After dependencies are installed, the build searches node_modules for a WebAssembly file belonging to a Perl runtime, copies it into the web application's public directory, and copies it a second time into a photographs subdirectory. The reason given is that an EXIF library depends on a TypeScript port of that runtime which loads the file by fetching it relative to the current page URL, so the asset has to exist under two paths. That is also why Perl itself is installed in the build stage. The user-facing consequence is that camera metadata is extracted client side, which pairs with the feature list: full EXIF display, automatic conversion of HEIC, HEIF and TIFF, multi-size thumbnails, blurhash placeholders, iPhone Live Photos, HDR images and Fujifilm film simulation recipes.
The manifest drives four packages the architecture tree does not mention
The package manifest is private, names the workspace explicitly, and pins its package manager to one release. Its scripts are where the tree stops matching the code. Building the manifest runs a package named for the builder through a command line entry point, and a second script runs that same package in cluster mode to produce a variant report:
pnpm build # Build production web app
pnpm build:manifest # Generate photo manifest (incremental)
pnpm build:manifest -- --force # Full rebuildThere is a script that prepares demo data by running a shell script and then building the web app, and another that rebuilds a remote repository through its own shell script. A mobile package has its own script, targeting an iOS local run, and a separate site package has development, build and preview scripts. None of those three packages appears in the monorepo diagram in the readme, so the diagram is a partial view of the workspace rather than a map of it.
pnpm is pinned in the manifest and stated loosely in the readme
Three version statements exist and they do not line up. The prerequisites section asks for Node 18 or newer, pnpm 10 or newer, and TypeScript 5.9 or newer. The manifest pins the package manager to a single pnpm release in its package manager field, which is what the corepack tooling reads, so a machine with pnpm 10 installed will be corrected or will fail depending on configuration. The container sidesteps the question entirely by starting from a rolling long-term-support Node image and enabling corepack rather than pinning a Node version, which means the Node version inside the image is not the one in the readme either. The one version statement that is precise and enforced is the package manager, which is the right way round for a repository that expects a reproducible install with a frozen lock file. There is also a hook that runs on install and a set of git hooks registered through a separate package, which together mean a fresh clone has opinions about your workflow before you have run anything. Formatting is applied by a separate tool with an explicit file glob list covering three directory trees, and linting runs with a fix flag by default, so a lint invocation rewrites files rather than only reporting. Type checking is delegated to the workspace itself, and one script removes every node_modules directory in the tree before reinstalling, which is the blunt instrument you reach for when a dependency graph has gone wrong.
The compose file ships fixed database credentials and a literal key
The development compose file is short enough to read in full, and it contains values you would have to change. A Postgres 16 service sets its user, password and database name to the same word. A Redis 7 service is configured to append to its file rather than only to memory. The core service is built from a second Dockerfile with an explicit target stage, listens on a non-standard port, and receives a database URL and a cache URL pointing at those two services, plus a configuration encryption key written out as a literal sixty-four character hex string. Its start is gated on both dependencies reporting healthy, with a retry loop rather than a fixed wait. The documented Docker path for users is a separate repository, which is the one to read before deploying anything.
The licence field is undetermined and the name is a backronym
Two loose ends are worth settling before you build on this. The first is licensing. The repository's licence could not be determined from its metadata, and the manifest does not help: its licence field is a pointer to a file called LICENSE.md, while the top level of the tree holds a file called LICENSE. That is a small inconsistency, but it means the licence of a project with a hosted service, a mobile package and an iOS build target has to be established by asking. The second is naming, which explains the rest. The name expands to auto focus, aperture, film and memory, and the readme supplies a phonetic spelling for it. The repository carries 2646 stars and 324 forks with 39 open issues, has no published releases, and was last pushed on 1 October 2026.
Editorial conclusion
Afilmory suits a photographer who wants their own gallery with EXIF, a map, Live Photos and a WebGL viewer, and who is willing to run a pnpm monorepo with three frontends and three backend services. Three things to resolve before you commit. Work out the licence, because the repository's licence field is undetermined and the manifest points at a file name that does not match the file in the tree. Replace the compose credentials and the literal encryption key in the shipped compose file, since both are development values. And read the storage adapter list carefully, because the difference between a GitHub repository as storage and an S3 bucket is the difference between a weekend project and a real one. If you would rather not run any of this, the hosted version exists and the readme recommends it first.
Frequently asked questions
What is Afilmory?
A photo gallery platform for photographers, built with React and TypeScript, offering automatic synchronisation from several storage sources, WebGL rendering and full EXIF metadata display. The name expands to Auto Focus, Aperture, Film and Memory. There is a hosted version and a self-hostable one, and the readme recommends the hosted route first.
Can I self-host Afilmory?
Yes. Clone the repository, run pnpm install, copy config.example.json to config.json and builder.config.default.ts to builder.config.ts, build the photo manifest with the manifest build script, then start the app with the dev script. The Docker deployment guide lives in a separate Afilmory repository, while this one carries three Dockerfiles and two compose files of its own.
Where can Afilmory store photos?
Through storage adapters: any S3-compatible service, of which the readme names four, a GitHub repository used as storage, an Eagle application library, or the local file system for development and testing. Tags are generated from directory structure, and synchronisation is incremental so only new or modified photographs are processed.
What does building Afilmory require?
Node.js 18 or newer, pnpm 10 or newer and TypeScript 5.9 or newer, with the manifest additionally pinning the package manager to one specific release. The container build installs Perl and copies a WebAssembly build of it into two public paths, because the EXIF tooling loads that file relative to the page URL.
Does Afilmory handle HEIC files and iPhone Live Photos?
Yes. The feature list covers automatic conversion of HEIC, HEIF and TIFF, multi-size thumbnail generation, full EXIF display down to camera model, focal length, aperture and ISO, blurhash placeholders, detection and display of iPhone Live Photos, HDR images, and Fujifilm film simulation recipes.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/afilmory-afilmory)