AWS S3 and S3-compatible storage¶
The built-in s3 adapter keeps a source in a bucket (optionally under a key prefix) through boto3. It needs the s3 extra: pip install "jodit-python[s3]" (or [all]); without it requests to S3 sources answer 501, while other sources keep working.
Quick start¶
{
"debug": false,
"allowCrossOrigin": true,
"createThumb": true,
"thumbSize": 250,
"quality": 90,
"sources": {
"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"
}
}
}
}
Or with Docker:
docker run --rm -p 8081:8081 \
-e CONFIG_FILE=/app/config.json -v $(pwd)/s3.json:/app/config.json:ro \
-e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \
w2fb/jodit-python
Options¶
All options live under sources.<name>.s3.
| Option | Type | Default | Meaning |
|---|---|---|---|
bucket |
string |
required | Bucket name |
region |
string |
us-east-1 |
AWS region; S3-compatible services usually accept the default |
endpoint |
string |
AWS | Endpoint URL of an S3-compatible service |
forcePathStyle |
boolean |
service default | endpoint/bucket/key URLs; MinIO and some self-hosted services need true |
prefix |
string |
"" |
Key prefix acting as the source root, e.g. uploads/site-a |
credentials |
object |
AWS default chain | accessKeyId, secretAccessKey, optional sessionToken |
publicBaseUrl |
string |
bucket URL | Base of S3StorageAdapter.public_url(); the connector's answers use the source baseurl |
connectTimeout |
number |
10 |
Seconds to wait for a connection |
readTimeout |
number |
60 |
Seconds to wait for data on an open connection |
maxAttempts |
integer |
3 |
Attempts per request, first one included (standard retry mode: throttling and transient errors, with backoff) |
serverSideEncryption |
string |
bucket default | AES256, aws:kms or aws:kms:dsse |
sseKmsKeyId |
string |
AWS managed key | KMS key ID, ARN or alias; needs serverSideEncryption aws:kms or aws:kms:dsse |
storageClass |
string |
STANDARD |
Storage class of uploaded files, e.g. STANDARD_IA, INTELLIGENT_TIERING |
cacheControl |
string |
none | Cache-Control header of uploaded files, e.g. public, max-age=31536000 |
Encryption and the storage class apply to uploads, copies, moves and generated files; folder markers get the encryption only. A copy keeps the source's Cache-Control and Content-Type. Archive classes (GLACIER, DEEP_ARCHIVE) are not a good fit: their objects cannot be read, so previews and thumbnails fail.
baseurl of the source is what file paths in answers are relative to: the public URL of the prefix, ending with /.
Credentials¶
Without credentials boto3 looks in the standard places:
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY(andAWS_SESSION_TOKEN)- the shared files
~/.aws/credentials/~/.aws/configandAWS_PROFILE - the instance, task or pod role (EC2, ECS, EKS/IRSA)
This keeps secrets out of configuration files. Use credentials when one connector serves buckets with different keys; the values can come from your secret store through a dynamic source instead of a file.
S3-compatible services¶
| Service | endpoint |
forcePathStyle |
region |
|---|---|---|---|
| MinIO | http://minio:9000 |
true |
any |
| Cloudflare R2 | https://<account-id>.r2.cloudflarestorage.com |
true |
auto |
| Yandex Object Storage | https://storage.yandexcloud.net |
false |
ru-central1 |
| DigitalOcean Spaces | https://<region>.digitaloceanspaces.com |
false |
the space region |
| Backblaze B2 | https://s3.<region>.backblazeb2.com |
false |
the bucket region |
| Wasabi | https://s3.<region>.wasabisys.com |
false |
the bucket region |
OpenStack Swift (s3api) |
the provider's S3 endpoint | true |
the provider's, often us-east-1 |
| Ceph RADOS Gateway | https://<rgw-host> |
true |
the zonegroup name, often default |
MinIO, OpenStack Swift and Ceph RGW are tested against the same storage contract as the other adapters (Docker images in the test suite); the other services are configured the same way but not tested.
MinIO next to the connector in Docker Compose:
{
"sources": {
"files": {
"title": "Files",
"baseurl": "http://localhost:9000/jodit/files/",
"storageAdapter": "s3",
"s3": {
"bucket": "jodit",
"endpoint": "http://minio:9000",
"forcePathStyle": true,
"prefix": "files",
"credentials": {"accessKeyId": "minioadmin", "secretAccessKey": "minioadmin"}
}
}
}
}
The connector talks to MinIO at minio:9000 inside the network; the browser loads files from localhost:9000 (baseurl).
OpenStack clouds¶
Object storage in OpenStack clouds (OVHcloud, Infomaniak, Catalyst Cloud, Cleura, private clouds) is OpenStack Swift or Ceph RADOS Gateway. Both speak the S3 API, so the s3 adapter works with them:
- Credentials. Swift's S3 layer takes EC2-style credentials from Keystone:
openstack ec2 credentials createprints anaccessand asecretfor the current project. Ceph RGW users have keys of their own (radosgw-admin user create), or Keystone EC2 credentials when RGW is wired to Keystone. - Endpoint. The provider documents an S3 endpoint for each region; it is not the Swift (
/v1/AUTH_...) URL. UseforcePathStyle: trueunless the provider supports bucket subdomains. - Containers are buckets. A Swift container created in Horizon or with
openstack container createis thebucket. - Public files. Make the container public on the provider's side (a public read ACL in Horizon, or a bucket policy where the gateway supports one) and use its public URL as
baseurl.
The native Swift API with Keystone authentication (without the S3 layer) is not supported.
Serving the files¶
The connector never streams files to the browser: answers contain paths and the editor loads baseurl + path. The objects under the prefix must be readable by browsers.
Public read on the prefix, with a bucket policy:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "PublicReadForJoditUploads",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/media/*"
}]
}
On AWS this also needs "Block public access" relaxed for bucket policies (BlockPublicPolicy, RestrictPublicBuckets).
A CDN in front of the bucket (CloudFront with origin access control, or your provider's CDN): baseurl points at the CDN and the bucket stays private.
The adapter never sets object ACLs; buckets created since April 2023 have ACLs disabled, and access goes through the bucket policy.
Private buckets¶
baseurl must open for everyone who reads the content, not only for editors: the editor inserts baseurl + path into the HTML, and the file browser uses the same URL for previews, drag and drop and the image editor. Two setups keep the rest of the bucket private:
- Keep public files under their own prefix, readable by the bucket policy above, and set it as the source
prefix. Keys outside the prefix are never listed, read or written. - Put a CDN with access to the bucket in front of it and point
baseurlat the CDN.
Signed URLs are not generated: they expire (after 7 days at most), and content that links to them would break.
IAM policy¶
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-bucket",
"Condition": {"StringLike": {"s3:prefix": ["media/*", "media"]}}
},
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::my-bucket/media/*"
}
]
}
s3:PutObject covers copies, which rename and move use. With serverSideEncryption: "aws:kms" and a customer managed key, also allow kms:GenerateDataKey and kms:Decrypt on that key.
Bucket layout¶
S3 has keys, not folders; the adapter follows the AWS console convention:
/photos/cat.jpgin the browser is the object<prefix>/photos/cat.jpg.- Creating a folder writes an empty
<prefix>/photos/object. Folders that exist only because objects sit under them are listed too. - Thumbnails go to
<prefix>/photos/_thumbs/cat.jpg, as on disk; the_thumbsfolder is hidden from listings. - Rename and move are copy plus delete. Moving a folder copies its objects concurrently (16 at a time) and deletes the source only after every copy succeeded, so a failure leaves the source intact.
- Removing a folder deletes every key under it, 1000 per request.
Objects uploaded by other tools appear when they are under the prefix and their extension is in extensions.
Thumbnails cost one GetObject and one PutObject per image on the first listing of a folder (at most safeThumbsCountInOneTime per request). Set createThumb: false on the source for large buckets or when a CDN resizes images.
Limits¶
- Objects over 8 MB are copied in parts (multipart copy), so renaming and moving work for objects of any size; metadata such as
Content-Typeis kept. - Signed URLs are not generated; see Private buckets.
- Uploads and
fileUploadRemotekeep at most 1 MB of a file in memory, the rest goes to a temporary file, and reach S3 in parts;fileDownloadstreams in 64 KiB chunks. The upload size is bounded bymaxUploadFileSize.
Troubleshooting¶
AccessDeniedon listings3:ListBucketmust be granted on the bucket ARN (notbucket/*) and thes3:prefixcondition must match the prefix.- Files upload but do not show in the editor
- Open
baseurl+ a file name in a browser. A403there means the bucket policy or the CDN is missing. - MinIO or Ceph answers
NoSuchBucketor301 - Set
forcePathStyle: true; otherwise boto3 addressesbucket.minio:9000, which does not resolve. With Ceph RGW, also use the host name the gateway is configured for (rgw_dns_nameor the zonegroup host names): requests for other names can be read as bucket names. - Wrong region
- AWS answers
PermanentRedirectnaming the right region; setregionto it. - Two sources on one bucket
- Give them different prefixes, or each sees the other's
_thumbsfolder as data.