How It Works
Architecture at a glance
Foldergram has two deliberate layers:
- A scanner/indexer that walks the gallery tree, updates SQLite, and generates derivatives.
- A runtime API and SPA that read indexed data from SQLite and serve static derivative assets.
That separation is the core performance decision in the project.
Source discovery model
Foldergram recursively walks GALLERY_ROOT and applies these rules:
- Hidden paths are skipped.
- Managed storage paths under the gallery root are skipped.
- Any non-hidden folder that directly contains supported media becomes an indexed album.
- Files directly in
GALLERY_ROOTare ignored. - Nested folders are treated separately from their parent folders.
Storage layout
By default the app uses:
data/
gallery/ # originals
db/
gallery.sqlite
thumbnails/ # mirrored thumbnail derivatives
previews/ # mirrored preview derivativesThe database schema includes:
foldersimagesscan_runsapp_settingsfolder_scan_statelikes
What is stored per indexed post
The images table stores:
- normalized relative and absolute paths
- file size and
mtime_ms - width and height
- media type and MIME type
- duration for videos
- a fingerprint built from
relative_path + file_size + mtime_ms sort_timestamptaken_atandtaken_at_source- mirrored derivative paths
- playback strategy for videos
- soft-delete state
Stable ordering
Foldergram preserves stable sort order across rescans.
If a file already exists in the database, the scanner keeps its prior sort_timestamp. If not, it falls back to:
- the existing
first_seen_at, if present - the current file
mtime_ms
That prevents older posts from jumping around every time the library is rescanned.
Soft delete and reactivation
Foldergram does not hard-delete missing indexed files during scans.
Instead it:
- marks missing files as
is_deleted = 1 - keeps their historical row data
- reactivates them if the same relative path reappears later
Direct user-triggered delete actions are different. Those remove the source file and derivatives first, then mark or remove the indexed records as part of the delete flow.
Folder shortcuts during scans
To avoid unnecessary work, Foldergram records per-folder scan signatures in folder_scan_state. If a folder signature still matches and metadata coverage is complete, the scanner can skip reprocessing every file in that folder.
That shortcut is bypassed when Foldergram needs to repair unchanged derivatives or when gallery-root assumptions no longer match.
Full scan lifecycle
During a full scan, Foldergram:
- Walks the gallery tree to discover source folders.
- Stats supported files in those folders.
- Resolves folder records and stable slugs.
- Reads or refreshes media metadata.
- Marks missing indexed rows as deleted.
- Queues derivative work for changed or missing outputs.
- Writes scan status to
scan_runs.
Incremental scans and watching
The project includes a chokidar watcher for development. It batches changes with a 700ms debounce window and chooses between:
- a full rescan for directory add/remove events
- an incremental scan for file-level changes
The watcher is not part of request handling, and request handlers never scan the filesystem directly.
Feed behavior
The home feed supports three modes:
| Mode | Behavior |
|---|---|
recent | Uses taken_at when available, otherwise sort_timestamp, then diversifies bursts from the same folder. |
rediscover | Surfaces posts older than 180 days and prioritizes liked items within that older pool. |
random | Uses a deterministic seeded shuffle so a browsing session stays stable while paging. |
Moments and highlights
GET /api/feed/moments can return either a Moments rail or a Highlights rail.
Moments rail
Foldergram prefers date-based moments when the library has enough EXIF-backed timestamps:
- at least
24indexed posts - at least
18posts withtaken_at_source = 'exif' - at least
30%EXIF coverage
The current date-driven capsules are:
- On This Day
- This Week
- Last Year Around Now
Highlights rail
When date coverage is too sparse, Foldergram falls back to curated sets:
- Recent Batches
- Forgotten Favorites
- Deep Cuts
- Lucky Dip
Folder rebuild requirement
Foldergram tracks the last successful gallery root. If that path changes and there is already indexed content, the scanner marks the library as requiring a rebuild. This is meant to prevent silent cross-library drift in the index and cached derivatives.
Runtime read model
Once data is indexed:
- folder pages read from SQLite
- feed pages read from SQLite
- likes read from SQLite
- moments and highlights read from SQLite
- thumbnails and previews are served as static files
- originals are served by image ID only