vintageJS: a canvas filter for retro photos, rewritten in TypeScript
Add a retro/vintage effect to images using the HTML5 canvas element
At a glance
- What is it?
- vintageJS applies a set of image effects to a source image or canvas and hands back the result as a data URL, a canvas or an image element. The 2.3.0 rewrite moved it from Flow and Browserify to TypeScript and Vite without changing the public API, which is the interesting part of the release.
- Who is it for?
- Adopt vintageJS if you need a browser-side retro filter and you can work with a canvas pipeline and a promise-based result object, and if you are comfortable with an ES module build served over the web.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 101 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One function, one options object, one promise
The API is a single function. You call vintagejs with a source, which can be a URL, an ImageElement or a CanvasElement, and an effect object holding the options, and you get back a Promise that resolves to a result object. Nothing is written back to the DOM for you, and nothing is mutated in place. The result object exposes three ways to get at the processed pixels. getDataURL returns a data url of the updated image, which the README suggests for updating the source of an existing image. getCanvas returns the canvas with the updated image, for drawing onto another canvas. genImage returns a promise that resolves to an HTMLImageElement of the updated image. The mimeType and quality arguments are optional, and when not provided mimeType defaults to image/jpeg and quality defaults to 1. That is a small, predictable surface, and it is the reason the library can be dropped into a page without a framework.
Effects are typed, and the type is the documentation
The option list is published as a TypeScript type, which is unusually complete for a project this size. Each entry carries its own default in a comment:
type TEffect = {
curves: false | TCurve, // default: false
screen: false | TRGBAColor, // default: false
saturation: number, // float between 0 and 1, default: 1
vignette: number, // float between 0 and 1, default: 0
lighten: number, // float between 0 and 1, default: 0
viewfinder: false | string, // string must be URL, default: false
sepia: boolean, // default: false
gray: boolean, // default: false
brightness: number, // float between -1 and 1, default: 0
contrast: number, // float between -1 and 1, default: 0
};The two compound options are described by their own types. curves is a look up table for colour mappings with one array per channel, r, g and b, each of length 256 holding values between 0 and 255. screen is an RGBA colour with three integer channels and an alpha between 0 and 1. A curve therefore gives you arbitrary per-channel remapping rather than a fixed filter, and screen is a single tint laid over the image. The effect list is small and generic, and the tint and curve options are where the actual control lives.
A promise chain that ends in the DOM
The usage example is short enough to read in one go, and it shows the intended shape of a call:
vintagejs('./path/to/picture.jpg', { brightness: 0.2 })
.then(res => res.genImage())
.then(img => document.body.appendChild(img));The first promise resolves to the result object, genImage returns another promise for the image element, and the element is appended. Two other patterns are documented. You can use an image as the source and update it in place through the data url:
const srcEl = document.querySelector('img.myImage');
vintagejs(srcEl, { brightness: 0.2 })
.then(res => {
srcEl.src = res.getDataURL();
});Or you can take a canvas as the source and draw the result back onto it:
const srcEl = document.querySelector('canvas.myCanvas');
const ctx = srcEl.getContext('2d');
vintagejs(srcEl, { brightness: 0.2 })
.then(res => {
ctx.drawImage(res.getCanvas(), 0, 0, srcEl.width, srcEl.height);
});There is no error handling in any of these examples. The function returns a promise, so a failed image load rejects it, and what you do with that rejection is up to you.
The 2.3.0 rewrite changed the toolchain, not the API
The changelog entry for 2.3.0 is the most recent and the most consequential. The toolchain moved from Flow plus Browserify to TypeScript plus Vite, and the library is now written in TypeScript and ships .d.ts type declarations. An ES module build at dist/vintage.mjs was added alongside the existing UMD bundle at dist/vintage.js and the minified bundle at dist/vintage.min.js. The changelog also records that all 31 npm audit vulnerabilities, 5 critical and 9 high among them, were fixed by removing the legacy build and test dependencies, and that no runtime dependency was affected. Finally, no API changes: the UMD global vintagejs and the function signature are unchanged. The manifest matches. The main field points at the UMD build, module at the ES module, types at dist/index.d.ts, and the exports map lists types, import and require conditions. So the migration is a build system change with a maintained compatibility surface, which is the right way to do it.
One runtime dependency, and a browser support caveat
The dependency list is short enough to read at a glance. There is exactly one runtime dependency, nullthrows at 1.0.0, and everything else is dev-only: TypeScript 5.6.3, Vite 7.3.6 and Vitest 3.2.7. The build script runs vite build, then vite build with the min mode, then tsc, and the test script is vitest run. The browser support section is where the caveats live. The README points at caniuse.com/canvas to check support for the canvas element itself, and notes that you get higher performance when canvas blend modes are supported, pointing at the canvas blending feature page, but that fallbacks are implemented. The blend mode question matters because the screen effect, the tint, is a blend operation. On a browser without canvas blending, the same effect still renders through a fallback path, presumably at a lower cost, and the README does not say which browsers fall into which group, so that is a page you have to check yourself.
The npm package, the demo folder and the licence
Installation is a single npm command:
$ npm install vintagejsThe published files array includes dist and src, so the TypeScript sources ship alongside the bundles, and the repository also carries a bower.json, a package-lock.json, a vite.config.ts, a tsconfig.json and a test directory. The examples folder is a plain HTML page with example.js, an index.html, a couple of sample photographs and a film-1.jpg used as the viewfinder overlay. That last file is the viewfinder option in action: you pass a URL to an image and it is composited over the result as a frame. The licence is MIT, and the project is published under the vintagejs name on npm, with a project page at vintagejs.com. The examples folder is the fastest way to see the effects without wiring anything up.
Latest release tags lag the package version
The maintenance picture is uneven, and it is worth being precise about it. The repository is not archived and the last push was on 2026-07-14. The published GitHub releases are old: 2.2.0 from 2017-10-20 and 2.0.1 from 2017-08-18. The package version in package.json is 2.3.0, and the changelog documents a 2.3.0 entry covering the TypeScript migration, the ES module build and the audit fix, so the work exists in the repository even though the newest tagged release is 2.2.0. In practice you install from npm, where the version reflects the manifest, and the tags are not what you consume. The changelog itself also shows the shape of the project over time: 1.0.0 initial release, 1.1.0 and 1.1.1 performance work, 1.1.2 AngularJS support and a grunt based build, 2.0.0 a rewrite from ground up with a functional API, 2.1.0 string sources, 2.2.0 true grayscale, and now the TypeScript move. Treat 2.3.0 as the version to read the source of, and do not expect new effects to arrive on a schedule.
Editorial conclusion
Adopt vintageJS if you need a browser-side retro filter and you can work with a canvas pipeline and a promise-based result object, and if you are comfortable with an ES module build served over the web. Do not adopt it for server-side image processing, since the whole implementation depends on the canvas element and its blend modes, and do not expect the effect list to grow quickly, because the last push was on 2026-07-14 and the newest published GitHub release is 2.2.0 from 2017 while the package version is 2.3.0. Before you commit, check the browser support notes for the canvas element and for canvas blend modes, and pin the exact version you install.
Frequently asked questions
How do I install and use vintageJS?
Install it with npm install vintagejs. The library exposes a single function that takes a source (a URL, an ImageElement or a CanvasElement) and an effect options object, and returns a promise resolving to a result object with getDataURL, getCanvas and genImage methods.
What image effects does vintageJS support?
The documented options are curves, screen, saturation, vignette, lighten, viewfinder, sepia, gray, brightness and contrast. Curves takes a per-channel look up table of 256 values, and screen takes an RGBA colour with an alpha value.
Does vintageJS need canvas blend mode support?
The README says higher performance is available when canvas blend modes are supported, but that fallbacks are implemented. The canvas element itself is the baseline requirement, and the README links to caniuse.com for both checks.
What changed in vintageJS 2.3.0?
The toolchain moved from Flow and Browserify to TypeScript and Vite, the library now ships .d.ts type declarations and an ES module build at dist/vintage.mjs, and 31 npm audit vulnerabilities were removed with the legacy dependencies. The README states there were no API changes and the UMD global vintagejs is unchanged.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/rendro-vintagejs)