Upload clients
Connect PicGo, PicList, Typora, the aws CLI, rclone and other clients to ImgYoung, either as a custom web uploader or over the S3 protocol.
Two ways to connect
| Custom web uploader | S3 protocol | |
|---|---|---|
| Client needs to | Send a multipart POST and read the link from a JSON response | Upload to S3-compatible storage (SigV4 signing, path-style addressing) |
| Typical clients | PicGo with the web-uploader plugin, PicList "advanced custom" uploader | PicList's built-in AWS S3, PicGo with the s3 plugin, aws CLI, rclone, S3 SDKs |
| Credential | The upload token itself | Access key and secret derived from an upload token |
| Returned link | The server returns /i/<short ID> | The client builds /o/<user id>/<object key> from the key |
| Lifetime | Can be set per upload | Defaults only |
| EXIF processing | Policy can be set per upload; the global default otherwise | None; bytes are stored as uploaded |
| Idempotency key | Supported | Not supported |
| Uploading the same name again | Always a new image | Same object key overwrites, S3 style |
Both end up in the same upload pipeline with the same format, size, quota and rate limit rules; see Uploading. Credentials always come from the Upload tokens page of the console. Issue one named token per client, so a leaked client can be cut off by revoking just its token.
Custom web uploader
The client POSTs the image as a multipart form to /api/upload with the file in the file field and an Authorization: Bearer <token> header, then takes the link from the url field of the JSON response.
url is absolute. If your public domain behind a reverse proxy or CDN differs from the host the server sees, set Public site address under Settings → Runtime settings first; otherwise the returned link points at the address inside the proxy.
PicGo: the web-uploader plugin
PicGo has no generic HTTP uploader of its own. Search for and install the web-uploader plugin (npm package picgo-plugin-web-uploader) in the plugin settings; a new uploader called "自定义Web图床" appears:
| Plugin field | Config key | Value |
|---|---|---|
| API地址 (API URL) | url | https://img.example.com/api/upload |
| POST参数名 (POST field name) | paramName | file |
| JSON路径 (JSON path) | jsonPath | url |
| 自定义请求头 (custom headers) | customHeader | {"Authorization": "Bearer imgy_xxxxxxxx"} |
| 自定义Body (custom body) | customBody | Optional extra form fields, e.g. {"ttl": "never", "exif_policy": "strip"} |
Write the values in customBody as strings.
PicList: advanced custom uploader
Fill in PicList's built-in "advanced custom" uploader with the same information: endpoint https://img.example.com/api/upload, method POST, file field name file, header Authorization: Bearer imgy_xxxxxxxx, and url as the JSON path of the link in the response.
To set a lifetime or EXIF policy, add ttl and exif_policy as extra form fields.
S3 protocol
ImgYoung implements a subset of the S3 protocol under /s3, and the credential is your upload token: revoke the token and this route stops working too.
The protocol lives under the /s3 prefix because S3 bucket paths are single segments at the site root, and mounting them at the root would take over the admin console's top-level routes.
Getting the S3 settings
On the Upload tokens page of the console, open More actions at the end of an active token's row and choose S3 client settings. The dialog lists everything you need to copy:
| Dialog field | Example | Notes |
|---|---|---|
| Access key ID | imgy_AbCdEfG | The token's identifying prefix (12 characters) |
| Secret access key | 64 hex characters | Derived from the master key for this token and never stored; you can come back and view it any time |
| Bucket | images | There are no buckets; any bucket name is accepted, and the bucket name is not part of the object key |
| Region | us-east-1 | Regions are not distinguished; any value is accepted |
| Custom endpoint | https://img.example.com/s3 | The trailing /s3 is the protocol prefix and must stay |
| Custom domain | https://img.example.com/o/<user id> | Used to build image links; contains your user id |
The endpoint and domain are built from the Public site address, or from the address you opened the console with if that setting is empty.
- Path-style addressing is required (requests look like
/s3/<bucket>/<object key>). A self-hosted server has no bucket subdomains, so turn on any "path style" or "ForcePathStyle" switch your client has. - You can only view the S3 settings of your own tokens; administrators cannot view other users' either.
- Rotating a token changes both the access key and the secret, so update the client with the new values. Changing the master key invalidates every S3 secret.
- If the token is revoked or expired, or its owner is disabled, signature verification always fails.
Supported operations
| Operation | Request | Behavior |
|---|---|---|
| PutObject | PUT /s3/<bucket>/<object key> | Runs the upload pipeline. The ETag response header is the SHA-256 of the uploaded body, and x-amz-meta-image-id carries the image's short ID |
| HeadBucket | HEAD /s3/<bucket> | 200 whenever the credential is valid |
| HeadObject | HEAD /s3/<bucket>/<object key> | The object's metadata headers |
| GetObject | GET /s3/<bucket>/<object key> | The stored original; no on-demand processing, not counted as an access, not subject to hotlink protection; same cache headers as /o/ aliases |
| DeleteObject | DELETE /s3/<bucket>/<object key> | Deletes the image the key currently points to; returns 204 even if the object does not exist |
Not supported: listing buckets and objects (ListBuckets, ListObjects), multipart upload, CopyObject, bucket creation, ACLs, presigned URLs (query string signing) and SigV2. These requests get a 4xx error.
ACL headers sent by a client do not cause an error and have no effect.
Signing and request bodies:
- Only
AWS4-HMAC-SHA256signatures in theAuthorizationheader are accepted, and requests whose timestamp is more than 15 minutes off the server clock are rejected. UNSIGNED-PAYLOADis supported, and so are theaws-chunkedstreaming bodies that aws CLI v2 and others use by default. When a payload hash is declared, the server checks it against the actual body.
Values used for an upload:
- The object key is the whole path after the bucket name (URL-decoded), at most 1024 bytes, with no control characters and no leading
/; it is unique within one user. - The file name is the last segment of the object key; the lifetime is the per-user default, then the global default.
- No EXIF processing is done and the global EXIF default does not apply: the stored bytes are exactly the uploaded bytes, so the ETag returned by PUT matches the one GetObject returns later.
Errors come back as S3-style XML, and clients act on the Code:
| Situation | Status | Code |
|---|---|---|
| Signature mismatch, unknown access key, token no longer valid | 403 | SignatureDoesNotMatch |
| Request time more than 15 minutes off | 403 | RequestTimeTooSkewed |
Authorization header missing or unparsable | 400 | AuthorizationHeaderMalformed |
Signature is not AWS4-HMAC-SHA256 | 400 | InvalidRequest |
| Over the per-image size limit | 400 | EntityTooLarge |
| Not a supported image, or over the pixel budget | 400 | InvalidRequest |
| Storage quota full | 403 | QuotaExceeded |
| Object key too long / with invalid characters / missing | 400 | KeyTooLongError / InvalidArgument / InvalidRequest |
| Object does not exist, or the image it points to has expired | 404 | NoSuchKey |
| No healthy channel to write to during upload, or fewer synchronous copies than the minimum succeeded | 503 | ServiceUnavailable |
| Object temporarily unreadable (GET / HEAD) | 503 | ServiceUnavailable |
| Any other server error | 500 | InternalError |
When the upload concurrency gate is full or the rate limit is hit, the response is 429 with a JSON body rather than XML.
Object key aliases
An image uploaded over S3 has, besides its short ID, an address based on its object key:
https://img.example.com/o/<user id>/<object key>- The user id is the immutable account identifier, not the username:
defaultfor the built-in administrator, and a random 10-character string for every other account. You can find it in the ID column of the Users page or in the Custom domain field of the S3 settings dialog. Object keys are only unique within a user, which is why the link needs it. It is the immutable ID rather than the username because usernames can change: after a rename, someone else could take the old name, and links shared earlier would then point at their images. /o/and/i/<short ID>are two entrances to the same image, with identical on-demand processing, format negotiation, hotlink protection and access counting; only caching differs, see below and Accessing images.- The short ID is in the
x-amz-meta-image-idheader of the PutObject response, and the image also shows up on the Images page.
Overwriting a key
A second PutObject to the same object key overwrites it, S3 style:
- The object key now points to the newly uploaded image, and the
/o/link shows the new image. - The old image is not deleted, since its short ID may already have been shared: the short ID link keeps working, and it keeps using storage and quota; delete it on the Images page if you no longer need it.
- DeleteObject only deletes the image the key currently points to.
Because object keys can be overwritten, responses for /o/ links (and S3 GetObject) carry Cache-Control: public, no-cache with a strong ETag: browsers and CDNs may cache them but must check with this server, sending the ETag, before each use, and get a 304 when nothing changed.
After an overwrite, the next check returns the new image. The short link /i/<short ID> always points to the same image and is still cached as immutable for a year.
Client configuration examples
Replace imgy_AbCdEfG, <secret> and <user id> below with the values from the S3 client settings dialog.
PicList: built-in AWS S3
PicList ships an "AWS S3" uploader. Fill it in by config key (the labels in the UI vary between versions):
| Config key | Value |
|---|---|
accessKeyID | Access key ID from the dialog |
secretAccessKey | Secret access key from the dialog |
bucketName | images |
uploadPath | Object key template, e.g. {year}/{month}/{md5}.{extName} |
region | us-east-1 |
endpoint | https://img.example.com/s3 |
pathStyleAccess | On |
urlPrefix | https://img.example.com/o/<user id> |
disableBucketPrefixToURL | On. Otherwise, with path-style addressing PicList inserts the bucket name into the link and produces an address that does not work |
PicGo: the s3 plugin
PicGo has no built-in S3 uploader. Install the s3 plugin (npm package picgo-plugin-s3) in the plugin settings, and an "Amazon S3" uploader appears.
For links, this plugin recommends its output URL template (outputURLPattern), in which {path} is the full object key after upload:
| Config key | Value |
|---|---|
accessKeyID / secretAccessKey | Access key ID and Secret access key from the dialog |
bucketName | images |
uploadPath | e.g. {year}/{month}/{md5}.{extName} |
region | us-east-1 |
endpoint | https://img.example.com/s3 |
pathStyleAccess | On (labelled ForcePathStyle in the UI) |
outputURLPattern | https://img.example.com/o/<user id>/{path} |
Using a content hash such as {md5} in the object key means re-uploading the same image yields the same key, and different images never overwrite each other.
Typora
Typora does not upload to S3 or a custom endpoint by itself; it hands images to PicGo. In Typora's preferences under Image, set the upload service to PicGo (app) or PicGo-Core (command line).
- With
PicGo (app), Typora calls the running PicGo app, so configure PicGo with either of the methods above. - With
PicGo-Core (command line), PicGo-Core reads~/.picgo/config.json. Install the plugin withpicgo install s3(orpicgo install web-uploader), then write the config:
{
"picBed": {
"uploader": "aws-s3",
"current": "aws-s3",
"aws-s3": {
"accessKeyID": "imgy_AbCdEfG",
"secretAccessKey": "<secret>",
"bucketName": "images",
"uploadPath": "{year}/{month}/{md5}.{extName}",
"region": "us-east-1",
"endpoint": "https://img.example.com/s3",
"pathStyleAccess": true,
"outputURLPattern": "https://img.example.com/o/<user id>/{path}"
}
},
"picgoPlugins": {
"picgo-plugin-s3": true
}
}aws CLI
[imgyoung]
aws_access_key_id = imgy_AbCdEfG
aws_secret_access_key = <secret>[profile imgyoung]
region = us-east-1
s3 =
addressing_style = path
multipart_threshold = 64MB# Upload
aws --profile imgyoung --endpoint-url https://img.example.com/s3 \
s3 cp ./photo.png s3://images/blog/2026/photo.png
# Read metadata and delete
aws --profile imgyoung --endpoint-url https://img.example.com/s3 \
s3api head-object --bucket images --key blog/2026/photo.png
aws --profile imgyoung --endpoint-url https://img.example.com/s3 \
s3 rm s3://images/blog/2026/photo.pngaws s3 cpswitches to multipart upload for files abovemultipart_threshold(8 MB by default), and multipart upload is not supported. Raise it above the per-image size limit, or useaws s3api put-objectfor a single-request upload.aws s3 lsandaws s3 syncrely on listing objects and do not work.
The resulting link is https://img.example.com/o/<user id>/blog/2026/photo.png.
rclone
rclone config create imgyoung s3 \
provider=Other \
access_key_id=imgy_AbCdEfG \
secret_access_key=<secret> \
endpoint=https://img.example.com/s3 \
region=us-east-1 \
force_path_style=true \
no_check_bucket=true
rclone copyto ./photo.png imgyoung:images/blog/2026/photo.pngno_check_bucket=truemakes rclone skip its bucket check before uploading; the server does not support creating buckets.- Stick to commands that act on a single object, such as
copytoanddeletefile.ls,syncand directory-levelcopyrely on listing objects and do not work.
Other S3 SDKs
Any S3 SDK works with the same settings: endpoint https://img.example.com/s3, path-style addressing on, any region (such as us-east-1), only PutObject / HeadObject / GetObject / DeleteObject, and no automatic switch to multipart upload.
Uploading
Upload images through the HTTP API, URL fetch or the admin console, plus format and size limits, EXIF handling, idempotency keys, how duplicates are handled and what a successful upload guarantees.
Accessing images
The two forms of image links, caching and conditional requests, on-demand resizing and WebP negotiation, the proxy and 302 access modes, hotlink protection, and how accesses are counted.