CLI tool
cloudinary/cloudinary_npm avatar
cloudinary/cloudinary_npm

What the cloudinary npm package does inside a Node.js service

Upload, transform, optimize, and manage images and videos with Cloudinary from Node.js.

670 stars318 forksJavaScriptMIT

At a glance

What is it?
Cloudinary's Node.js SDK covers server-side uploads, signed operations, asset administration and delivery URL generation for the `cloudinary` package on npm. It is a thin, stable wrapper around one hosted platform, and the interesting decisions live in how credentials are supplied and where the package deliberately stops.
Who is it for?
Adopt this package if your Node.js service already stores media in Cloudinary and you want one maintained client for uploads, signatures and delivery URLs. Do not adopt it for browser rendering, in-browser upload widgets, generative image or video features, or multi-step workflow automation, since the README points each of those at a different tool.
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 17 days ago.
What is it written in?
Mainly JavaScript, 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 runtime dependency, one entry point, and a bundled docs tree

The `cloudinary` package on npm resolves to a single runtime dependency, lodash, and a single main entry, cloudinary.js. That is a small surface for a client that talks to a large hosted platform. The repository carries the usual machinery of a long-lived JavaScript library: babel.config.js, a .eslintrc.js plus an eslintrc.node9.js for the older runtime, .mocharc.json, tsconfig.json, and a types/ directory that publishes TypeScript declarations through the `types` field.

What stands out is the published file list. Alongside the lib directory and cloudinary.js, the package ships docs/**/*.md and examples/**/*.js. The examples are complete runnable files, not fragments: examples/upload-image.js, examples/upload-large-video.js, examples/sign-browser-upload.js, examples/transform-and-deliver-image.js, examples/transform-and-deliver-video.js and examples/moderate-upload.js. The npm scripts point at shell wrappers in tools/scripts, including a separate suite that runs against a temporary cloud, which tells you the test setup is aware of the difference between local checks and anything that touches the real service.

CLOUDINARY_URL is the whole credential story

Credentials arrive through a single environment variable rather than a config file the library reads. The README tells you to set it from Console > Settings > API Keys, using the cloudinary:// scheme:

bash
export CLOUDINARY_URL=cloudinary://<api_key>:<api_secret>@<cloud_name>

That string embeds the API key, the API secret and the cloud name, so it inherits the usual operational hazards of a connection string. The quick start catches this explicitly: the error handler prints `Check that CLOUDINARY_URL is set (Console > Settings > API Keys).` and sets process.exitCode to 1, so a missing variable surfaces as a failed process rather than a silent no-op.

There is also a path to credentials that does not require a signup form. The README mentions running `npx @cloudinary/cloud` to provision an account without signing up, and points at docs/get-credentials.md for the full walkthrough. For a first evaluation that is the cheaper route. If you already have a cloud, exporting CLOUDINARY_URL is all the setup there is, and there is no separate client constructor step in the example.

Uploading a remote image and printing a transformed delivery URL

The documented first use uploads a remote asset, then asks the library to build a delivery URL for it. The quick start in the README looks like this:

js
const cloudinary = require('cloudinary').v2;

async function main() {
  // Upload a remote image (a local file path works the same way)
  const result = await cloudinary.uploader.upload(
    'https://res.cloudinary.com/demo/image/upload/sample.jpg',
    { public_id: 'quickstart-sample' }
  );
  console.log(`Uploaded: ${result.public_id}`);

  // Build a 400x400 auto-cropped URL with automatic format and quality
  const url = cloudinary.url(result.public_id, {
    width: 400,
    height: 400,
    crop: 'fill',
    gravity: 'auto',
    fetch_format: 'auto',
    quality: 'auto',
    secure: true
  });
  console.log(`Optimized URL: ${url}`);
}

main().catch((error) => {
  console.error(`Quick start failed: ${error.message}`);
  console.error('Check that CLOUDINARY_URL is set (Console > Settings > API Keys).');
  process.exitCode = 1;
});

Save it as quickstart.js and run `node quickstart.js`. Two things worth noting about the shape of that code. First, the require goes through `.v2`, so the modern API is reached through an explicit version suffix rather than a separate package name. Second, the transformation is expressed entirely as parameters on cloudinary.url: width, height, crop set to fill, gravity set to auto, fetch_format set to auto, quality set to auto, and secure set to true. The generated address encodes those choices, which is why the same public_id can serve many sizes and aspect ratios without storing derivatives locally.

Raw uploads lose their extension unless you pass filename

Buffers and typed arrays are accepted directly, which is what makes server-side uploads from generated content practical. The catch is in how raw files are named. The README states that the default for original_filename is the File name, or the string `file`, and that raw uploads keep their extension only when filename is passed. The example is short:

js
const csv = Buffer.from('id,name\n1,sample\n');
await cloudinary.uploader.upload(csv, { resource_type: 'raw', filename: 'report.csv' });

Without that filename argument, a buffer upload of a CSV arrives under a generic name, and any downstream code that infers the type from the extension has nothing to work with. The upload() method also accepts a Uint8Array, an ArrayBuffer, a Blob or a File, so the naming rule matters for each of those input shapes. This is a small trap, but it sits on the most common path of uploading a generated report or export from a background job, where no File object exists to supply a name.

The package stops at the server boundary on purpose

The README is unusually direct about where this SDK should not be used, and the boundary is the server. For browser and frontend framework rendering it points to @cloudinary/url-gen and the frontend SDKs. For a complete in-browser upload interface it points to the Upload Widget. For text-to-image generation and image-to-video it points to platform APIs, with the note that those are not wrapped by this package. Multi-step media workflow automation belongs to MediaFlows, and interactive agent-driven asset operations to Cloudinary MCP servers and Skills.

The practical difference is that those alternatives are not smaller versions of the same thing. @cloudinary/url-gen is built for delivery URL construction in the browser rather than for server-side upload calls. The Upload Widget is a ready-made browser UI rather than a Node.js client you wire into your own form. The platform generative APIs are not reachable through this package at all. If your roadmap includes any of those, the question of whether to adopt `cloudinary` is settled before the install, because the relevant capability lives in a different dependency.

Keeping api_secret out of client code with signed browser uploads

The security guidance in the README has one instruction that carries most of the weight: keep your api_secret in server-side code, and for client uploads use the server-signed pattern described in docs/sign-browser-upload.md. The examples directory contains examples/sign-browser-upload.js, so the pattern is implemented rather than merely described.

This is the practical reason a Node.js SDK exists alongside the browser tooling. A browser cannot hold the API secret, so a direct browser upload has to be authorised by a signature that your server computes. The server-side package is what produces that signature. A team that tries to keep everything in the browser and skips this package ends up with a different problem, and the README does not present a browser variant of the upload call as an alternative to signing.

Private vulnerability reporting is handled through SECURITY.md, which is shipped inside the package alongside CHANGELOG.md. The licence is MIT, with copyright held by Cloudinary Ltd. The MIT grant covers use, modification and redistribution with the notice retained, so there is no copyleft obligation on the code you build around this client. That is a licensing note rather than legal advice, and it does not settle the terms of the hosted Cloudinary service itself.

2.x on Node.js 9 or later, with 1.x left behind

The compatibility table in the README is small and worth reading before an upgrade. SDK version 2.x targets Node.js 9 and later, while 1.x targeted Node.js 6 and later and is marked as no longer maintained. The package.json engines field agrees, declaring node >=9.

On maintenance, the README calls the project stable and actively maintained, and the recorded activity is consistent with that claim rather than being a stale label. The last push was on 2026-10-06, and the release history shows 2.11.0 on 2026-08-27, 2.10.1 on 2026-08-19 and 2.10.0 on 2026-04-25. Those two releases inside August suggest a fairly active line, though the gap back to April shows the cadence is not fixed.

Upgrade cost for a 1.x user is the sharp edge here. The 2.x line requires Node.js 9 or later, so moving off the unmaintained branch means moving off older Node.js runtimes at the same time. The repository also keeps a separate eslintrc.node9.js, which implies the build still accounts for the oldest supported runtime even while the release line moves forward.

Docs that ship inside the package and follow your installed version

Cloudinary keeps two documentation shapes for the same pages. README links point at a browsable HTML page, with an `(md)` companion link that returns the same content as raw Markdown. Inside docs/ and examples/ the links are Markdown only, because those files are written to be read by coding agents.

The more useful detail is versioning. The bundled task docs are described as shipping inside the package, version-matched, so node_modules/cloudinary/docs/ corresponds to whatever you installed. The README recommends starting with docs/platform-capabilities.md before assuming a feature exists. That capability map is also where the README points for the Skills, MCP servers and CLI that are worth setting up first.

For a team running an agent against this package, that instruction is practical rather than decorative. Checking the installed docs directory tells you which capabilities your pinned version actually exposes, and it avoids chasing a feature that exists in the platform guide but not in your lockfile. The repository also carries AGENTS.md and CLAUDE.md at the top level for contributions, and the README points contributors at AGENTS.md before they open a pull request.

Editorial conclusion

Adopt this package if your Node.js service already stores media in Cloudinary and you want one maintained client for uploads, signatures and delivery URLs. Do not adopt it for browser rendering, in-browser upload widgets, generative image or video features, or multi-step workflow automation, since the README points each of those at a different tool. Before committing, check that your Node.js runtime is 9 or later for the 2.x line, confirm the CLOUDINARY_URL value your deployment actually injects, and read the bundled docs in node_modules/cloudinary/docs/ that match the installed version rather than relying on the published guide.

Frequently asked questions

How do I install the cloudinary npm package and set up credentials?

Run npm install cloudinary, then export CLOUDINARY_URL in the cloudinary://<api_key>:<api_secret>@<cloud_name> form, taking the values from Console > Settings > API Keys. The quick start in the README prints a reminder to check that variable if the upload fails.

Which Node.js versions does the cloudinary SDK support?

The 2.x line supports Node.js 9 and later, and package.json declares an engines requirement of node >=9. The 1.x line covered Node.js 6 and later and is marked as no longer maintained.

Can I use the cloudinary npm package in the browser instead of on the server?

The README scopes this package to Node.js server-side code and points browser rendering to @cloudinary/url-gen and the frontend SDKs, and complete in-browser upload interfaces to the Upload Widget. For client uploads the guidance is to sign the request on the server.

Why does my uploaded file lose its name or extension?

For raw uploads the extension is kept only when a filename is passed, and original_filename otherwise defaults to the File name or the string file. Buffer, Uint8Array, ArrayBuffer, Blob and File inputs are all accepted by upload().

Does the cloudinary npm package support text-to-image generation?

No. The README lists text-to-image generation and image-to-video under the platform APIs and states that these are not wrapped by this package. Multi-step workflow automation is directed to MediaFlows instead.

Official sources

  1. cloudinary/cloudinary_npm on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cloudinary-cloudinary-npm.svg)](https://hysenlabs.com/projects/cloudinary-cloudinary-npm)

Was this useful?