# Cloud Image Uploader Cloud Image Uploader is a desktop-first Obsidian plugin that uploads pasted and dropped attachments to Alibaba Cloud OSS and inserts the resulting Markdown embed at the cursor. [简体中文](README.zh-CN.md) ## Features - Paste images directly into a note and upload them to OSS. - Drag images, video, audio, or PDF files into the editor. - Keep files in the local vault instead of OSS when desired. - Configurable object paths such as `obsidian/{year}/{month}/{day}/{filename}`. - Original, timestamped, random, and UUID filename strategies. - Public OSS URLs, custom-domain/CDN URLs, and temporary signed URLs. - Multipart upload progress for large files and exponential retry for transient failures. - Safe duplicate handling: rename, overwrite, or skip. - Migrate local image embeds in the current note without deleting files by default. - Per-note frontmatter overrides. - Obsidian Keychain integration: plugin data stores only secret references. - Optional last-reference cleanup for plugin-managed OSS objects, with a full-vault safety check. - Deleted notes retain their unreferenced cloud objects for 30 days by default before a second safety check and cleanup. ## Installation ### Manual installation 1. Download `cloud-image-uploader.zip` from the release. 2. Extract the `cloud-image-uploader` folder into `/.obsidian/plugins/`. 3. In Obsidian, open **Settings → Community plugins**. 4. Reload installed plugins and enable **Cloud Image Uploader**. The plugin folder must contain `main.js`, `manifest.json`, and `styles.css`. ## Alibaba Cloud OSS setup 1. Create an OSS bucket in the region closest to you. 2. Create a dedicated RAM user or obtain temporary STS credentials. Do not use the Alibaba Cloud root-account AccessKey. 3. Grant only the object permissions needed by the configured prefix: `oss:PutObject`, `oss:GetObject`, and optionally `oss:DeleteObject` for cleanup. The connection test uploads a tiny object and then tries to delete it. 4. Decide how readers will access uploads: - Public-read objects/bucket and **Public OSS URL**. - A bound CDN/custom domain and **Custom domain**. - A private bucket and **Temporary signed URL** (links expire, so this is not ideal for permanent notes). 5. Enter the AccessKey ID, secret, region (for example `oss-cn-hangzhou`), and bucket name in plugin settings. 6. Select **Test connection**. ## Remote cleanup Enable **Delete remote file when its last link is removed** only after granting `oss:DeleteObject` for the configured object prefix. This operation is irreversible on OSS. - The plugin waits five seconds after a link edit, then scans every Markdown note. It deletes only a remote object previously uploaded and registered by this plugin, and only when no reference remains. - Deleting an entire note does not delete its cloud files immediately. They enter a persisted retention queue for 30 days by default. At expiry—or the next time Obsidian opens after expiry—the vault is scanned again before deletion. - Restoring the note or referencing the same URL elsewhere prevents deletion. A scan or network failure also leaves the object intact for a later check. - Tracking starts with uploads made by version 0.2.0; older OSS objects are intentionally not adopted automatically. - OSS folders are key prefixes rather than normal directories. If the plugin did not create a folder-marker object, deleting the last object makes the empty date prefix disappear automatically. The desktop build uses the Node.js transport from `ali-oss`, so browser CORS rules are normally not involved. If a proxy, custom endpoint, or future mobile/browser build changes the transport, configure OSS CORS for the exact origin and required methods instead of using a broad wildcard policy. ## Target path The default target is: ```text obsidian/{year}/{month}/{day}/{filename} ``` On 2026-09-08, `figure.png` becomes an object such as: ```text obsidian/2026/09/08/20260908123456-a1b2c3.png ``` Supported tokens include `{year}`, `{month}`, `{day}`, `{hour}`, `{minute}`, `{second}`, `{filename}`, `{basename}`, `{ext}`, `{originalName}`, `{noteName}`, `{random}`, and `{uuid}`. ## Per-note overrides Credentials are never read from note properties. The following frontmatter can override behavior for one note: ```yaml --- cloudImageUploader: true uploadOnPaste: true uploadOnDrag: true uploadTarget: aliyun remotePath: research/{year}/{month}/{filename} --- ``` Use `cloudUpload: false` to disable automatic uploading for a note. ## Commands - Upload image from clipboard - Upload local file - Upload all local images in current note - Test OSS connection - Copy last uploaded URL - Toggle automatic upload ## Security - Use a dedicated, least-privilege RAM user or short-lived STS credentials. - AccessKey Secret and STS token are stored through Obsidian SecretStorage (Keychain); `data.json` contains only their secret IDs. - Debug logs redact fields whose names indicate secrets, tokens, credentials, authorization data, or signed URLs. - Temporary signed URLs contain an expiring access signature. They are inserted into the note and recent-upload history by design; treat both as sensitive until the link expires. - `data.json`, `.env*`, and release output are ignored by Git. - Never place credentials in note frontmatter or source code. - Automatic remote cleanup requires delete permission and permanently removes qualifying objects after its checks and retention rules. - Please report suspected vulnerabilities privately as described in [SECURITY.md](SECURITY.md). ## Network and privacy disclosure - The plugin requires an Alibaba Cloud account and sends only attachments you paste, drop, select, or explicitly migrate to the OSS bucket you configure. - Alibaba Cloud OSS usage may incur storage, request, and network-transfer charges under your Alibaba Cloud plan. - The plugin has no telemetry, analytics, advertising, or developer-operated relay service. - Local-vault fallback writes only inside the active vault. Migration reads only attachment files referenced by the current note. ## FAQ **Upload succeeded but the image is not visible.** Check bucket/object read permission, custom-domain binding, HTTPS, and whether a signed URL has expired. **AccessDenied.** Confirm that the RAM identity can access the exact bucket and prefix. Automatic remote cleanup and connection-test cleanup require `oss:DeleteObject`. **SignatureDoesNotMatch.** Recheck the secret, region, endpoint, HTTPS choice, and system time. **NoSuchBucket.** The bucket name or region is wrong. **Why does the plugin save a local file after a cloud error?** Local fallback is enabled by default so a pasted image is not lost. It can be disabled in settings. More help is available in [Chinese troubleshooting](docs/TROUBLESHOOTING.zh-CN.md). ## Development ```bash npm ci npm run check npm run package ``` `npm run package` creates `release/cloud-image-uploader/` and `release/cloud-image-uploader.zip`. ## Credits The user experience was inspired by [S3 Image Uploader](https://github.com/jvsteiner/s3-image-uploader), an MIT-licensed Obsidian plugin. Cloud Image Uploader uses a new Alibaba OSS-first implementation and architecture. See [reference analysis](docs/reference-analysis.md). ## License MIT ## Support Cloud Image Uploader is free and open source. If it saves you time, you can optionally buy me a coffee through Alipay or WeChat Pay. Donations never unlock features and are not required to use the plugin.

Alipay payment QR code WeChat Pay QR code

[Open Alipay](https://qr.alipay.com/2m618186effokvetuaxts52) · [Open WeChat Pay](https://payapp.wechatpay.cn/sjt/qr/AQGSQDiSEAQO-eytF4GmHj_N)