FolderFrame

How it works

Serve the app files and media folders, then open the gallery in a browser.

What you need

  • A computer, NAS or static host that can serve files
  • HTTP or HTTPS access — not a file opened from disk
  • Live directory listings or a published static manifest
  • A modern browser and a reachable address

Caddy, NGINX, Apache and lighttpd can provide live directory listings. Cloudflare Pages, GitHub Pages and similar static hosts can publish a generated manifest instead. No database, PHP, backend or app build step is needed.

FolderFrame is available in Unraid Community Apps bundled with Caddy and performance options enabled.

EXAMPLE FOLDER STRUCTURE
FolderFrame/
├── index.html + app files
├── folderframe.config.json
└── photos/
    ├── Family/
    │   └── birthday.jpg
    ├── Vacations/
    │   └── beach.heic
    └── garden.mp4

Are directory listings required?

No. Use live listings on a normal web server, or publish a generated manifest on a static host.

Does static hosting need a backend?

No. Deploy the app, media and manifest as static files. New media appears after you regenerate and redeploy the manifest. Static Pages installation

Setup

01

Download the app

Download the app from GitHub. Keep its files together in your server’s web root.

Get the app ↗
02

Add your media

Add media to photos/ or define served folder paths in folderframe.config.json. Subfolders become albums.

Configuration guide ↗
03

Open the gallery

Open the app. Directory sources scan live listings; manifest sources load the published index.

Hosting details ↗

Local test

RUN INSIDE THE DOWNLOADED APP FOLDER · REQUIRES PYTHON 3
python3 -m http.server 8000

# Then open http://localhost:8000

For testing or a trusted local network. Use a configured web server for a permanent installation.

Static-host discovery

See Static Pages hosting options in Ways to install

auto

Default. Try a published manifest, then fall back to directory listings, which can then update the manifest for future scans.

directory

Use live directory listings and never request a manifest.

manifest

Require a generated manifest and never attempt directory discovery.

STRICT STATIC-HOST SOURCE
{
  "id": "photos",
  "label": "Photos",
  "path": "photos/",
  "thumbnailPath": "thumbnails/",
  "manifestPath": "folderframe-data/library.json",
  "discoveryMode": "manifest"
}
STATIC PACKAGE LAYOUT
FolderFrame/
├── index.html
├── app.js
├── folderframe.config.json
├── photos/
├── thumbnails/              # optional
└── folderframe-data/
    ├── library.json
    └── library.d/
        └── *.json
In manifest mode
  • Browser scanCache is ignored.
  • Generated thumbnails still work.
  • FolderFrame never attempts directory discovery.
  • The interface shows Reload Library.
  • Reload Library cache-busts and reloads the published index.
  • New files require regenerating and redeploying the manifest.
  • Missing or invalid manifests show an actionable error, not an empty gallery.

Generate the index with FolderFrame’s helper, then deploy it with the media. Configuration guide ↗

The official public demo uses strict manifest discovery on GitHub Pages.

Manual web-server examples

These directory-discovery examples use the deployment container paths: app files at /srv/app, read-only libraries mounted below /media/ such as /media/Library, and container port 8080. They provide a browsable /photos/ URL. Static hosts can use the manifest workflow above instead.

Container installs. Docker and Unraid enable persistent thumbnails and manifests by default; the manifest reuses unchanged directory records on later scans. scanCache is a separate optional browser feature and is not required. Installation options

NGINX · SERVER BLOCK
server {
    listen 8080;
    root /srv/app;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /photos/ {
        alias /media/;
        autoindex on;
        autoindex_exact_size off;
        add_header Cache-Control "no-store";
    }
}
CADDY · CADDYFILE
:8080 {
    redir /photos /photos/ 308

    handle_path /photos/* {
        root * /media
        header Cache-Control "no-store"
        file_server browse {
            hide .*
        }
    }

    handle {
        root * /srv/app
        header Cache-Control "no-cache"
        file_server
    }
}