How to Optimize Images in Node.js with the ShortPixel SDK

Heavy images are one of the most common reasons websites feel sluggish. A product photo that weighs 3MB loads fine on your development machine with a fast connection, but it punishes real users on mobile, and it quietly tanks your Core Web Vitals scores while doing it.

ShortPixel has been solving this problem for years, and the new Node.js SDK puts that optimization power directly into your backend code. Whether you’re building a content pipeline, processing user uploads, or compressing assets before they hit your CDN, the SDK fits cleanly into existing Node.js workflows.

This guide walks through the SDK from installation to real-world patterns you can copy straight into a project.

What the SDK actually does

At its core, the ShortPixel SDK is a wrapper around two API endpoints:

  • Reducer API — optimizes images fetched from remote URLs
  • Post-Reducer API — optimizes local files or buffers uploaded directly

The SDK handles the transport details: polling, retries, response normalization.

You pass in an image, configure how you want it processed, and get back download-ready URLs or write files locally.

The real-world compression numbers can reach up to 86%, which makes a noticeable difference at scale.

Installation

npm i @shortpixel-com/shortpixel

One thing to be aware of before diving in: the package is ESM-only. Your package.json needs to declare this:

{
  "type": "module"
}

Node 20+ is recommended. The SDK won’t work reliably in CommonJS environments without additional configuration.

Setting up the client

Every operation flows through a ShortPixelClient instance. You’ll need an API key from shortpixel.com to get started.

import SHORTPIXEL from "@shortpixel-com/shortpixel";

const { ShortPixelClient } = SHORTPIXEL;

const cli = new ShortPixelClient({
  apiKey: process.env.SHORTPIXEL_API_KEY,
});

Store your API key in an environment variable, never hardcode it. A .env file with a library like dotenv works fine here.

Four ways to think about the API

The SDK is designed with multiple layers of abstraction, and which one you reach for depends on how much control you need.

  1. Explicit helpers: fromFile, fromUrl, fromBuffer. These map directly to an endpoint and make it obvious what’s happening. Good for large codebases where clarity matters.
  2. Intent-first aliases: optimizeFile, optimizeUrl, optimizeBuffer. Same behavior, naming that reads more naturally.
  3. Generic dispatcher: optimize(input). Pass in a file path, URL, or Buffer, and the SDK routes it automatically. Cleanest for quick scripts.
  4. Feature helpers: upscale, rescale, backgroundRemove, convert. These set intelligent defaults for specific operations and still accept any additional options you need.

In practice, you’ll likely mix these. Feature helpers are great for readability; explicit helpers are better when your team needs to trace exactly what’s hitting which endpoint.

Basic file optimization

The simplest case: take a local image, compress it, and write the result to a folder.

const src = await cli.fromFile("assets/product-shot.png", { lossy: 1 });
await src.downloadTo("./output");

The lossy parameter controls compression aggressiveness:

  • 0 — lossless (no quality reduction)
  • 1 — lossy (good balance of size and quality)
  • 2 — glossy (more aggressive, still visually acceptable for most use cases)

If you prefer named methods:

const src = await cli.glossy("assets/product-shot.png");
await src.downloadTo("./output");

Optimizing remote URLs

For images that live on a CDN or external server, pass the URL directly:

const src = await cli.fromUrl(
  "https://example.com/images/hero-banner.jpg",
  { lossy: 1, convertto: "+webp" }
);
await src.downloadTo("./output");

The convertto: "+webp" option tells ShortPixel to produce the optimized original plus a WebP version. If you only want WebP (no original), use "webp" without the +.

Format conversion

Modern image formats like WebP and AVIF deliver significantly better compression than JPEG or PNG at equivalent quality. The convertto option handles this:

// Original + WebP
await cli.convert("assets/banner.jpg", "+webp");

// Only WebP and AVIF, no original
await cli.convert("assets/banner.jpg", "webp|avif");

// Original + WebP + AVIF
await cli.convert("assets/banner.jpg", "+webp|+avif");

The + prefix means “keep the optimized original as well.” Without it, only the converted formats are returned.

Resizing and upscaling

The SDK exposes several resize modes that correspond to different visual outcomes.

Upscaling — useful when you need to increase an image’s resolution without losing detail:

const src = await cli.upscale("assets/thumbnail.png", 2, {
  lossy: 0,
  convertto: "+webp",
});
await src.downloadTo("./output");

The second argument is the upscale factor (2x, 3x, or 4x). Valid values are 2, 3, and 4.

Rescale (outer) — resize to fit within given dimensions, maintaining aspect ratio:

const src = await cli.rescale("assets/photo.jpg", 1200, 800, {
  convertto: "+avif",
});
await src.downloadTo("./output");

Resize inner — resize so the image fills the dimensions, potentially cropping:

const src = await cli.resizeInner("assets/product.jpg", 600, 600);
await src.downloadTo("./output");

Smart crop — like inner resize, but uses AI to focus on the most important part of the image:

const src = await cli.smartCrop("assets/portrait.jpg", 400, 400, {
  convertto: "+webp",
});
await src.downloadTo("./output");

Smart crop is particularly useful for product images or portraits where you don’t want to lose the subject when cropping to a square.

Background removal and replacement

ShortPixel can remove image backgrounds or replace them with a color or another image.

Remove the background (returns transparent PNG):

const src = await cli.backgroundRemove("assets/product.png", {
  convertto: "+webp",
});
await src.downloadTo("./output");

Replace with a solid color using an #rrggbbaa hex value (the last two characters control opacity):

// Warning: background operations take longer to process
const src = await cli.backgroundChange("assets/product.png", "#ffffff00", {
  lossy: 2,
});
await src.downloadTo("./output");

Replace with another image:

const src = await cli.backgroundChange(
  "assets/product.png",
  "https://example.com/studio-background.jpg",
  { convertto: "+webp" }
);
await src.downloadTo("./output");

Background operations are more computationally intensive than basic compression, so expect longer processing times. You may need to increase the polling configuration (more on that below).

Processing multiple files

The SDK handles batches cleanly. You can pass arrays to any of the batch methods.

Multiple local files:

const src = await cli.fromFiles([
  "assets/product-1.jpg",
  "assets/product-2.jpg",
  "assets/product-3.jpg",
], {
  lossy: 1,
  convertto: "+webp",
});

await src.downloadTo("./output");

Multiple URLs:

const src = await cli.fromUrls([
  "https://example.com/images/image-1.jpg",
  "https://example.com/images/image-2.jpg",
], {
  lossy: 2,
  convertto: "+avif",
});

await src.downloadTo("./output");

One important restriction: don’t mix local file paths and remote URLs in the same array. The SDK will throw an error because they route to different endpoints.

Working with buffers

When you’re handling file uploads in a web server and want to process images before saving them, buffers are the right approach:

import fs from "fs";

const imageBuffer = fs.readFileSync("assets/upload.png");

const src = await cli.fromBuffer(imageBuffer, "upload.png", {
  lossy: 1,
  convertto: "+webp",
});

await src.downloadTo("./output");

For multiple buffers:

const src = await cli.fromBuffers([
  { buffer: bufferA, filename: "image-a.png" },
  { buffer: bufferB, filename: "image-b.png" },
], "fallback.png", { lossy: 1 });

await src.downloadTo("./output");

Configuring polling and retries

ShortPixel’s API is asynchronous — you submit an image and poll for the result. The SDK handles this automatically, but the defaults may need adjusting for complex operations like background removal or AI-based upscaling.

// Increase timeout for long-running operations
cli.set("timeout", 60000);

// More retries on temporary failures
cli.set("retries", 5);
cli.set("retryDelay", 1500);

// Tune polling for complex operations
cli.set("poll", {
  enabled: true,
  interval: 3000,     // check every 3 seconds
  maxAttempts: 30,    // try up to 30 times
});

If you’re running background removal jobs and getting timeout errors, increasing interval and maxAttempts is usually the fix.

You can also set a global default output format so you don’t have to repeat it on every call:

cli.set("convertto", "+webp");

Inspecting metadata

Every Source object returned by an operation includes lastMetas — the raw API response for each processed item. This is useful for debugging or when you need to handle output URLs yourself:

const src = await cli.fromFile("assets/image.png", {
  lossy: 1,
  convertto: "+webp|+avif",
});

for (const meta of src.lastMetas || []) {
  console.log("Status code:", Number(meta?.Status?.Code));
  console.log("Status message:", meta?.Status?.Message);
  console.log("Optimized URL:", meta?.LossyURL);
  console.log("WebP URL:", meta?.WebPLossyURL || meta?.WebPLosslessURL);
  console.log("AVIF URL:", meta?.AVIFLossyURL || meta?.AVIFLosslessURL);
}

Status code 2 means the image was processed successfully. Status code 1 means it’s still pending (the SDK handles polling automatically, so you shouldn’t see this often in normal operation).

Error handling

The SDK uses typed errors, which makes it straightforward to handle specific failure modes:

try {
  const src = await cli.fromFile("assets/image.png", { upscale: 4 });
  await src.downloadTo("./output");
} catch (err) {
  if (err.name === "ShortPixelAuthError") {
    console.error("Invalid API key");
  } else if (err.name === "ShortPixelQuotaError") {
    console.error("Monthly quota exceeded");
  } else if (err.name === "ShortPixelTemporaryError") {
    console.error("Temporary API issue, retry later");
  } else if (err.name === "ShortPixelInvalidRequestError") {
    console.error("Invalid request parameters");
  } else {
    console.error("Unexpected error:", err.name);
    console.error("SP code:", err.spCode);
    console.error("Message:", err.spMessage);
  }
}

The full set of typed errors the SDK can throw is: ShortPixelError (base class), ShortPixelAuthError, ShortPixelQuotaError, ShortPixelTemporaryError, ShortPixelInvalidRequestError, and ShortPixelBatchError.

In batch mode, if one or more items fail, the SDK throws a ShortPixelBatchError that includes per-item error details. You can iterate through those to handle partial failures gracefully.

Example: Image pipeline for user uploads

Here’s a pattern you might use in a Node.js API route that processes user-uploaded images:

import SHORTPIXEL from "@shortpixel-com/shortpixel";
import fs from "fs";
import path from "path";

const { ShortPixelClient } = SHORTPIXEL;

const cli = new ShortPixelClient({
  apiKey: process.env.SHORTPIXEL_API_KEY,
});

// Set global defaults once
cli.set("convertto", "+webp");
cli.set("poll", { enabled: true, interval: 2000, maxAttempts: 25 });

async function processUserUpload(uploadedBuffer, originalFilename) {
  try {
    const src = await cli.fromBuffer(uploadedBuffer, originalFilename, {
      lossy: 1,
      keep_exif: 0,
    });

    const outputDir = path.join("./storage", "optimized");
    fs.mkdirSync(outputDir, { recursive: true });

    const files = await src.downloadTo(outputDir);

    return {
      success: true,
      files,
    };
  } catch (err) {
    console.error("Optimization failed:", err.name, err.spMessage);
    return {
      success: false,
      error: err.name,
    };
  }
}

A few things to keep in mind

HTTPS is enforced throughout the SDK. Any input URL using plain http:// will be rejected before a network call is made.

Output URLs that come back over HTTP from the API are automatically upgraded to HTTPS. This is the expected behavior and not something you need to work around.

The pluginVersion constructor option must be five characters or fewer. It’s optional, but if you pass something longer, initialization will fail.

downloadTo() only works after an optimization call completes. Calling it on a freshly instantiated Source without running a job first won’t do anything useful. useful.

Resources

📄 ShortPixel SDK
📄 API documentation

Andrei Alba
Andrei Alba

Andrei Alba is a support specialist and writer here at ShortPixel. He enjoys helping people understand WordPress through his easily digestible materials.

Articles: 98