Color Thief
Library to extract color palettes from photos
Color Thief is an open-source JavaScript library that extracts the dominant color and a representative color palette from an image. It is self-hostable and used in photo galleries and apps to generate color-aware layouts.
Key features
- Dominant color extraction
- Color palette generation
- Browser and Node support
- Tiny library
Pros & cons
Strengths
- Simple and reliable
- Widely used
Trade-offs
- Library, not an app
- Single-purpose
Color Thief replaces
Last reviewed Aug 26, 2026 · 826 words
Color Thief is two functions. getColor(image) returns the dominant colour of an image as an [r, g, b] array, and getPalette(image, count) returns a list of representative colours. There is no server, no database, no port, and nothing to deploy; it is a JavaScript library with an MIT licence, 13,614 GitHub stars, and a history that goes back to 2011. It appears in this directory because self-hosted galleries and dashboards use it, and because people arrive here looking for "self-hosted colour extraction" and are surprised to find that the answer is 3 KB of code rather than a container.
Where it sits in a self-hosted stack
Three places, in order of how often I have seen it used. First, in the browser of a gallery front-end, to tint a card background or a lightbox frame with the photo's own colours. Second, in a Node script during import, so a static gallery or a small photos app stores a palette per image alongside width, height, and EXIF data. Third, in dashboard and media-server themes that pull an accent colour from album art or a poster. The big self-hosted photo apps do their own colour work internally; Immich and PhotoPrism do not depend on Color Thief, but Lychee themes and countless home-grown galleries do exactly this job with it.
The Node version is the one you will actually run
Install it, point it at a file, get colours back:
npm install colorthief
const ColorThief = require('colorthief');
const palette = await ColorThief.getPalette('./photos/2026-08-beach.jpg', 5);
// [[212, 184, 141], [64, 108, 152], [246, 244, 239], [31, 47, 66], [140, 158, 174]]
Run this once at import time and store the result. Do not call it per request: it decodes the whole image into memory, and a 24-megapixel JPEG is about 72 MB of raw pixels. Downscale to a 200-pixel thumbnail first with sharp, then extract; the palette is almost identical and the run takes milliseconds instead of a second. The 64 MB RAM figure in the catalogue is realistic for a single small image; batch processing a whole library wants either thumbnails or a queue.
The browser version needs CORS you control
In the browser Color Thief draws the image onto a canvas and reads pixels back. Browsers block that read if the image came from a different origin without CORS headers, and the failure is a silent tainted-canvas error. Two fixes: serve the images from the same origin as the page, which self-hosters can usually arrange, or set crossorigin="anonymous" on the <img> tag and add an Access-Control-Allow-Origin header on the object store or CDN that serves them. If your photos live in a MinIO bucket, that means a bucket CORS rule; without it nothing works and the console tells you why only if you look.
<img id="cover" src="https://media.example.com/cover.jpg" crossorigin="anonymous">
<script type="module">
import ColorThief from 'colorthief';
const img = document.getElementById('cover');
img.addEventListener('load', () => {
const [r, g, b] = new ColorThief().getColor(img);
document.body.style.setProperty('--accent', `rgb(${r} ${g} ${b})`);
});
</script>
What "dominant" means, and what it does not
Color Thief uses median-cut quantisation over the RGB values it samples. The third argument to getPalette is a quality setting: 10 (the default) inspects every tenth pixel, 1 inspects all of them and is proportionally slower. The result is the colour that covers the most area, which for a photo of a person against a grey wall is the grey wall. It does not know about faces, subjects, or what a designer would call "the interesting colour". If you want a pleasing accent rather than the statistically dominant one, take the palette of 5 and pick the entry with the highest saturation yourself; that is a 6-line function and it is how most galleries that look good with Color Thief are doing it.
It is also not a placeholder generator. The blurred preview trick that Immich and others use while a full image loads is a different technique (BlurHash or ThumbHash), and it is not a colour-search index; PhotoPrism's "find photos that are mostly blue" feature is built from its own classification, not from a palette library.
What I'd do
Extract at import in a Node script, on a 200-pixel thumbnail, store 5 colours as JSON next to the image record, and use the most saturated one for card backgrounds with the dominant one as a fallback. Never in the request path, never on the full-size original, and never across origins without a CORS rule you have tested in the browser console. Used that way it is one of the more dependable small libraries in the self-hosting toolbox: single purpose, unchanged API for years, and no operational weight at all.
Similar photo management apps
Immich
Photo ManagementHigh-performance self-hosted photo and video backup
Replaces Google Photos, iCloud Photos
PhotoPrism
Photo ManagementAI-powered photo app for the decentralized web
Replaces Google Photos
Ente Photos
Photo ManagementEnd-to-end encrypted photo storage and backup
Replaces Google Photos, iCloud Photos
DeepFace
Photo ManagementFace recognition and facial attribute analysis framework
Replaces AWS Rekognition, Azure Face API
gallery-dl
Photo ManagementCommand-line tool to download image galleries from websites
Replaces Manual downloads
darktable
Photo ManagementOpen-source photography workflow and RAW developer
Replaces Adobe Lightroom