CO

Color Thief

Library to extract color palettes from photos

Photo Management ★ 13.6k stars Easy setup MIT

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