Built-ins
An app imports these from app/std/*; manh.run provides the credentials through its gateway, so app code never holds manh.run’s keys.
Files — app/std/blob
Each app has its own private folder in manh.run’s storage.
import { blob } from "app/std/blob";
await blob.set("invoices/42.pdf", bytes, { contentType: "application/pdf" });
const res = await blob.get("invoices/42.pdf"); // Response
const meta = await blob.head("invoices/42.pdf"); // { key, size, contentType, lastModified } | null
const all = await blob.list("invoices/"); // every object under the prefix
await blob.delete("invoices/42.pdf");
blob.getUrl("public/products/ao-thun.jpg"); // permanent public link — only keys under public/
await blob.presignGet("contracts/7.pdf", { expiresIn: 3600 }); // expiring signed link, private files
await blob.presignPut("uploads/big.zip"); // the browser uploads straight to storageFiles under public/ are public (product images, avatars); every other key is private and reachable only through a signed link. Store the key on the record and build the link when you return it. Keys are relative (no leading /, no ..). The owner browses the folder in the workspace: Dữ liệu → File.
Secrets
API keys of other services (payment, SMS, an ERP token) are set by the owner in Cài đặt → Secrets or by the agent with the MCP tool env_set, and read with env.NAME. Values apply at once — the running app picks them up without a redeploy; agents can list names (env_list) but never read values. Never put a key in a file — check rejects strings that look like live keys.
$name store
For small side data (settings, counters, short lists) a free identifier starting with $ in a backend .ts file is a JSON value saved in the app’s database:
$settings ??= { currency: "VND" };
$counters ??= { orders: 0 };
$counters.orders++;Only the names a request touches are loaded; changes are saved at the end of the request, all or nothing (a thrown error or a 4xx/5xx answer saves nothing). Business objects stay in tables. The owner sees and edits each $name in Dữ liệu → JSON.
Email, AI, cron
app/std/email (email({ to, subject, html }) — sent as "<project name>" <no-reply@manh.run>, fromName overrides the name), app/std/openai (new OpenAI(), no key) and *.cron.ts jobs — see Cron jobs and the backend guide.
MCP tools — *.mcp.ts
A file app/<x>/<name>.mcp.ts is a tool of the app’s MCP server at https://<slug>.manh.run/mcp, so each user’s own agent (Claude, Cursor, ChatGPT) can work in the app as that user. Users create tokens on the app’s /mcp page (block mcp-access); the owner sees tools, calls and tokens in Vận hành → MCP.
// app/orders/approve_order.mcp.ts
import { z } from "zod";
export const description = "Duyệt một đề nghị mua đang chờ theo id. Chỉ quản lý.";
export const input = z.object({ id: z.number().int().positive() });
export const access = { roles: ["manager"] }; // "user" (default) | "public" (read-only tools) | { roles }
export default async function ({ id }, ctx) { // ctx.user = { id, role } of the token's owner
return approveRequest(id, ctx.user.id);
}Tokens are hashed, scoped (read / read+write), expiring and revocable; read tokens never see write tools; input is validated, output capped, calls logged and rate-limited. Connectors like Claude.ai and ChatGPT sign in with OAuth 2.1 instead of a pasted token — the app handles it (consent page uses the user’s app login, PKCE, rotating refresh tokens).
Inbound email — *.email.ts
A file app/<x>/<name>.email.ts receives mail sent to app+<slug>.<name>@in.manh.run (inboxAddress("<name>") from app/std/email returns it). The default export gets the parsed message — from, to, cc, subject, date, text, html, attachments[] (filename, contentType, size, content), headers.
// app/orders/orders.email.ts → app+<slug>.orders@in.manh.run
export default async function (mail) {
// anyone can send here: check mail.from.address before acting
await createOrderFromMail(mail.from.address, mail.subject, mail.text ?? "");
}Name: lowercase, digits, - (≤ 32), unique per app. Throwing bounces the mail to the sender; no retries — push slow work to a job. Max 20 MB.