---
title: Store Jodit uploads in AWS S3 with the Node.js connector
description: Point the Jodit Node.js file connector at an S3 bucket (AWS, MinIO, Cloudflare R2, Yandex Object Storage) so the file browser and uploader work without local disk.
keywords: jodit s3, jodit aws, jodit file browser s3, jodit uploader s3, jodit-nodejs s3, minio, cloudflare r2, jodit connector bucket
---

# Store Jodit uploads in AWS S3 with the Node.js connector

The [Node.js connector](./jodit-nodejs.html) for Jodit has a built-in `s3` storage adapter since version 1.0.37. A source configured with it keeps files in a bucket instead of a directory: the file browser lists the bucket, uploads go straight to it, and thumbnails, resize and crop work on the objects there. The editor does not change at all, it still talks to the connector.

The adapter is built on the official AWS SDK and works with every service that speaks the S3 API: MinIO, Cloudflare R2, Yandex Object Storage, DigitalOcean Spaces, Backblaze B2 and others.

## Why a bucket

- Several connector instances (or containers) share the same files without a shared volume.
- Uploads are served by the bucket or a CDN, so the connector only handles listings and writes.
- Backups, lifecycle rules and access logs come from the storage provider.

## Configure a source

Install the connector and point a source at the bucket:

```bash
npm install jodit-nodejs
```

```typescript
import { start } from 'jodit-nodejs';

await start({
	port: 8081,
	config: {
		allowCrossOrigin: true,
		sources: {
			media: {
				name: 'media',
				title: 'Media library',
				baseurl:
					'https://my-bucket.s3.eu-central-1.amazonaws.com/media/',
				storageAdapter: 's3',
				s3: {
					bucket: 'my-bucket',
					region: 'eu-central-1',
					prefix: 'media'
				}
			}
		}
	}
});
```

`baseurl` is the public URL of the prefix. The connector puts it in front of every file path it returns, and the editor loads images from there.

There is no `root` option for an S3 source. The connector treats the prefix as the root and confines every path the client sends to it.

### Credentials

Leave `s3.credentials` out and the AWS SDK finds them in the usual places: the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables, a profile in `~/.aws/credentials`, or the instance role on EC2, ECS and EKS. Pass them explicitly when one connector serves several buckets with different keys:

```typescript
s3: {
	bucket: 'my-bucket',
	credentials: {
		accessKeyId: process.env.MEDIA_KEY,
		secretAccessKey: process.env.MEDIA_SECRET
	}
}
```

### The same source as a JSON config

The Docker image reads its configuration from a JSON file, so the same source looks like this in `config.json`:

```json
{
	"allowCrossOrigin": true,
	"sources": {
		"media": {
			"name": "media",
			"title": "Media library",
			"baseurl": "https://my-bucket.s3.eu-central-1.amazonaws.com/media/",
			"storageAdapter": "s3",
			"s3": {
				"bucket": "my-bucket",
				"region": "eu-central-1",
				"prefix": "media"
			}
		}
	}
}
```

```bash
docker run --rm -p 8081:8081 \
  -e AWS_ACCESS_KEY_ID=AKIA... \
  -e AWS_SECRET_ACCESS_KEY=... \
  -v $(pwd)/config.json:/usr/src/app/config.json \
  xdsoft/jodit-nodejs
```

You do not need a files volume: everything lives in the bucket.

## S3-compatible services

Add `endpoint`, and `forcePathStyle` where the service wants path-style URLs:

| Service               | `endpoint`                                      | `forcePathStyle` |
| --------------------- | ----------------------------------------------- | ---------------- |
| MinIO                 | `http://minio:9000`                             | `true`           |
| Cloudflare R2         | `https://<account-id>.r2.cloudflarestorage.com` | `true`           |
| Yandex Object Storage | `https://storage.yandexcloud.net`               | `false`          |
| DigitalOcean Spaces   | `https://<region>.digitaloceanspaces.com`       | `false`          |
| Backblaze B2          | `https://s3.<region>.backblazeb2.com`           | `false`          |

A MinIO source for local development:

```typescript
s3: {
	bucket: 'jodit',
	endpoint: 'http://localhost:9000',
	forcePathStyle: true,
	prefix: 'files',
	credentials: { accessKeyId: 'minioadmin', secretAccessKey: 'minioadmin' }
}
```

## Make the files readable

The connector never proxies file downloads to the browser. Images load from `baseurl`, so the prefix has to be readable: either a bucket policy that allows `s3:GetObject` on `my-bucket/media/*`, or a CDN (CloudFront, the provider's CDN) in front of a private bucket with `baseurl` pointing at the CDN.

The adapter does not set object ACLs, because buckets created after 2023 have ACLs disabled by default. A bucket policy is the way to go.

The IAM user or role of the connector needs `s3:ListBucket` on the bucket and `s3:GetObject`, `s3:PutObject`, `s3:DeleteObject` on the prefix. `PutObject` also covers rename and move, which are implemented as copy plus delete.

## Editor side

The editor configuration has nothing S3-specific in it. Point the uploader and the file browser at the connector:

```javascript
Jodit.make('#editor', {
	uploader: {
		url: 'https://connector.example.com/?action=fileUpload'
	},
	filebrowser: {
		ajax: {
			url: 'https://connector.example.com/'
		}
	}
});
```

With Jodit PRO the same URL goes into the `finder` plugin configuration.

## What ends up in the bucket

- A file shown as `/photos/cat.jpg` is the object `media/photos/cat.jpg`.
- Creating a folder writes a zero-byte object `media/photos/`, the same convention the AWS console uses. Folders that only exist because objects sit under them are listed too.
- Thumbnails are stored next to the originals in a `_thumbs` folder, exactly as with local storage. Set `createThumb: false` on the source if you would rather not have them in the bucket.
- Existing objects uploaded by other tools appear in the file browser as long as they are under the prefix and their extension is allowed.

## Further reading

- [AWS S3 and S3-compatible storage](https://jodit.github.io/jodit-nodejs/aws-s3/) in the connector docs: every option, IAM and bucket policies, troubleshooting.
- [Dynamic sources](https://jodit.github.io/jodit-nodejs/dynamic-sources/): one connector serving many buckets, resolved per request.
- [Storage adapters](https://jodit.github.io/jodit-nodejs/storage-adapters/): writing an adapter for another backend.
