chronoframe is a free, open source photo & video editors project written in Vue and released under MIT. It has 1,927 GitHub stars, 140 forks and 51 open issues, and was last pushed 28 hours ago. On this registry it ranks #27 of 29 tracked projects in Photo & Video Editors, with 5 head-to-head comparisons available.

What is chronoframe?

ChronoFrame is a self-hosted personal photo gallery and album application, built with Vue and Nuxt 4, for individuals and small teams who want to manage, browse, and map their own photo libraries on their own hardware.

What it is

ChronoFrame is a web application for managing and displaying personal photo collections. It is built on Nuxt 4 with TypeScript, TailwindCSS and Drizzle ORM, supports server-side rendering and static generation, and ships as a containerised service. The feature set covers online photo management and albums, Live and Motion Photos, EXIF parsing, reverse geocoding, an explore map for browsing photo locations, and a WebGL viewer for large images. Thumbnails are generated with ThumbHash, and the application handles JPEG, PNG and HEIC/HEIF files. Configuration is driven by environment variables, so site metadata, map provider, storage backend and session secrets are all set at deploy time.

The problem it solves is ownership of a growing photo library. Instead of handing images to a third-party hosted gallery service, an operator runs ChronoFrame against their own storage and keeps the library, its metadata and its access control on infrastructure they control. Uploading through the dashboard triggers automatic EXIF extraction for capture time, geolocation and camera parameters, reverse geocoding to identify where a photo was taken, and thumbnail generation. Photos are then browsed as albums or placed on the explore map, and delivered through a configurable CDN URL.

Key capabilities

  • Online photo management through a dashboard at /dashboard, with batch upload and drag-and-drop.
  • Smart EXIF parsing that extracts capture time, geolocation and camera parameters from uploaded images.
  • Reverse geocoding that automatically identifies photo shooting locations.
  • Explore map for browsing photo locations, configured with NUXT_PUBLIC_MAP_PROVIDER set to maplibre or mapbox.
  • Multi-format support for JPEG, PNG and HEIC/HEIF, alongside Live and Motion Photos and a WebGL viewer.
  • Smart thumbnail generation using ThumbHash.
  • Pluggable storage through NUXT_STORAGE_PROVIDER set to local, s3 or openlist, with a configurable CDN URL.

Who users are and how they use it

  • Self-hosters running the prebuilt container on their own hardware, exposing port 3000 and mounting ./data for the database and local storage.
  • Individuals with large personal libraries, including HEIC/HEIF images captured on phones, who want automatic EXIF and geolocation handling.
  • Families or small teams sharing albums through the album and photo-sharing features.
  • Travellers and photographers who use the explore map and reverse geocoding to see where their pictures were taken.
  • Administrators who sign in to the dashboard with GitHub OAuth or email and password credentials.

Getting started

Deploy the prebuilt image, either ghcr.io/hoshinosuzumi/chronoframe:latest or hoshinosuzumi/chronoframe:latest, with a .env file that sets CFRAME_ADMIN_EMAIL and a 32-character NUXT_SESSION_PASSWORD. Run it directly with docker run, or start it with docker compose up -d using the provided docker-compose.yml.

How it compares

The facts provided name no comparable tools and no paid products that ChronoFrame replaces, so within this registry it stands alone in its category. No licence, hosting or cost comparison can be drawn from the available material.

When to use it — and when not to

A self-hoster must run and maintain the container, a persistent /app/data volume, and the environment configuration, and must supply map credentials such as a MapTiler token for MapLibre or a Mapbox access token for the map and reverse geocoding features. Anyone who wants a zero-configuration hosted gallery, or who cannot run Docker or issue map API tokens, should look elsewhere. The README also documents default credentials, [email protected] with the password CF1234@!, and these must be changed before the instance is exposed publicly.

project readme (upstream, from github) — read inline

ChronoFrame

Chronoframe

Latest Release Latest Nightly Release License

Discord Server

Featured|HelloGitHub ChronoFrame - Self-hosted photo gallery for photographers. | Product Hunt

Languages: English | 中文

A smooth photo display and management application, supporting multiple image formats and large-size image rendering.

Live Demo: TimoYin's Mems

✨ Features

🖼️ Powerful Photo Management

  • Manage photos online - Easily manage and browse photos via the web interface
  • Explore map - Browse photo locations on a map
  • Smart EXIF parsing - Automatically extracts metadata such as capture time, geolocation, and camera parameters
  • Reverse geocoding - Automatically identifies photo shooting locations
  • Multi-format support - Supports mainstream formats including JPEG, PNG, HEIC/HEIF
  • Smart thumbnails - Efficient thumbnail generation using ThumbHash

🔧 Modern Tech Stack

  • Nuxt 4 - Built on the latest Nuxt framework with SSR/SSG support
  • TypeScript - Full type safety
  • TailwindCSS - Modern CSS framework
  • Drizzle ORM - Type-safe database ORM

☁️ Flexible Storage Solutions

  • Multiple storage backends - Supports S3-compatible storage, local filesystem
  • CDN acceleration - Configurable CDN URL for faster photo delivery

🐳 Deployment

We recommend deploying with the prebuilt Docker image. View the image on ghcr

Create a .env file and configure environment variables.

Below is a minimal configuration example. For complete configuration options, see Configuration Guide:

# Admin email (required)
CFRAME_ADMIN_EMAIL=
# Admin username (optional, default Chronoframe)
CFRAME_ADMIN_NAME=
# Admin password (optional, default CF1234@!)
CFRAME_ADMIN_PASSWORD=

# Site metadata (all optional)
NUXT_PUBLIC_APP_TITLE=
NUXT_PUBLIC_APP_SLOGAN=
NUXT_PUBLIC_APP_AUTHOR=
NUXT_PUBLIC_APP_AVATAR_URL=

# Map provider (maplibre/mapbox)
NUXT_PUBLIC_MAP_PROVIDER=maplibre
# MapTiler access token for MapLibre
NUXT_PUBLIC_MAP_MAPLIBRE_TOKEN=
# Mapbox access token for Mapbox
NUXT_PUBLIC_MAPBOX_ACCESS_TOKEN=

# Mapbox unrestricted token (optional, reverse geocoding)
NUXT_MAPBOX_ACCESS_TOKEN=

# Storage provider (local, s3 or openlist)
NUXT_STORAGE_PROVIDER=local
NUXT_PROVIDER_LOCAL_PATH=/app/data/storage

# Session password (32‑char random string, required)
NUXT_SESSION_PASSWORD=
# Secret key for stable signing og images
# Use: npx nuxt-og-image generate-secret
NUXT_OG_IMAGE_SECRET=

Pull Image

Use the published image on GitHub Container Registry and Docker Hub. Choose the source that works best for your network:

GitHub Container Registry (GHCR)
docker pull ghcr.io/hoshinosuzumi/chronoframe:latest
Docker Hub
docker pull hoshinosuzumi/chronoframe:latest

Docker

Run with customized environment variables:

docker run -d --name chronoframe -p 3000:3000 -v $(pwd)/data:/app/data --env-file .env ghcr.io/hoshinosuzumi/chronoframe:latest

Docker Compose

Create docker-compose.yml:

services:
  chronoframe:
    image: ghcr.io/hoshinosuzumi/chronoframe:latest
    container_name: chronoframe
    restart: unless-stopped
    ports:
      - '3000:3000'
    volumes:
      - ./data:/app/data
    env_file:
      - .env

Start:

docker compose up -d

📖 User Guide

If CFRAME_ADMIN_EMAIL and CFRAME_ADMIN_PASSWORD are not set, the default admin account is:

Logging into the Dashboard

  1. Click avatar to sign in with GitHub OAuth or use email/password login

Uploading Photos

  1. Go to the dashboard at /dashboard
  2. On the Photos page, select and upload images (supports batch & drag-and-drop)
  3. System will automatically parse EXIF data, generate thumbnails, and perform reverse geocoding

📸 Screenshots

Gallery Photo Detail Map Explore Dashboard

🛠️ Development

Requirements

  • Node.js 18+
  • pnpm 9.0+

Install dependencies

# With pnpm (recommended)
pnpm install

# Or with other package managers
npm install
yarn install

Configure environment variables

cp .env.example .env

Initialize database

# 2. Generate migration files (optional)
pnpm db:generate

# 3. Run database migrations
pnpm db:migrate

Start development server

pnpm dev

App will start at http://localhost:3000.

Project Structure

chronoframe/
├── app/                    # Nuxt app
│   ├── components/         # Components
│   ├── pages/              # Page routes
│   ├── composables/        # Composables
│   └── stores/             # Pinia stores
├── packages/
│   └── webgl-image/        # WebGL image viewer
├── server/
│   ├── api/                # API routes
│   ├── database/           # DB schema & migrations
│   └── services/           # Business logic services
└── shared/                 # Shared types & utils

Build commands

# Development (with dependencies build)
pnpm dev

# Build only dependencies
pnpm build:deps

# Production build
pnpm build

# Database operations
pnpm db:generate    # Generate migration files
pnpm db:migrate     # Run migrations

# Preview production build
pnpm preview

🤝 Contributing

Contributions are welcome! Please:

  1. Fork the repo
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit changes (git commit -m 'Add some amazing feature')
  4. Push to branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Coding Guidelines

  • Use TypeScript for type safety
  • Follow ESLint and Prettier conventions
  • Update documentation accordingly

📄 License

This project is licensed under the MIT License.

👤 Author

Timothy Yin

❓ FAQ

How is the admin user created?

On first startup, an admin user is created based on CFRAME_ADMIN_EMAIL, CFRAME_ADMIN_NAME, and CFRAME_ADMIN_PASSWORD. The email must match your GitHub account email used for login.

Which image formats are supported?

Supported formats: JPEG, PNG, HEIC/HEIF, MOV (for Live Photos).

Why can’t I use GitHub/Local storage?

Currently only S3-compatible storage is supported. GitHub and local storage support is planned.

Why is a map service required and how to configure it?

The map is used to browse photo locations and render mini-maps in photo details. Currently Mapbox is used. After registering, get an access token and set it to the MAPBOX_TOKEN variable.

Why wasn’t my MOV file recognized as a Live Photo?

Ensure the image (.heic) and video (.mov) share the same filename (e.g., IMG_1234.heic and IMG_1234.mov). Upload order does not matter. If not recognized, you can trigger pairing manually from the dashboard.

How do I import existing photos from storage?

Direct import of existing photos is not yet supported. A directory scanning import feature is planned.

🙏 Acknowledgements

This project was inspired by Afilmory, another excellent personal gallery project.

Thanks to the following open-source projects and libraries:

⭐️ Star History

Star History Chart

Frequently asked questions

Is chronoframe free to use?

chronoframe is open source under the MIT licence. There is no licence fee and no seat count — you can self-host it or, where the project offers one, pay a vendor for a managed version instead.

What does chronoframe do?

Self-hosted personal gallery application with online photo management and albums, supporting Live/Motion Photos, EXIF parsing, geolocation recognition, and an e

What is chronoframe written in?

chronoframe is primarily written in Vue. Its source is publicly available at https://github.com/HoshinoSuzumi/chronoframe, and it has 1,927 GitHub stars.