Open-source project
nextcloud/photos avatar
nextcloud/photos

Nextcloud Photos: a self-hosted photo timeline with albums, favorites and tagging

📸 Your memories under your control

725 stars95 forksJavaScriptAGPL-3.0

At a glance

What is it?
Nextcloud Photos is a Nextcloud app that turns the files already on your server into a browsable photo and video timeline with favorites, tags, albums and slideshows. It needs the Viewer app, and its real dependencies for previews and face recognition live in separate apps.
Who is it for?
Nextcloud Photos is worth adding when your Nextcloud already holds a large media library and you want album, favorite and tag organisation without sending photos to a third-party service. It is the wrong tool if you expect server-side face recognition or fast previews out of the box, because both come from other apps you have to install and tune.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Photo organisation for a server you control

Nextcloud Photos is an app for a Nextcloud instance that answers a narrow question: how do you find things in a photo library that already lives on your own disk. Its own one line description is that your memories are under your control, and the feature list is deliberately short. There is a photo and video timeline, favorites and tagging, slideshows and easy sharing of photos or albums, and the ability to create albums from your contents.

That last point is the design decision that shapes the whole app. Albums are built from existing content rather than from a separate import step, so a folder of photos that arrived by sync, by mobile upload or by manual copy is immediately eligible. Nothing needs to be uploaded twice, and nothing needs to be re-encoded to be catalogue.

The audience is a Nextcloud operator who already trusts the server with the files and wants the browsing experience to stay there too. A family sharing photos through the mobile app, a club that keeps its archive on a hosted Nextcloud, a self-hosting user who objects to handing a photo library to a commercial gallery service. For any of them, the app is a front end over storage they already have.

It is not an editor, and it does not present itself as one. Nothing in the documented feature set covers cropping, filters or non-destructive adjustments. The value is retrieval and organisation, which is a different job.

The app is a PHP backend with a Vue frontend and a Vite build

The repository layout describes the architecture more honestly than any summary would. On the PHP side there is a lib directory, a templates directory, appinfo, a composer.json and composer.lock, and quality tooling in psalm.xml, rector.php and .php-cs-fixer.dist.php. On the JavaScript side there is a src directory, a package.json, vite.config.ts, tsconfig.json, babel.config.cjs, postcss.config.cjs, eslint.config.js and stylelint.config.cjs, plus a l10n directory for translations and an img directory for assets.

The dependency list in package.json makes the platform relationship concrete. The app builds on the @nextcloud family of packages, including @nextcloud/axios, @nextcloud/router, @nextcloud/paths, @nextcloud/auth, @nextcloud/initial-state, @nextcloud/files, @nextcloud/l10n, @nextcloud/event-bus, @nextcloud/dialogs and @nextcloud/logger. Those names tell you the app participates in the Nextcloud frontend conventions: routing goes through the server aware router, server provided state arrives through initial state, and permission checks come from the auth package.

The build itself runs on Vite, in two modes. There is a development mode with a watcher and a production mode, and a TypeScript type check is available through vue-tsc. Notably the package.json is marked private with a version of 9.0.0-dev.0, which tells you the frontend is built in the repository rather than published to a package registry, and that the version string tracks the app release line.

One structural detail is easy to miss: the top level has both a src directory and a js directory, and the Makefile cleans the js directory with rm -rf js. The js folder is build output committed for distribution, while src holds the sources that the build regenerates it from.

Enabling the app in Nextcloud 18 and later

Installation for end users is not a package manager step. The documentation says that in your Nextcloud you simply enable the Photos app through the Apps management, and that the app is included in Nextcloud 18 and higher. If you are on a recent server, the path is the app store, not a clone.

Three optional companions are named, and they are worth reading carefully because they are the difference between a slow gallery and a fast one, and between a browsable library and a searchable one.

The first is Recognize for Nextcloud, described as providing AI powered automatic face and object recognition, with automated tagging and categorising. This is a separate app, and it is the piece that adds face grouping. The second is the preview generator, installed if pre-generating thumbnails is desired. The third is Imaginary, recommended to speed up preview generation. The previews of large photos are the expensive operation in any gallery, and the documentation is candid that the base app does not generate them quickly on its own.

So the practical first use is not a code path but a decision: enable Photos from the app management, then decide which of the two preview paths you will run, since the documentation offers both a dedicated generator app and the Imaginary service. On mobile, the Android and iOS apps can view the photos and auto-upload them, which means new pictures can enter the timeline without anyone touching a browser.

A hard dependency on the Viewer app, and why the alternatives look different

The development setup states that this app requires the Viewer app to be installed and enabled, and instructs contributors to follow its development setup and then continue. That is a genuine architectural dependency rather than a recommendation, and it is worth understanding before you install anything.

The Viewer app is the media viewing component of the platform, and Photos builds its gallery experience on top of it rather than shipping its own viewer. This is the clearest structural difference between two ways to expose a media library to users. The path that avoids the Photos app entirely is to open media through Nextcloud Files and the Viewer app: you get browsing of the file tree and viewing of images and video, and you give up the timeline, favorites, tagging, albums and slideshow that Photos adds.

The other difference is where the intelligence lives. Photos itself is a browser and organiser. Recognition of faces, objects and categories comes from the Recognize app, and thumbnail generation comes from the preview generator or from Imaginary. Compare that with a hosted gallery service that performs recognition, resizing and sharing on its own infrastructure: there, one vendor holds the whole pipeline, and the trade is that your originals leave the machine.

For a self-hoster the split is usually preferable even though it means installing three apps instead of one. The cost is operational. You now maintain the preview path, and if the preview generator is not running, the first open of a large photo is slow for every user until a thumbnail exists.

Building the frontend with make dev-setup and make watch-js

Contributors clone the repository into the apps folder of a Nextcloud instance, then use the Makefile for everything else. The Makefile exposes the whole workflow as targets, and the top level default target chains the pieces together:

code
all: dev-setup lint build-js-production test

The setup target cleans the build output and the node_modules directory, installs npm dependencies with npm ci, and installs the PHP dependencies with composer install. The build targets are thin wrappers around the npm scripts, with build-js running the development build and build-js-production running the production build, and watch-js running the watcher.

The underlying npm scripts are where the real commands live:

json
  "scripts": {
    "build": "vite --mode production build",
    "dev": "export NODE_ENV=development; vite --mode development build",
    "lint": "eslint",
    "lint:fix": "eslint --fix",
    "test": "TZ=CET NODE_ENV=test vitest run",

The documentation adds a few details the Makefile does not spell out. It tells you to run make dev-setup to install dependencies, then make build-js whenever you change something, and make build-js-production before opening a pull request. For linting, the shortcut given is npm run lint:fix, and the scripts confirm that it maps to eslint --fix. The Makefile also has separate stylelint targets for the style sheets, which the npm scripts run as stylelint src.

After building, you enable the app through the app management of your Nextcloud and the timeline appears in the web interface.

Four kinds of tests, and an end-to-end test for every feature

The project takes testing seriously enough to spell out the layers. Unit tests exist, PHPUnit tests cover the backend, Vitest covers the frontend, and there are end-to-end tests. When adding a new feature, at least one end-to-end test for that feature should be added, and the documentation points to a Playwright readme for how to write one.

The tooling matches. There is a playwright.config.ts at the top level, and the npm scripts include separate Playwright entries for the default project and for faces, which lines up with the face recognition work that lives in the Recognize app. A Vitest coverage script is also defined, and the test command pins the timezone to CET and the environment to test, so a test run does not depend on the timezone of the machine that started it.

The end-to-end requirement is the part that shapes contributor behaviour. A new feature without an end-to-end test is incomplete by the project's own standard, which raises the cost of contributions and, arguably, the quality of what lands.

It is also worth holding that against the wider picture. Nextcloud Photos is one app in a large platform, so its test suite covers its own surface. A regression in the shared Viewer app or in the file access layer would show up in the gallery but would not be fixed here, which is one more argument for running the app on a platform that is kept up to date rather than pinned to an old server.

AGPL licensing, a single named maintainer and no GitHub releases

The licence is AGPL-3.0, and package.json spells out the exact form as AGPL-3.0-or-later, with the licence file in COPYING and a LICENSES directory alongside a REUSE.toml for per file licensing metadata. The AGPL is a network copyleft licence, so a modified version made available to users over a network carries source availability obligations. That is a factual reading of the identifier rather than legal advice, and anyone embedding a modified Photos app in a hosted service should have their own counsel read the licence text.

The repository keeps a single named maintainer, John Molakvoæ, and the README invites new contributors to join by picking up items from the issues list and fixing them. There is no GitHub releases feed for this repository, so version tracking happens through the app version in the source rather than through release artefacts on the platform, and the frontend package.json carries a development version of 9.0.0-dev.0.

Activity is not in question. The last push to the master branch was on 2026-10-20, the day before this review, and the repository carries a GitHub Actions build badge, a translation platform directory, and a test suite with four layers. The cost of adopting is not about whether the project is alive. It is about the three extra apps you need to run for previews and recognition, and the AGPL terms if you intend to modify and host it.

Editorial conclusion

Nextcloud Photos is worth adding when your Nextcloud already holds a large media library and you want album, favorite and tag organisation without sending photos to a third-party service. It is the wrong tool if you expect server-side face recognition or fast previews out of the box, because both come from other apps you have to install and tune. Before adopting, confirm the Viewer app is installed and enabled, since the development setup explicitly requires it, and check the preview path in your deployment, since the app depends on pre-generated thumbnails for the experience to feel quick. Under the AGPL-3.0-or-later licence, the module distribution terms apply to anyone shipping a modified version as part of a hosted service.

Frequently asked questions

How do I install Nextcloud Photos?

The documentation says to enable the Photos app through the Apps management in your Nextcloud, and the app is included in Nextcloud 18 and higher. For contributors, the repository is cloned into the apps folder and `make dev-setup` installs the dependencies.

Does Nextcloud Photos support face recognition?

The app itself does not do the recognition. The documentation lists Recognize for Nextcloud as an optional add-on providing AI powered automatic face and object recognition with automated tagging and categorising.

Why are previews of my photos slow in Nextcloud Photos?

The documentation points to preview generation as the step that needs help, recommending either the preview generator app for pre-generating thumbnails or the Imaginary service to speed up preview generation. Both are listed as optional add-ons installed separately from Photos.

Official sources

  1. Issues
  2. License: AGPL-3.0
  3. nextcloud/photos on GitHub
  4. README
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/nextcloud-photos.svg)](https://hysenlabs.com/projects/nextcloud-photos)

Was this useful?