
Makers Storage
- 30 installs
- 2k repo stars
- Updated July 16, 2026
- tencentedgeone/edgeone-makers-tools
Use when working on frontend tasks.
About
Makers Storage is a skill that helps with frontend work. It supports teams during the build phase of development. Use this skill to improve your frontend processes and deliverables.
- Makers
- Storage
Makers Storage by the numbers
- 30 all-time installs (skills.sh)
- Ranked #416 of 782 Skill Development skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
npx skills add https://github.com/tencentedgeone/edgeone-makers-tools --skill makers-storageAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 30 |
|---|---|
| repo stars | ★ 2k |
| Last updated | July 16, 2026 |
| Repository | tencentedgeone/edgeone-makers-tools ↗ |
What it does
Use when working on frontend tasks.
Files
KV Storage
EdgeOne Pages KV is a globally distributed key-value persistent storage service deployed across multiple edge nodes. Data follows an eventual consistency model and synchronizes globally within 60 seconds.
⚠️ KV Storage is only available in Edge Functions — NOT supported in Node Functions.
Prerequisites (MUST complete before using KV)
You must enable KV Storage in the EdgeOne Pages console before writing any code.
Step 1 — Enable KV Storage
1. Log in to the EdgeOne Pages console 2. Navigate to the "KV Storage" page 3. Click "Apply Now" to activate (free tier includes 1 GB storage)
Step 2 — Create a Namespace
A namespace is the unit of data isolation (like a separate database):
1. On the "KV Storage" page, click "Create Namespace" 2. Enter a name (e.g. my-kv-store) 3. Wait for creation to complete
One account can create up to 10 namespaces.
Step 3 — Bind Namespace to Project
After creating a namespace, bind it to your EdgeOne Pages project and assign a variable name.
Option A — From KV Storage page: 1. Open the namespace → "Associated Projects" tab 2. Click "Bind Project" 3. Select project and set the variable name (e.g. my_kv)
Option B — From Project settings: 1. Open project details → "KV Storage" menu 2. Click "Bind Namespace" 3. Select the namespace and set the variable name (e.g. my_kv)
The variable name becomes a global variable in your Edge Function code. Different namespaces can use different variable names.
---
Core Concept: KV is a Global Variable
⚠️ CRITICAL: The KV namespace is accessed as a global variable — it is NOT on context.env.// ❌ WRONG — KV is NOT in context.env
export async function onRequest(context) {
const KV = context.env.KV;
await KV.get('key');
}
// ✅ CORRECT — my_kv is a global variable (name set when binding)
export async function onRequest(context) {
const value = await my_kv.get('key');
}---
API Reference
All methods are called on the global KV variable (e.g. my_kv).
put — Write data
put(key: string, value: string | ArrayBuffer | ArrayBufferView | ReadableStream): Promise<void>key: Key name (≤ 512 bytes, alphanumeric and underscores only)value: Value (≤ 25 MB)- Returns
Promise<void>— alwaysawaitto confirm write
await my_kv.put('count', '100');
await my_kv.put('user', JSON.stringify({ name: 'Alice' }));get — Read data
get(key: string, type?: 'text' | 'json' | 'arrayBuffer' | 'stream'): Promise<string | object | ArrayBuffer | ReadableStream | null>typedefaults to'text'; use'json'to auto-deserialize- Returns
nullif key does not exist
const count = await my_kv.get('count'); // '100' (string)
const user = await my_kv.get('user', 'json'); // { name: 'Alice' }
const buf = await my_kv.get('file', 'arrayBuffer');delete — Remove data
delete(key: string): Promise<void>await my_kv.delete('count');list — Enumerate keys
list(options?: { prefix?: string; limit?: number; cursor?: string }): Promise<ListResult>ListResult:
{
complete: boolean; // true if all keys have been returned
cursor: string; // pagination cursor for next page
keys: Array<{ name: string }>;
}Single page:
const result = await my_kv.list({ prefix: 'user:' });
// result.keys → [{ name: 'user:123' }, { name: 'user:456' }]Paginate through all keys:
let allKeys = [];
let result;
let cursor;
do {
result = await my_kv.list({ prefix: 'user:', limit: 256, cursor });
allKeys.push(...result.keys);
cursor = result.cursor;
} while (!result.complete);---
Examples
Page view counter
// edge-functions/api/counter.js
export async function onRequest({ request }) {
// my_kv is a global variable — NOT context.env.my_kv
let count = await my_kv.get('page_views');
count = count ? Number(count) + 1 : 1;
await my_kv.put('page_views', String(count));
return new Response(JSON.stringify({ views: count }), {
headers: { 'Content-Type': 'application/json' },
});
}CRUD with KV
// edge-functions/api/users/[id].js
export async function onRequestGet({ params }) {
const user = await my_kv.get(`user:${params.id}`, 'json');
if (!user) {
return new Response(JSON.stringify({ error: 'Not found' }), {
status: 404,
headers: { 'Content-Type': 'application/json' },
});
}
return new Response(JSON.stringify(user), {
headers: { 'Content-Type': 'application/json' },
});
}
export async function onRequestPost({ params, request }) {
const data = await request.json();
await my_kv.put(`user:${params.id}`, JSON.stringify(data));
return new Response(JSON.stringify({ success: true }), {
status: 201,
headers: { 'Content-Type': 'application/json' },
});
}
export async function onRequestDelete({ params }) {
await my_kv.delete(`user:${params.id}`);
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json' },
});
}List all keys with prefix
// edge-functions/api/users.js
export async function onRequestGet() {
const users = [];
let result;
let cursor;
do {
result = await my_kv.list({ prefix: 'user:', limit: 256, cursor });
const fetched = await Promise.all(
result.keys.map(k => my_kv.get(k.name, 'json'))
);
users.push(...fetched.filter(Boolean));
cursor = result.cursor;
} while (!result.complete);
return new Response(JSON.stringify({ users }), {
headers: { 'Content-Type': 'application/json' },
});
}---
Common Errors
| Symptom | Cause | Fix |
|---|---|---|
ReferenceError: my_kv is not defined | KV not enabled or namespace not bound | Enable KV in console → create namespace → bind to project |
Accessing context.env.KV returns undefined | KV is a global variable, not on context.env | Use my_kv.get(...) directly (global) |
get() returns a Promise object, not the value | Missing await | Always await KV operations |
| KV works in production but fails locally | Project not linked | Run edgeone pages link |
| KV not available in Node Functions | KV only works in Edge Functions | Move KV logic to Edge Functions, or use an external database in Node Functions |
---
Limits
| Resource | Limit |
|---|---|
| Storage per account (free tier) | 1 GB |
| Key length | ≤ 512 bytes |
| Value size | ≤ 25 MB |
| Namespaces per account | 10 |
| Consistency | Eventual (≤ 60 s global sync) |
| List results per call | 256 max |
| Supported runtime | Edge Functions only |
---
Best Practices
1. Use key prefixes to organize data: user:123, cart:456, config:theme 2. Handle null — get() returns null for missing keys 3. Batch reads with Promise.all() after list() instead of sequential awaits 4. Always wrap writes in try/catch for error handling 5. Serialize objects — use JSON.stringify() for put() and 'json' type for get()
---
Local Development
# 1. Link to a remote project (required for KV access)
edgeone pages link
# 2. Start dev server
edgeone pages dev
# 3. Test
curl http://localhost:8088/api/counterProduction Deployment
1. Enable KV Storage in the console 2. Create a namespace 3. Bind it to the project (set the variable name) 4. Deploy:
edgeone pages deploy---
Blob Storage
Distributed object storage for Makers Functions. Ideal for images, documents, user uploads, AI-generated content.
Limits: 1GB storage per free-tier account. Currently Node.js SDK only (@edgeone/pages-blob).Install
npm install @edgeone/pages-blobGet a Store
import { getStore } from "@edgeone/pages-blob";
// Inside Makers Functions (auto-configured)
const store = getStore("my-store");
// With strong consistency
const store = getStore({ name: "my-store", consistency: "strong" });Consistency Model
| Mode | Behavior | Use when |
|---|---|---|
"eventual" (default) | Edge-cached, fastest, data syncs in seconds | Content display, tolerate brief staleness |
"strong" | Bypasses cache, reads from primary storage | Counters, state machines, must read latest |
API Reference
| Method | Description |
|---|---|
store.set(key, value, opts?) | Write an object (string / ArrayBuffer / Blob / ReadableStream) |
store.setJSON(key, value, opts?) | Write JSON (auto-serialized) |
store.get(key, opts?) | Read an object (returns null if not found) |
store.getWithHeaders(key, opts?) | Read with response headers (etag, content-type) |
store.delete(key) | Delete an object |
store.list(opts?) | List objects (prefix filter, directory grouping, pagination) |
store.createUploadUrl(key, opts?) | Generate pre-signed PUT URL for client-side direct upload |
Write
await store.set("photos/cat.jpg", imageBuffer);
await store.set("notes/todo.txt", "Buy milk");
await store.setJSON("user/preferences", { theme: "dark", lang: "zh-CN" });
// Only write if key doesn't exist
await store.set("init.json", data, { onlyIfNew: true });Read
const text = await store.get("hello.txt"); // string
const json = await store.get("config.json", { type: "json" }); // parsed object
const buffer = await store.get("image.png", { type: "arrayBuffer" }); // ArrayBuffer
const stream = await store.get("large.zip", { type: "stream" }); // ReadableStream
// Strong consistency read
const fresh = await store.get("counter", { consistency: "strong" });type options: "text" (default) | "json" | "arrayBuffer" | "blob" | "stream"
List
// All objects
const { blobs } = await store.list();
// By prefix
const { blobs } = await store.list({ prefix: "photos/" });
// Directory grouping
const { blobs, directories } = await store.list({ prefix: "photos/", directories: true });
// Manual pagination
const page1 = await store.list({ paginate: false });
const page2 = await store.list({ paginate: false, cursor: page1.cursor });Client-Side Direct Upload
Generate a pre-signed URL for large file uploads without going through Functions:
const { url, key, expiresAt } = await store.createUploadUrl("files/photo.jpg", {
expireSeconds: 3600,
contentType: "image/jpeg",
});
// Client PUTs directly to `url`Delete
await store.delete("photos/cat.jpg"); // silent if key doesn't existList All Stores
import { listStores } from "@edgeone/pages-blob";
const { stores } = await listStores(); // [{ name: "my-store" }, ...]