Guides

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 uploaderS3 protocol
Client needs toSend a multipart POST and read the link from a JSON responseUpload to S3-compatible storage (SigV4 signing, path-style addressing)
Typical clientsPicGo with the web-uploader plugin, PicList "advanced custom" uploaderPicList's built-in AWS S3, PicGo with the s3 plugin, aws CLI, rclone, S3 SDKs
CredentialThe upload token itselfAccess key and secret derived from an upload token
Returned linkThe server returns /i/<short ID>The client builds /o/<user id>/<object key> from the key
LifetimeCan be set per uploadDefaults only
EXIF processingPolicy can be set per upload; the global default otherwiseNone; bytes are stored as uploaded
Idempotency keySupportedNot supported
Uploading the same name againAlways a new imageSame 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 fieldConfig keyValue
API地址 (API URL)urlhttps://img.example.com/api/upload
POST参数名 (POST field name)paramNamefile
JSON路径 (JSON path)jsonPathurl
自定义请求头 (custom headers)customHeader{"Authorization": "Bearer imgy_xxxxxxxx"}
自定义Body (custom body)customBodyOptional 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 fieldExampleNotes
Access key IDimgy_AbCdEfGThe token's identifying prefix (12 characters)
Secret access key64 hex charactersDerived from the master key for this token and never stored; you can come back and view it any time
BucketimagesThere are no buckets; any bucket name is accepted, and the bucket name is not part of the object key
Regionus-east-1Regions are not distinguished; any value is accepted
Custom endpointhttps://img.example.com/s3The trailing /s3 is the protocol prefix and must stay
Custom domainhttps://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

OperationRequestBehavior
PutObjectPUT /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
HeadBucketHEAD /s3/<bucket>200 whenever the credential is valid
HeadObjectHEAD /s3/<bucket>/<object key>The object's metadata headers
GetObjectGET /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
DeleteObjectDELETE /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-SHA256 signatures in the Authorization header are accepted, and requests whose timestamp is more than 15 minutes off the server clock are rejected.
  • UNSIGNED-PAYLOAD is supported, and so are the aws-chunked streaming 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:

SituationStatusCode
Signature mismatch, unknown access key, token no longer valid403SignatureDoesNotMatch
Request time more than 15 minutes off403RequestTimeTooSkewed
Authorization header missing or unparsable400AuthorizationHeaderMalformed
Signature is not AWS4-HMAC-SHA256400InvalidRequest
Over the per-image size limit400EntityTooLarge
Not a supported image, or over the pixel budget400InvalidRequest
Storage quota full403QuotaExceeded
Object key too long / with invalid characters / missing400KeyTooLongError / InvalidArgument / InvalidRequest
Object does not exist, or the image it points to has expired404NoSuchKey
No healthy channel to write to during upload, or fewer synchronous copies than the minimum succeeded503ServiceUnavailable
Object temporarily unreadable (GET / HEAD)503ServiceUnavailable
Any other server error500InternalError

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: default for 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-id header 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 keyValue
accessKeyIDAccess key ID from the dialog
secretAccessKeySecret access key from the dialog
bucketNameimages
uploadPathObject key template, e.g. {year}/{month}/{md5}.{extName}
regionus-east-1
endpointhttps://img.example.com/s3
pathStyleAccessOn
urlPrefixhttps://img.example.com/o/<user id>
disableBucketPrefixToURLOn. 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 keyValue
accessKeyID / secretAccessKeyAccess key ID and Secret access key from the dialog
bucketNameimages
uploadPathe.g. {year}/{month}/{md5}.{extName}
regionus-east-1
endpointhttps://img.example.com/s3
pathStyleAccessOn (labelled ForcePathStyle in the UI)
outputURLPatternhttps://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 with picgo install s3 (or picgo install web-uploader), then write the config:
~/.picgo/config.json
{
  "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

~/.aws/credentials
[imgyoung]
aws_access_key_id = imgy_AbCdEfG
aws_secret_access_key = <secret>
~/.aws/config
[profile imgyoung]
region = us-east-1
s3 =
  addressing_style = path
  multipart_threshold = 64MB
Terminal
# 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.png
  • aws s3 cp switches to multipart upload for files above multipart_threshold (8 MB by default), and multipart upload is not supported. Raise it above the per-image size limit, or use aws s3api put-object for a single-request upload.
  • aws s3 ls and aws s3 sync rely on listing objects and do not work.

The resulting link is https://img.example.com/o/<user id>/blog/2026/photo.png.

rclone

Terminal
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.png
  • no_check_bucket=true makes 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 copyto and deletefile. ls, sync and directory-level copy rely 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.

On this page