Skip to main content

cloud-storage-web

Complete guide for CloudBase cloud storage using Web SDK (@cloudbase/js-sdk) - upload, download, temporary URLs, file management, and best practices.

Source facts

Repository
TencentCloudBase/CloudBase-AI-Toolkit
Last source activity
October 28, 2026 at 06:27
Detected SKILL.md language
English
Stars
1,151
Forks
148

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
cloud-storage-web
description
Complete guide for CloudBase cloud storage using Web SDK (@cloudbase/js-sdk) - upload, download, temporary URLs, file management, and best practices.
version
2.36.2
license
MIT
alwaysApply
false
## Sibling skills (local only) Sibling CloudBase skills ship beside this skill. Use local relative paths such as `../auth-tool-cloudbase/SKILL.md`. If a referenced sibling skill file is missing from this environment, ask the user to install the full CloudBase plugin (or the missing skill). Do **not** HTTP-fetch remote skill or protocol markdown into the agent context. # Cloud Storage Web SDK ## Activation Contract ### Use this first when - A browser or Web app must upload, download, or manage CloudBase storage objects through `@cloudbase/js-sdk`. - The request mentions `uploadFile`, `getTempFileURL`, `deleteFile`, or `downloadFile` in frontend code. ### Read before writing code if - The task is browser-side storage work but you still need to separate it from Mini Program storage, backend storage management, or static hosting deployment. - The request may be blocked by security domains or frontend auth. ### Then also read - Web login and identity -> `../auth-web-cloudbase/SKILL.md` - General Web app setup -> `../web-development/SKILL.md` - Direct storage management through MCP tools -> `../cloudbase-platform/SKILL.md` ### Do NOT use for - Mini Program file APIs. - Backend or agent-side direct storage management through MCP. - Static website hosting deployment via `manageHosting(action="upload")`. - Database operations. ### Common mistakes / gotchas - Uploading from browser code without configuring security domains. - Using this skill for static hosting instead of storage objects. - Mixing browser SDK upload flows with server-side file-management tasks. - Assuming temporary download URLs are permanent links. - Ignoring `STORAGE_NOT_EXIST`; it means the target storage bucket/resource is not ready, not that the browser upload code should fabricate a URL. - On local Vite or dev-server tasks, forgetting to whitelist the exact current browser `host:port` before testing `app.uploadFile()`. - Treating CloudBase PG / `pgstore` like the legacy NoSQL CloudBase storage. PG environments use a separate `pgstore` backend whose buckets are NOT auto-created from your old NoSQL bucket. If `pgstore` has no bucket, every upload returns `STORAGE_BUCKET_NOT_FOUND` and the SDK then issues `PUT https://undefined/` (visible in DevTools as `net::ERR_NAME_NOT_RESOLVED`). Treat bucket existence as a hard prerequisite, just like Supabase: in Supabase Storage every upload must target an already-created bucket; CloudBase PG follows the same model. ### Minimal checklist - Confirm the caller is a browser/Web app. - Initialize the Web SDK once. - Confirm CloudBase storage exists in the current environment before testing upload. Use available MCP management/query tools to inspect or create/select the storage bucket when the environment has no default bucket. **In a PG / pgstore environment, the legacy NoSQL bucket from `DescribeEnvs` does NOT count as a usable pgstore bucket; create one explicitly before any browser upload. The legacy NoSQL bucket itself is still fine for legacy `app.uploadFile()` flows that already target it — PG and NoSQL storage coexist; this skill applies to BOTH.** - Check security-domain/CORS requirements. - Pick the right storage method before coding. ### Local dev recipe When the app runs on a local browser origin and must upload files from the frontend: 1. Use `queryEnv` with `action="domains"` to inspect the current security-domain whitelist. 2. Convert the browser origin into the CloudBase whitelist entry format: - Browser origin `http://127.0.0.1:4173` -> whitelist entry `127.0.0.1:4173` - Browser origin `http://localhost:5173` -> whitelist entry `localhost:5173` 3. If the exact current host entry is missing, call `envDomainManagement` with `action="create"` and add that host entry before relying on `app.uploadFile()`. 4. If the runtime port may change between runs, do not assume any fixed default port list is sufficient. Re-check the actual browser origin you are really using for testing or final validation, then add that exact `host:port`. 5. Tell the user that security-domain changes may take a few minutes to propagate; poll `queryEnv(action="domains")` rather than blind-sleeping for a fixed long interval. 6. Only after that should you implement and test browser-side `app.uploadFile()` flows. If `app.uploadFile()` returns `STORAGE_NOT_EXIST`, stop editing frontend code and fix the environment-side storage resource first. Re-check the environment storage list, create or select an available bucket if the task allows it, then retry the same SDK upload flow. If the task uses browser-side file upload, treat this as a prerequisite rather than an optional cleanup. ### Bucket existence prerequisite (mandatory before any upload code) Just like Supabase Storage, CloudBase Storage requires the target bucket to exist before any client-side upload. This is true for both legacy CloudBase NoSQL storage (`STORAGE_NOT_EXIST`) and the newer PG / `pgstore` backend (`STORAGE_BUCKET_NOT_FOUND`). Mental model parity with Supabase: | Step | Supabase | CloudBase | | ---- | -------- | --------- | | Create bucket | `supabase.storage.createBucket('covers', { public: true })` (admin-side, with service role) | In PG mode, create a `storage.buckets` bucket through PG storage HTTP API / CLI / console / SQL on `storage.buckets` when appropriate. The browser SDK cannot create one. | | Upload | `supabase.storage.from('covers').upload('a.png', file)` | **PG 模式**: `app.storage.from('covers').upload('a.png', file)` — `from(bucketName)` 指定 pgstore 存储桶。<br>**非 PG 模式**: `app.storage.from().upload('covers/a.png', file)` — bucket 名作为路径第一段。| | Bucket missing error | `Bucket not found` | Browser sees `STORAGE_BUCKET_NOT_FOUND` (PG) or `STORAGE_NOT_EXIST` (NoSQL), then a follow-up `PUT https://undefined/` because the SDK still tries to PUT a missing `metadata.url`. | Required pre-upload steps in any task that needs browser uploads: 1. List existing buckets first. For PG / pgstore, the legacy NoSQL bucket (the `6d63-…-1409864723` shape returned by `DescribeEnvs.Storages[]`) is NOT a valid pgstore bucket — do not assume it works. 2. If no usable bucket exists for the upload target (e.g. `covers`), create one through the PG storage management surface BEFORE editing frontend upload code. Adding `covers` as a path prefix in code does not auto-create a bucket. 3. After creating the bucket, the upload pattern depends on environment: - **PG / pgstore**: `app.storage.from('covers').upload('<file>', file)` — bucket 名传入 `from()` - **Non-PG (NoSQL)**: `app.storage.from().upload('covers/<file>', file)` — bucket 名作为路径第一段 4. If you see `net::ERR_NAME_NOT_RESOLVED` going to `https://undefined/` in DevTools, that is the SDK reacting to a missing `metadata.url` field — almost always because the bucket does not exist or the SDK request was rejected upstream. Inspect the failed `POST .../v1/storages/get-objects-upload-info` response in DevTools first; the `code` field (e.g. `STORAGE_BUCKET_NOT_FOUND`, `STORAGE_CONTENT_LENGTH_REQUIRED`, `INVALID_PARAM`) tells you exactly what to fix. Do not silently swallow upload failures. If `uploadCoverImage()` rejects, the parent `createArticle()` MUST also reject — never proceed to `db.from(...).insert(...)` with a fabricated URL or a placeholder, and never let the UI show a success toast. ### ⚠️ PG mode upload: use `app.storage.from('bucket')`, NOT `app.uploadFile()` In PG / pgstore environments, use `app.storage.from('covers').upload(key, file)` for uploads and `app.storage.from('covers').createSignedUrl(path, expiresIn)` for getting access URLs. Do NOT use the legacy NoSQL APIs in PG mode: - ❌ `app.uploadFile()` — 这是旧 NoSQL 的上传 API - ❌ `app.getTempFileURL()` — 这是旧 NoSQL 的获取 URL 方式 - ❌ `app.storage.from().upload('covers/file', file)` — 没有传 bucket 名 Use instead: - ✅ `app.storage.from('covers').upload('file', file)` — PG 模式上传 - ✅ `app.storage.from('covers').createSignedUrl('file', 3600)` — 获取签名 URL(返回 `fullSignedURL` 字段) **Return shapes differ between modes (v3 SDK) — copy the right column:** | call | 传统模式 (`from()` 无参, `cloud://` fileID) | PG 模式 (`from('bucket')`, bucket 内对象名) | |---|---|---| | `upload(path, file)` | `{ data: { id, path, fullPath } }`;`upsert` 默认 **true** | `{ data: { id, ... } }`;`upsert` 默认 **false** | | `createSignedUrl(path, expiresIn)` | `await` → `{ data: { signedUrl } }` | `await` → `{ data: { fullSignedURL } }` | | `getPublicUrl(path)` | `await` → `{ data: { publicUrl } }` | **同步调用(不 await)** → `{ data: { publicUrl } }` | Source: [webv3/classic/storage.md](https://docs.cloudbase.net/api-reference/webv3/classic/storage.md) · [webv3/storage.md](https://docs.cloudbase.net/api-reference/webv3/storage.md)(raw markdown)。 ### PG mode URL resolution: 公开桶直链 vs 签名 URL | Bucket 类型 | URL 策略 | 代码 | |---|---|---| | 公开桶(`storage.buckets.public = true`) | 直链,无需登录态,可直接进 `<img src>` | `app.storage.from('covers').getPublicUrl('a.png')` → `{ data: { publicUrl } }` | | 私有桶 | 签名 URL,带过期时间 | `app.storage.from('covers').createSignedUrl('a.png', 3600)` | - 公开桶直链能否访问取决于 `storage.objects` 的 RLS SELECT 策略是否放行 anon —— 建桶 SQL 与策略模板见 `postgresql-development-cloudbase/references/storage-pg.md` "Public-read bucket template"。 - 展示层做 `onerror` 兜底(直链被策略拦下时降级到签名 URL),不要硬依赖单一取址流程。 - 业务表只存 bucket + key(或 SDK 解析出的最终 URL),不要在浏览器手工拼接 URL。 ### Post-bucket: storage RLS (mandatory in PG / pgstore environments) In **PG / pgstore** environments, storage access control is enforced through **PostgreSQL Row Level Security (RLS) on `storage.buckets` / `storage.objects`** — exactly like Supabase Storage. These tables are already granted to `anon`, `authenticated`, and `service_role`; RLS is the permission gate. Traditional storage permission labels (`READONLY` / `PRIVATE` / `CUSTOM`) and JSON storage safe rules do not apply. The default RLS policy is deny all, so even if the bucket exists, `app.storage.from('covers').upload()` from a browser will fail with `STORAGE_PERMISSION_DENIED` unless you configure policies. Use `managePgDatabase(action="execute", confirm=true)` to run the following SQL after creating the bucket: ```sql ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY; -- Allow authenticated users to upload files CREATE POLICY "authenticated_upload" ON storage.objects FOR INSERT TO authenticated WITH CHECK (true); -- Allow authenticated users to read/download files CREATE POLICY "authenticated_read" ON storage.objects FOR SELECT TO authenticated USING (true); -- Optional: allow users to update/delete their own files CREATE POLICY "users_manage_own" ON storage.objects FOR UPDATE TO authenticated USING (auth.uid() = owner_id) WITH CHECK (auth.uid() = owner_id); ``` Key points: - `storage.objects` RLS is **separate** from CloudBase legacy NoSQL storage security rules (`managePermissions` / `ModifyStorageSafeRule`). In PG mode, always configure storage RLS via PG SQL, not the legacy security rule API. - Without these policies, the browser receives `STORAGE_PERMISSION_DENIED` when calling `app.storage.from('covers').upload()` in PG mode. - Use `IF NOT EXISTS` in a `DO $$` block when re-applying to avoid "policy already exists" errors on re-run. ## Overview Use this skill for **browser-side cloud storage operations** through the CloudBase Web SDK. Typical tasks: - upload files from a browser - generate temporary download URLs - delete files - trigger browser downloads ## SDK initialization Init reference: [webv3/initialization.md](https://docs.cloudbase.net/api-reference/webv3/initialization.md) ```javascript import cloudbase from "@cloudbase/js-sdk"; const app = cloudbase.init({ env: "your-env-id", accessKey: import.meta.env.VITE_PUBLISHABLE_KEY, // publishable key — auto-provisioned, see below }); ``` Initialization rules: - **Publishable key auto-provisioning (do not ask the user):** browser-side storage requests are data-plane calls that require a publishable `accessKey`. Before writing client code, call `queryAppAuth(action="getPublishableKey")`; if empty, call `manageAppAuth(action="ensurePublishableKey")`; then write the key to `.env.local` as `VITE_PUBLISHABLE_KEY` (create the file if missing) and read it via `import.meta.env.VITE_PUBLISHABLE_KEY`. Never hardcode the key into source files. Only fall back to the console (`https://tcb.cloud.tencent.com/dev?envId={env}#/env/apikey`) if both MCP calls fail. - Use synchronous initialization with a shared app instance. - Do not re-initialize in every component. - If the operation depends on user identity, handle auth before storage operations. ## Method routing - Upload from browser -> `app.uploadFile()` - Temporary preview/download URL -> `app.getTempFileURL()` - Delete existing files -> `app.deleteFile()` - Trigger browser download -> `app.downloadFile()` ## Upload ```javascript const result = await app.uploadFile({ cloudPath: "uploads/avatar.jpg", filePath: selectedFile }); ``` ### Upload rules - `cloudPath` must include the filename. - Use `/` to create folder structure. - **In a CloudBase PG / pgstore environment**, the `from(bucketName)` argument is used as the bucket name (e.g. `from('covers')`), and `upload(key, file)` takes a key without bucket prefix. The bucket must already exist. Same model as Supabase Storage — never upload into a not-yet-created bucket. - Validate file type and size before upload. - Show upload progress for larger files when UX matters. - On local dev origins, confirm the exact frontend origin already exists in environment security domains before assuming the upload path is usable. - Match against the whitelist entry format returned by `queryEnv(action="domains")`, which is typically `host:port` instead of a full `http://...` URL. - If the environment has no storage bucket or the SDK returns `STORAGE_NOT_EXIST` / `STORAGE_BUCKET_NOT_FOUND`, use CloudBase management/MCP storage tools to create or choose a bucket before retrying. Do not treat this as a successful optional upload. - After `app.uploadFile()` succeeds, do **not** fabricate a public-looking URL by concatenating `envId`, bucket domain, or `cloudPath`. Use the returned `fileID` with `app.getTempFileURL()` and store or display the SDK-resolved URL instead. ### Progress example ```javascript await app.uploadFile({ cloudPath: "uploads/avatar.jpg", filePath: selectedFile, onUploadProgress: ({ loaded, total }) => { const percent = Math.round((loaded * 100) / total); console.log(percent); } }); ``` ## Temporary URLs ```javascript const result = await app.getTempFileURL({ fileList: [ { fileID: "cloud://env-id/uploads/avatar.jpg", maxAge: 3600 } ] }); ``` Use temp URLs when the browser needs to preview or download private files without exposing a permanent public link. Typical upload + preview flow: ```javascript const uploadResult = await app.uploadFile({ cloudPath: "uploads/avatar.jpg", filePath: selectedFile }); const tempUrlResult = await app.getTempFileURL({ fileList: [{ fileID: uploadResult.fileID, maxAge: 3600 }] }); const previewUrl = tempUrlResult.fileList?.[0]?.tempFileURL || tempUrlResult.fileList?.[0]?.download_url; if (!previewUrl) { throw new Error("Failed to resolve temporary file URL after upload"); } ``` ## Delete files ```javascript await app.deleteFile({ fileList: ["cloud://env-id/uploads/old-avatar.jpg"] }); ```
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub