From e01d63d7ec65e2582d44dd1bce25c79f547add45 Mon Sep 17 00:00:00 2001 From: Constantine Nathanson Date: Mon, 17 Aug 2026 16:14:20 +0300 Subject: [PATCH 1/3] docs: add agent-readable documentation and repository metadata Ship version-matched Markdown task docs and runnable examples inside the published package so they always match the installed version. Add LICENSE, SECURITY.md, AGENTS.md, and context7.json. - docs/: 12 self-contained task guides, published with the package - examples/: 5 complete runnable scripts - LICENSE: MIT (previously only declared in package.json) - SECURITY.md: private vulnerability reporting and disclosure process - AGENTS.md + CLAUDE.md: contributor guide for coding agents - context7.json: Context7 library ownership verification - README: restructured around install, quick start, and common tasks - package.json: publish docs/, examples/, SECURITY.md, CHANGELOG.md Co-Authored-By: Claude Opus 5 --- .gitignore | 2 +- AGENTS.md | 77 +++++++++++ CLAUDE.md | 1 + LICENSE | 21 +++ README.md | 175 +++++++++++++----------- SECURITY.md | 37 +++++ context7.json | 4 + docs/README.md | 46 +++++++ docs/configure-cloudinary.md | 61 +++++++++ docs/migrate-to-v2.md | 47 +++++++ docs/moderate-upload.md | 68 +++++++++ docs/platform-capabilities.md | 31 +++++ docs/search-and-manage-assets.md | 71 ++++++++++ docs/sign-browser-upload.md | 66 +++++++++ docs/transform-and-deliver-media.md | 84 ++++++++++++ docs/troubleshoot-errors.md | 73 ++++++++++ docs/upload-image.md | 53 +++++++ docs/upload-large-video.md | 93 +++++++++++++ docs/use-structured-metadata.md | 68 +++++++++ examples/moderate-upload.js | 42 ++++++ examples/sign-browser-upload.js | 53 +++++++ examples/transform-and-deliver-image.js | 57 ++++++++ examples/upload-image.js | 36 +++++ examples/upload-large-video.js | 77 +++++++++++ package.json | 8 +- tools/scripts/docs.sh | 2 +- 26 files changed, 1270 insertions(+), 83 deletions(-) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 LICENSE create mode 100644 SECURITY.md create mode 100644 context7.json create mode 100644 docs/README.md create mode 100644 docs/configure-cloudinary.md create mode 100644 docs/migrate-to-v2.md create mode 100644 docs/moderate-upload.md create mode 100644 docs/platform-capabilities.md create mode 100644 docs/search-and-manage-assets.md create mode 100644 docs/sign-browser-upload.md create mode 100644 docs/transform-and-deliver-media.md create mode 100644 docs/troubleshoot-errors.md create mode 100644 docs/upload-image.md create mode 100644 docs/upload-large-video.md create mode 100644 docs/use-structured-metadata.md create mode 100644 examples/moderate-upload.js create mode 100644 examples/sign-browser-upload.js create mode 100644 examples/transform-and-deliver-image.js create mode 100644 examples/upload-image.js create mode 100644 examples/upload-large-video.js diff --git a/.gitignore b/.gitignore index 9aba9153..27b59701 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ bin !lib test_cache/ .nyc_output -docs +out/ coverage # contains temporary cloudianry_url for test accounts tools/cloudinary_url.sh diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..2cec5e53 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,77 @@ +# Contributor guide for coding agents + +This file is for agents contributing to this repository. If you are *using* the installed +`cloudinary` package in another project, read the bundled docs in +`node_modules/cloudinary/docs/` instead. + +## Commands + +```bash +npm install # install dependencies +npm test # lint + unit tests + type declaration tests +npm run test:unit # mocha unit tests only (mocked, no network) +npm run lint # eslint +npm run dtslint # TypeScript declaration tests +npm run test-with-temp-cloud # full integration tests against a temporary cloud (CI) +``` + +Unit tests require a `CLOUDINARY_URL` in the environment or a `.env` file; +any syntactically valid value works for mocked tests: +`CLOUDINARY_URL=cloudinary://key:secret@test-cloud`. + +## Testing + +- `test/unit/` is mocked and must never perform network calls. +- `test/integration/` requires a real or temporary Cloudinary environment; do not run it + by default and do not add tests there that consume paid add-ons without a skip guard. +- Nondeterministic AI output (captions, tags, moderation verdicts) must be asserted by + request shape, state transition, and response schema — never by exact output values. + +## Project structure + +- `cloudinary.js` — package entry point; exposes the legacy v1 API and `require('cloudinary').v2`. +- `lib/` — implementation. `lib/v2/` wraps the v1 modules with promise support. +- `lib/analysis/` — Analyze API (`analyze_uri`). +- `types/index.d.ts` — TypeScript declarations, tested by `npm run dtslint`. +- `examples/` — small, runnable task examples shipped in the npm package. +- `docs/` — version-matched Markdown docs shipped in the npm package. +- `samples/` — legacy full applications; not part of the tested example set. +- `test/` — `unit/`, `integration/`, shared helpers in `spechelper.js` and `testUtils/`. +- `tools/scripts/` — shell entry points used by the npm scripts. + +## Code style + +- CommonJS modules, ES6+ syntax, two-space indent; eslint config is authoritative. +- Public API methods accept an options object and an optional Node-style callback and + return a Promise, following the existing pattern: + +```js +exports.example_method = function example_method(public_id, callback, options = {}) { + return call_api("post", ["example"], { public_id }, callback, options); +}; +``` + +## Git workflow + +- Branch from `master`; keep changes focused; one topic per pull request. +- Run `npm test` before opening a PR. +- Do not rewrite published changelog entries; add new entries at the top. +- Never commit generated output (`out/`, `coverage/`), credentials, or `.env` files. + +## Boundaries + +**Always** +- Update `types/index.d.ts` and tests when public behavior changes. +- Keep `docs/` and `examples/` consistent with the code they document. +- Keep API secrets out of examples, docs, tests, and fixtures. + +**Ask first** +- Changing supported Node versions, dependencies, or `package.json.files`. +- Renaming or removing any public method or exported symbol. +- Changing release, CI, or publishing configuration. + +**Never** +- Commit credentials or real account identifiers. +- Perform live network calls in unit or example tests. +- Document a Cloudinary platform capability as an SDK method unless this package + implements it (see docs/platform-capabilities.md). diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..ac54d055 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2011-2026 Cloudinary Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index cfba7c4d..aabef40a 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,120 @@ -Cloudinary Node SDK -========================= -## About -The Cloudinary Node SDK allows you to quickly and easily integrate your application with Cloudinary. -Effortlessly optimize, transform, upload and manage your cloud's assets. +# Cloudinary Node.js SDK +Upload, transform, optimize, and manage images and videos with Cloudinary from Node.js — the `cloudinary` package on npm. -#### Note -This Readme provides basic installation and usage information. -For the complete documentation, see the [Node SDK Guide](https://cloudinary.com/documentation/node_integration). +[![CI](https://github.com/cloudinary/cloudinary_npm/actions/workflows/ci.yml/badge.svg)](https://github.com/cloudinary/cloudinary_npm/actions/workflows/ci.yml) +[![npm](https://img.shields.io/npm/v/cloudinary.svg)](https://www.npmjs.com/package/cloudinary) +[![License](https://img.shields.io/npm/l/cloudinary.svg)](LICENSE) -## Table of Contents -- [Key Features](#key-features) -- [Version Support](#Version-Support) -- [Installation](#installation) -- [Usage](#usage) - - [Setup](#Setup) - - [Transform and Optimize Assets](#Transform-and-Optimize-Assets) - - [Generate Image and HTML Tags](#Generate-Image-and-Video-HTML-Tags) +## Install - -## Key Features -- [Transform](https://cloudinary.com/documentation/node_video_manipulation#video_transformation_examples) and - [optimize](https://cloudinary.com/documentation/node_image_manipulation#image_optimizations) assets. -- Generate [image](https://cloudinary.com/documentation/node_image_manipulation#deliver_and_transform_images) and - [video](https://cloudinary.com/documentation/node_video_manipulation#video_element) tags. -- [Asset Management](https://cloudinary.com/documentation/node_asset_administration). -- [Secure URLs](https://cloudinary.com/documentation/video_manipulation_and_delivery#generating_secure_https_urls_using_sdks). - - - -## Version Support -| SDK Version | Node version | -|-------------|--------------| -| 1.x.x | Node@6 & up | -| 2.x.x | Node@9 & up | - -## Installation ```bash npm install cloudinary ``` -# Usage -### Setup -```js -// Require the Cloudinary library -const cloudinary = require('cloudinary').v2 -``` +## Quick start -### Transform and Optimize Assets -- [See full documentation](https://cloudinary.com/documentation/node_image_manipulation). +Set your API environment variable (Console > Settings > API Keys): -```js -cloudinary.url("sample.jpg", {width: 100, height: 150, crop: "fill", fetch_format: "auto"}) +```bash +export CLOUDINARY_URL=cloudinary://:@ ``` -### Upload -- [See full documentation](https://cloudinary.com/documentation/node_image_and_video_upload). -- [Learn more about configuring your uploads with upload presets](https://cloudinary.com/documentation/upload_presets). +Upload an image and get an optimized delivery URL: + ```js -cloudinary.v2.uploader.upload("/home/my_image.jpg", {upload_preset: "my_preset"}, (error, result)=>{ - console.log(result, error); +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; }); ``` -### Large/Chunked Upload -- [See full documentation](https://cloudinary.com/documentation/node_image_and_video_upload#node_js_video_upload). -```js - cloudinary.v2.uploader.upload_large(LARGE_RAW_FILE, { - chunk_size: 7000000 - }, (error, result) => {console.log(error)}); -``` -### Security options -- [See full documentation](https://cloudinary.com/documentation/solution_overview#security). -## Contributions -- Ensure tests run locally (add test command) -- Open a PR and ensure tests pass +Save as `quickstart.js` and run `node quickstart.js`. [Create a free account](https://cloudinary.com/users/register/free) if you don't have one. + +## Common tasks + +- [Upload an image](docs/upload-image.md) +- [Upload a large video](docs/upload-large-video.md) +- [Sign a browser upload](docs/sign-browser-upload.md) +- [Transform and deliver media](docs/transform-and-deliver-media.md) +- [Search and manage assets](docs/search-and-manage-assets.md) +- [Moderate an upload](docs/moderate-upload.md) +- [Use structured metadata](docs/use-structured-metadata.md) +- [Troubleshoot errors](docs/troubleshoot-errors.md) + +Runnable versions live in [`examples/`](examples/) — each is a complete file you can run directly. + +## When to use this SDK + +Use this package in **Node.js server-side code**: uploads, signed operations, asset +administration, search, moderation, and delivery URL generation. + +For other jobs, better-fitting tools exist: + +- Browser or frontend framework rendering: [@cloudinary/url-gen](https://www.npmjs.com/package/@cloudinary/url-gen) and the [framework SDKs](https://cloudinary.com/documentation/cloudinary_sdks). +- Complete in-browser upload UI: [Upload Widget](https://cloudinary.com/documentation/upload_widget). +- Text-to-image generation and image-to-video: [platform APIs](https://cloudinary.com/documentation/image_generation_addon), not wrapped by this package. +- Multi-step media workflow automation: [MediaFlows](https://cloudinary.com/documentation/mediaflows_user_guide). +- Interactive agent-driven asset operations: [Cloudinary MCP servers and Skills](https://cloudinary.com/documentation/cloudinary_llm_mcp). + +The full capability map is in [docs/platform-capabilities.md](docs/platform-capabilities.md). + +## Status and compatibility + +Stable, actively maintained. See [CHANGELOG.md](CHANGELOG.md). + +| SDK version | Node.js | +|-------------|---------| +| 2.x | 9 and later | +| 1.x | 6 and later (no longer maintained) | + +## Documentation + +- [Bundled task docs](docs/README.md) — ship inside the package, version-matched. +- [Node SDK guide](https://cloudinary.com/documentation/node_integration) — hosted documentation. +## For AI coding agents -## Get Help -If you run into an issue or have a question, you can either: -- Issues related to the SDK: [Open a Github issue](https://github.com/cloudinary/cloudinary_npm/issues). -- Issues related to your account: [Open a support ticket](https://cloudinary.com/contact) +- Contributing to this repo: read [AGENTS.md](AGENTS.md). +- Using the installed package: the docs in `node_modules/cloudinary/docs/` match your + installed version and are the source of truth; start with + [platform-capabilities](docs/platform-capabilities.md) before assuming a feature exists. +## Support -## About Cloudinary -Cloudinary is a powerful media API for websites and mobile apps alike, Cloudinary enables developers to efficiently manage, transform, optimize, and deliver images and videos through multiple CDNs. Ultimately, viewers enjoy responsive and personalized visual-media experiences—irrespective of the viewing device. +- SDK bugs and feature requests: [GitHub issues](https://github.com/cloudinary/cloudinary_npm/issues) +- Account and platform questions: [Cloudinary support](https://support.cloudinary.com) +## Security -## Additional Resources -- [Cloudinary Transformation and REST API References](https://cloudinary.com/documentation/cloudinary_references): Comprehensive references, including syntax and examples for all SDKs. -- [MediaJams.dev](https://mediajams.dev/): Bite-size use-case tutorials written by and for Cloudinary Developers -- [DevJams](https://www.youtube.com/playlist?list=PL8dVGjLA2oMr09amgERARsZyrOz_sPvqw): Cloudinary developer podcasts on YouTube. -- [Cloudinary Academy](https://training.cloudinary.com/): Free self-paced courses, instructor-led virtual courses, and on-site courses. -- [Code Explorers and Feature Demos](https://cloudinary.com/documentation/code_explorers_demos_index): A one-stop shop for all code explorers, Postman collections, and feature demos found in the docs. -- [Cloudinary Roadmap](https://cloudinary.com/roadmap): Your chance to follow, vote, or suggest what Cloudinary should develop next. -- [Cloudinary Facebook Community](https://www.facebook.com/groups/CloudinaryCommunity): Learn from and offer help to other Cloudinary developers. -- [Cloudinary Account Registration](https://cloudinary.com/users/register/free): Free Cloudinary account registration. -- [Cloudinary Website](https://cloudinary.com): Learn about Cloudinary's products, partners, customers, pricing, and more. +See [SECURITY.md](SECURITY.md) for private vulnerability reporting. Keep your +`api_secret` in server-side code; for client uploads, use the server-signed pattern in +[Sign a browser upload](docs/sign-browser-upload.md). +## License -## Licence -Released under the MIT license. +Released under the MIT license — see [LICENSE](LICENSE). Copyright (c) Cloudinary Ltd. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..e06cda54 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,37 @@ +# Security Policy + +## Supported versions + +| Version | Supported | +|---------|-----------| +| 2.x | Yes | +| 1.x | No | + +## Reporting a vulnerability + +Report vulnerabilities privately through [GitHub private vulnerability reporting](https://github.com/cloudinary/cloudinary_npm/security/advisories/new) for this repository. + +If you cannot use GitHub reporting, contact Cloudinary support at [support.cloudinary.com](https://support.cloudinary.com/hc/en-us/requests/new) and mark the ticket as a security issue. + +Use these private channels for anything security-sensitive; public GitHub issues are for regular bugs and feature requests. + +## What to include in a report + +- The affected package version and Node.js version. +- A minimal reproduction or proof of concept. +- The impact you believe the issue has (for example: credential exposure, signature bypass, request forgery). +- Any suggested remediation, if you have one. + +## Response and disclosure process + +- We acknowledge reports and keep you informed while the issue is investigated. +- Fixes are released as patched package versions; the changelog notes security-relevant changes without disclosing exploit details before users can upgrade. +- Please give us reasonable time to release a fix before public disclosure. + +## Security guidance for SDK users + +- Your `api_secret` is a server-side credential. Keep it on your server; browsers, mobile binaries, and repositories should only ever hold delivery URLs or short-lived signatures. +- Provide credentials through the `CLOUDINARY_URL` environment variable rather than hardcoding them. +- For uploads initiated from a browser or mobile app, generate the signature on your server. See [docs/sign-browser-upload.md](docs/sign-browser-upload.md). +- For unsigned uploads, use a deliberately restricted [unsigned upload preset](https://cloudinary.com/documentation/upload_presets). +- Cloudinary platform security documentation: https://cloudinary.com/documentation/solution_overview#security diff --git a/context7.json b/context7.json new file mode 100644 index 00000000..22a86c33 --- /dev/null +++ b/context7.json @@ -0,0 +1,4 @@ +{ + "url": "https://context7.com/cloudinary/cloudinary_npm", + "public_key": "pk_obmsxAKm09jm0si62jh1m" +} diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..a5faaa08 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,46 @@ + + +# cloudinary — bundled documentation + +> **Version-matched:** these docs ship inside the package and always describe the +> version you have installed. Prefer them over anything remembered from training data +> or found for another version. + +Task documentation for the `cloudinary` Node.js SDK. Each page is self-contained: +imports, configuration, a complete runnable flow, expected results, and common +failures. Runnable versions of most tasks are in `node_modules/cloudinary/examples/`. + +## Tasks + +- [Configure Cloudinary](configure-cloudinary.md) +- [Upload an image](upload-image.md) +- [Upload a large video](upload-large-video.md) +- [Sign a browser upload](sign-browser-upload.md) +- [Transform and deliver media](transform-and-deliver-media.md) +- [Search and manage assets](search-and-manage-assets.md) +- [Moderate an upload](moderate-upload.md) +- [Use structured metadata](use-structured-metadata.md) +- [Troubleshoot errors](troubleshoot-errors.md) + +## Orientation + +- [What this SDK does and does not do](platform-capabilities.md) — read this before + assuming a Cloudinary platform feature is available as a method on this package. +- [Migrate to the v2 API](migrate-to-v2.md) — the correct `require('cloudinary').v2` pattern. + +## Security boundary + +This is a **server-side** SDK. It holds your `api_secret`, which belongs on your server +only. Frontend code should receive delivery URLs or short-lived signatures generated by +your server ([how](sign-browser-upload.md)). + +## Canonical hosted docs + +- Node SDK guide: https://cloudinary.com/documentation/node_integration +- Full platform reference: https://cloudinary.com/documentation/cloudinary_references + +Hosted pages also serve Markdown: append `.md` to any documentation URL. diff --git a/docs/configure-cloudinary.md b/docs/configure-cloudinary.md new file mode 100644 index 00000000..136a0770 --- /dev/null +++ b/docs/configure-cloudinary.md @@ -0,0 +1,61 @@ +# Configure Cloudinary + +## When to use + +Do this once per process before any upload, admin, analysis, or URL-generation call. + +## Recommended: environment variable + +Set `CLOUDINARY_URL` (from Console > Settings > API Keys): + +```bash +export CLOUDINARY_URL=cloudinary://:@ +``` + +```js +const cloudinary = require('cloudinary').v2; +// Configuration is read from CLOUDINARY_URL automatically on first use. +console.log(cloudinary.config().cloud_name); +``` + +## Alternative: explicit configuration + +```js +const cloudinary = require('cloudinary').v2; + +cloudinary.config({ + cloud_name: 'my-cloud', + api_key: process.env.CLOUDINARY_API_KEY, + api_secret: process.env.CLOUDINARY_API_SECRET, + secure: true +}); +``` + +## Behavior you should know + +- Configuration is **process-global**: `cloudinary.config()` affects every caller in the + process. Pass per-call options as the last argument of a method when you need to + override (for example a different `cloud_name` for one call). +- Always import the v2 API once: `const cloudinary = require('cloudinary').v2;` and then + call `cloudinary.uploader...` directly — the imported object already is the v2 API. +- Proxy support: set `api_proxy` in config or the `HTTPS_PROXY` environment variable. +- Account-level (provisioning) operations use `CLOUDINARY_ACCOUNT_URL` instead. + +## Validate configuration early + +```js +const { cloud_name, api_key, api_secret } = cloudinary.config(); +if (!cloud_name || !api_key || !api_secret) { + throw new Error('Cloudinary is not configured: set CLOUDINARY_URL.'); +} +``` + +## Common failures + +- `Must supply cloud_name` — `CLOUDINARY_URL` is missing or malformed; it must start with `cloudinary://`. +- `401 Unauthorized` — key/secret mismatch for the cloud name; re-copy from the console. + +## Related + +- [Sign a browser upload](sign-browser-upload.md) — keeping the secret server-side. +- Hosted reference: https://cloudinary.com/documentation/node_integration diff --git a/docs/migrate-to-v2.md b/docs/migrate-to-v2.md new file mode 100644 index 00000000..19788ed2 --- /dev/null +++ b/docs/migrate-to-v2.md @@ -0,0 +1,47 @@ +# Migrate to the v2 API + +## The one correct import + +```js +const cloudinary = require('cloudinary').v2; + +// then: +cloudinary.uploader.upload(...) // correct +cloudinary.api.resource(...) // correct +cloudinary.url(...) // correct +``` + +## The most common mistake + +```js +const cloudinary = require('cloudinary').v2; +cloudinary.v2.uploader.upload(...) // WRONG - cloudinary is already the v2 API; + // there is no .v2 property on it. This throws + // "Cannot read properties of undefined". +``` + +Either import the root and use `.v2` everywhere, or (recommended) import `.v2` once and +write plain `cloudinary.uploader...` from then on. + +## What v2 adds over the legacy v1 API + +- Promises: every method returns a Promise when no callback is passed. v1 is + callback-only. +- Consistent parameter order: the options object comes right after the main arguments, + with an optional callback last. + +The legacy v1 surface (`require('cloudinary')` without `.v2`) still works for backward +compatibility; use the v2 API for all new code. + +## TypeScript + +Type declarations cover the v2 API: + +```ts +import { v2 as cloudinary } from 'cloudinary'; +``` + +## Related + +- [Configure Cloudinary](configure-cloudinary.md) +- Hosted migration notes: https://cloudinary.com/documentation/node_integration diff --git a/docs/moderate-upload.md b/docs/moderate-upload.md new file mode 100644 index 00000000..5178b9f0 --- /dev/null +++ b/docs/moderate-upload.md @@ -0,0 +1,68 @@ +# Moderate an upload + +## When to use + +Content uploaded by users must be reviewed before it is delivered. Moderation in +Cloudinary is stateful: assets are `pending` until a decision is recorded, and your +application is responsible for delivering approved assets only. + +## Complete flow (manual review queue) + +```js +const cloudinary = require('cloudinary').v2; // reads CLOUDINARY_URL + +async function main() { + // 1. Upload into the moderation queue - the asset starts as "pending" + const uploaded = await cloudinary.uploader.upload( + 'https://res.cloudinary.com/demo/image/upload/sample.jpg', // or the user's file + { + public_id: 'examples/moderated-upload', + overwrite: true, + moderation: 'manual' + } + ); + console.log(uploaded.moderation[0].status); // 'pending' + + // 2. Your review UI lists the queue + const queue = await cloudinary.api.resources_by_moderation('manual', 'pending', { + max_results: 50 + }); + console.log(`Assets pending review: ${queue.resources.length}`); + + // 3. A reviewer records the decision ('approved' or 'rejected') + await cloudinary.api.update(uploaded.public_id, { moderation_status: 'approved' }); + + // 4. Deliver approved assets only - gate on moderation status in your data model +} + +main().catch((error) => { + console.error(`Moderation flow failed: ${error.message}`); + process.exitCode = 1; +}); +``` + +## Automatic moderation + +Pass an add-on name instead of `manual` (for example `moderation: 'aws_rek'`) to get an +automated verdict; the same pending/approved/rejected states apply, and you can still +override a machine decision with `api.update` + `moderation_status` for human review. +Automatic moderators require the matching add-on to be enabled on the account. + +## Design rules + +- Model moderation as a state machine, not a boolean. Keep the pending state visible in + your product (placeholder image, "under review" label). +- Keep human override even with automatic moderation — machine verdicts are drafts for + anything with legal or brand consequences. +- Rejected assets stay in storage unless you delete them; decide your retention policy. + +## Common failures + +- `Moderation kind not supported` — the add-on is not enabled for the account. +- Delivering a pending asset — nothing blocks delivery by default; enforcement is your + application's responsibility (or use access control on the asset). + +## Related + +- Runnable example: `examples/moderate-upload.js` +- Hosted reference: https://cloudinary.com/documentation/cloudinary_moderation diff --git a/docs/platform-capabilities.md b/docs/platform-capabilities.md new file mode 100644 index 00000000..b4174605 --- /dev/null +++ b/docs/platform-capabilities.md @@ -0,0 +1,31 @@ +# What this SDK does and does not do + +Cloudinary is a platform with many surfaces. This package — the Node.js server-side +SDK — implements some of them natively, expresses some through generic URL building, +and does not implement others. Verified against this package version. + +| Capability | This package | Where to go | +|---|---|---| +| Upload, chunked/streamed upload, asset mutation | **Native** (`uploader`) | [Upload an image](upload-image.md), [Upload a large video](upload-large-video.md) | +| Admin API: list, update, restore, folders, presets | **Native** (`api`) | [Search and manage assets](search-and-manage-assets.md) | +| Search query builder | **Native** (`search`) | [Search and manage assets](search-and-manage-assets.md) | +| Delivery/transformation URLs, image & video tags | **Native** (`url`, `image`, `video`) | [Transform and deliver media](transform-and-deliver-media.md) | +| Signing browser/mobile uploads | **Native** (`utils.api_sign_request`) | [Sign a browser upload](sign-browser-upload.md) | +| Analyze API (captioning, tagging, detection) | **Native, limited model set** (`analysis.analyze_uri`); requires a subscription | [Analyze API guide](https://cloudinary.com/documentation/analyze_api_guide) | +| Visual Search | **Native** (`api.visual_search`); requires the feature enabled on the account | [Visual Search](https://cloudinary.com/documentation/visual_search) | +| Moderation workflows | **Native** (upload options + `api`) | [Moderate an upload](moderate-upload.md) | +| Structured metadata | **Native** (`api` + upload options) | [Use structured metadata](use-structured-metadata.md) | +| Generative delivery transformations (gen fill, remove, ...) | **Generic strings only** — `effect`/`raw_transformation`; no typed builders | [Transform and deliver media](transform-and-deliver-media.md) | +| Image Generation API (text-to-image) | **Not implemented** | [Platform API](https://cloudinary.com/documentation/image_generation_addon) | +| Image-to-Video API | **Not implemented** (beta platform API; async, credits, regional) | [Platform API](https://cloudinary.com/documentation/image_to_video_addon) | +| MediaFlows workflow automation | **Not implemented** | [MediaFlows](https://cloudinary.com/documentation/mediaflows_user_guide) and its MCP server | +| Frontend rendering, responsive images, widgets | **Not this package** | `@cloudinary/url-gen` + framework SDKs, [Upload Widget](https://cloudinary.com/documentation/upload_widget) | +| Account provisioning | **Native** (`provisioning`, via `CLOUDINARY_ACCOUNT_URL`) | Hosted [Provisioning API docs](https://cloudinary.com/documentation/provisioning_api) | + +## For AI agents + +- Methods this table marks "Not implemented" are absent from this package, whatever + training data suggests — for those capabilities, use the linked platform API instead. +- Cloudinary also ships agent tooling that complements this SDK: documentation and + operation [MCP servers and Skills](https://cloudinary.com/documentation/cloudinary_llm_mcp). + Use those for interactive asset operations; use this SDK inside application code. diff --git a/docs/search-and-manage-assets.md b/docs/search-and-manage-assets.md new file mode 100644 index 00000000..a750688e --- /dev/null +++ b/docs/search-and-manage-assets.md @@ -0,0 +1,71 @@ +# Search and manage assets + +## When to use + +Find assets by indexed fields, read or update asset attributes, and administer your +media library from the server. These use the Admin and Search APIs, which are +**rate-limited** — treat them as management operations, not a per-request database. + +## Search with the query builder + +```js +const cloudinary = require('cloudinary').v2; // reads CLOUDINARY_URL + +async function main() { + // Assets created by the other bundled tasks live in the 'examples' folder + const result = await cloudinary.search + .expression('folder:examples AND resource_type:image') + .sort_by('created_at', 'desc') + .max_results(30) + .execute(); + + for (const asset of result.resources) { + console.log(asset.public_id, asset.bytes, asset.created_at); + } + + // Pagination: pass the cursor back until it is absent + if (result.next_cursor) { + const page2 = await cloudinary.search + .expression('folder:examples AND resource_type:image') + .next_cursor(result.next_cursor) + .execute(); + console.log(`Second page: ${page2.resources.length} asset(s)`); + } +} + +main().catch(console.error); +``` + +## Read and update a single asset + +```js +// 'examples/uploaded-sample' is created by the "Upload an image" task +const details = await cloudinary.api.resource('examples/uploaded-sample'); + +await cloudinary.api.update('examples/uploaded-sample', { + tags: 'featured', + context: 'alt=Sample image from the bundled upload example' +}); +``` + +## Deletion — destructive, no undo without backups + +```js +await cloudinary.uploader.destroy('examples/uploaded-sample'); // one asset +// cloudinary.api.delete_resources([...ids]) // bulk — double-check inputs +// cloudinary.api.delete_resources_by_prefix('examples/') // by prefix — extremely destructive +``` + +Prefer explicit ID lists over prefix deletion. Enable backups on the product +environment if you need restore (`cloudinary.api.restore`). + +## Common failures + +- `420 Rate limit exceeded` — the response includes reset time; back off and batch work. +- Stale search results — the search index lags writes by a short interval; for + read-after-write flows, use `api.resource` with the known `public_id`. + +## Related + +- [Use structured metadata](use-structured-metadata.md) +- Hosted reference: https://cloudinary.com/documentation/node_asset_administration diff --git a/docs/sign-browser-upload.md b/docs/sign-browser-upload.md new file mode 100644 index 00000000..43335485 --- /dev/null +++ b/docs/sign-browser-upload.md @@ -0,0 +1,66 @@ +# Sign a browser upload + +## When to use + +A browser or mobile app uploads directly to Cloudinary, but you want the operation +authorized by your server. The `api_secret` stays on the server; the client receives a +short-lived signature. + +For uploads without a server round-trip, use an +[unsigned upload preset](https://cloudinary.com/documentation/upload_presets) instead — +deliberately restricted, because anyone can use it. + +## Server: signing endpoint + +```js +const cloudinary = require('cloudinary').v2; // reads CLOUDINARY_URL + +// Example Express route +app.get('/api/sign-upload', (req, res) => { + const { api_key, api_secret, cloud_name } = cloudinary.config(); + const timestamp = Math.round(Date.now() / 1000); + const params = { timestamp, folder: 'user-uploads' }; // sign ONLY what the client may use + + const signature = cloudinary.utils.api_sign_request(params, api_secret); + res.json({ signature, timestamp, folder: params.folder, api_key, cloud_name }); +}); +``` + +## Browser: use the signature + +```js +const { signature, timestamp, folder, api_key, cloud_name } = await (await fetch('/api/sign-upload')).json(); + +const form = new FormData(); +form.append('file', fileInput.files[0]); +form.append('api_key', api_key); +form.append('timestamp', timestamp); +form.append('signature', signature); +form.append('folder', folder); + +const response = await fetch(`https://api.cloudinary.com/v1_1/${cloud_name}/auto/upload`, { + method: 'POST', + body: form +}); +const asset = await response.json(); // contains public_id, secure_url, ... +``` + +## Rules + +- Every parameter the browser sends (except `file`, `api_key`, `signature`, and + `resource_type`) must be included in the signed parameter set, or Cloudinary rejects + the request with `Invalid Signature`. +- Signatures embed the timestamp and expire; generate one per upload. +- Keep the `api_secret` in server code only; the client receives just the signature, + timestamp, `api_key`, and `cloud_name`. + +## Common failures + +- `Invalid Signature` — the client sent a parameter that was not signed, or sent values + differing from the signed ones. +- `Stale request` — the timestamp is too old; the client waited too long after signing. + +## Related + +- Runnable example: `examples/sign-browser-upload.js` +- Hosted reference: https://cloudinary.com/documentation/upload_images#generating_authentication_signatures diff --git a/docs/transform-and-deliver-media.md b/docs/transform-and-deliver-media.md new file mode 100644 index 00000000..062e2700 --- /dev/null +++ b/docs/transform-and-deliver-media.md @@ -0,0 +1,84 @@ +# Transform and deliver media + +## When to use + +Generate delivery URLs that resize, crop, overlay, or optimize images and video. URL +generation is local — no network call, no secret required — and the derived asset is +created by Cloudinary on first request, then served from CDN cache. + +## Optimized image URL + +```js +const cloudinary = require('cloudinary').v2; // only cloud_name is needed for URL generation + +// 'sample' ships with every new Cloudinary account; substitute any public_id you own +const thumbnailUrl = cloudinary.url('sample', { + width: 200, + height: 200, + crop: 'thumb', + gravity: 'auto', // focus on the most interesting region; use 'face' for people photos + fetch_format: 'auto', // f_auto: best format for the requesting browser + quality: 'auto', // q_auto: perceptual quality tuning + secure: true +}); +console.log(thumbnailUrl); +// https://res.cloudinary.com//image/upload/c_thumb,f_auto,g_auto,h_200,q_auto,w_200/sample +``` + +## Chained transformations (order matters) + +Each component runs on the output of the previous one: + +```js +// A text overlay needs no second asset; to overlay an image instead, pass +// overlay: ''. +const bannerUrl = cloudinary.url('sample', { + transformation: [ + { width: 1280, height: 720, crop: 'fill', gravity: 'auto' }, + { + overlay: { font_family: 'Arial', font_size: 64, font_weight: 'bold', text: 'SALE' }, + color: 'white', + gravity: 'south_east', + x: 24, + y: 24 + }, + { fetch_format: 'auto', quality: 'auto' } + ], + secure: true +}); +console.log(bannerUrl); +``` + +Reordering components changes the output. When matching eagerly generated versions, +the serialized transformation string must match exactly. + +## Video + +```js +// 'examples/uploaded-large-video' is created by the "Upload a large video" task +const clip = cloudinary.video('examples/uploaded-large-video', { + width: 640, + crop: 'scale', + quality: 'auto', + controls: true +}); // returns an HTML