| name | platform-api |
| description | Platform API reference for DenchClaw apps — UI integration, per-app storage, HTTP proxy, real-time events, inter-app messaging, cron scheduling, webhooks, clipboard, context, and widget mode. |
| metadata | {"openclaw":{"inject":true,"always":true,"emoji":"⚡"}} |
App Platform API
This skill documents the platform-level APIs available to DenchClaw apps. For core app structure, see the parent app-builder skill. For data/objects, see data-builder. For AI chat, see agent-builder.
UI Integration (ui permission required)
await dench.ui.toast("Record saved successfully", { type: "success" });
await dench.ui.toast("Something went wrong", { type: "error" });
await dench.ui.toast("Processing...", { type: "info" });
await dench.ui.navigate("/people");
await dench.ui.navigate("/apps/my-app.dench.app");
await dench.ui.openEntry("people", "entry_id_here");
await dench.ui.setTitle("My App — 5 results");
const confirmed = await dench.ui.confirm("Delete this record?");
if (confirmed) {
}
const name = await dench.ui.prompt("Enter a name:", "Default Name");
if (name !== null) {
}
Per-App KV Store (store permission required)
Persistent key-value storage scoped to each app. Data survives app reloads and is stored in the workspace.
await dench.store.set("lastQuery", { sql: "SELECT * FROM people", timestamp: Date.now() });
await dench.store.set("theme", "custom-dark");
await dench.store.set("counter", 42);
const lastQuery = await dench.store.get("lastQuery");
await dench.store.delete("lastQuery");
const keys = await dench.store.list();
await dench.store.clear();
Storage is backed by a JSON file at {workspace}/.dench-app-data/{appName}/store.json. Each app gets its own isolated namespace.
HTTP Proxy (http permission required)
Make HTTP requests from apps without CORS restrictions. Requests are proxied through the DenchClaw server.
const data = await dench.http.fetch("https://api.example.com/data");
const result = await dench.http.fetch("https://api.example.com/submit", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer sk-...",
},
body: JSON.stringify({ query: "test" }),
});
console.log(result.status);
console.log(result.body);
Security: requests to localhost, private IPs, and internal DenchClaw URLs are blocked.
Real-time Events (events permission required)
Subscribe to workspace events for live updates.
dench.events.on("theme.changed", (data) => {
document.body.className = data.theme;
});
dench.events.on("object.entry.created", (data) => {
console.log(`New entry in ${data.objectName}: ${data.entryId}`);
refreshList();
});
dench.events.on("object.entry.updated", (data) => {
console.log(`Entry ${data.entryId} updated in ${data.objectName}`);
});
dench.events.on("object.entry.deleted", (data) => {
console.log(`Entry ${data.entryId} deleted from ${data.objectName}`);
});
dench.events.on("app.visible", () => {
resumeAnimations();
});
dench.events.on("app.hidden", () => {
pauseAnimations();
});
dench.events.on("file.changed", (data) => {
console.log(`File changed: ${data.path}`);
});
dench.events.off("theme.changed");
Context (no permission required)
const workspace = await dench.context.getWorkspace();
const app = await dench.context.getAppInfo();
Inter-App Messaging (apps permission required)
Apps can communicate with other open apps for composite workflows.
await dench.apps.send("analytics-dashboard.dench.app", {
action: "refresh",
filter: { status: "Active" },
});
dench.apps.on("message", (event) => {
console.log(`Message from ${event.from}:`, event.message);
if (event.message.action === "refresh") {
reloadData(event.message.filter);
}
});
const activeApps = await dench.apps.list();
Cron Scheduling (cron permission required)
Schedule recurring tasks that send messages to the agent.
const { jobId } = await dench.cron.schedule({
expression: "0 9 * * *",
message: "Generate the daily sales report and save it to workspace",
channel: "announce",
});
const jobs = await dench.cron.list();
await dench.cron.run(jobId);
await dench.cron.cancel(jobId);
Webhooks (webhooks permission required)
Receive external HTTP webhooks inside your app.
const hook = await dench.webhooks.register("github-push");
console.log(hook.url);
dench.webhooks.on("github-push", (payload) => {
console.log("Received webhook:", payload);
processGithubPush(JSON.parse(payload.body));
});
const events = await dench.webhooks.poll("github-push", { since: lastTimestamp });
Clipboard (clipboard permission required)
await dench.clipboard.write("Copied text content");
const text = await dench.clipboard.read();
Note: clipboard operations are proxied through the parent DenchClaw window.
Widget Mode
Apps can render as compact widgets in a dashboard grid instead of full-page tabs.
Manifest Configuration
name: "Quick Stats"
display: "widget"
widget:
width: 2
height: 1
refreshInterval: 60
permissions:
- database
Widget Design Guidelines
- Keep the UI compact — widgets have limited space
- Use large, readable numbers and minimal text
- Avoid scroll bars — all content should be visible
- Support both light and dark themes
- Use the refresh interval for auto-updating data
Widget Example
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, sans-serif;
padding: 16px;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
}
body.dark {
background: #0f0f1a;
color: #e8e8f0;
}
body.light {
background: #fff;
color: #1a1a2e;
}
.metric {
font-size: 48px;
font-weight: 700;
}
.label {
font-size: 13px;
opacity: 0.6;
margin-bottom: 4px;
}
</style>
</head>
<body>
<div class="label">Total Records</div>
<div class="metric" id="count">—</div>
<script>
async function init() {
const theme = await dench.app.getTheme().catch(() => "dark");
document.body.className = theme;
const result = await dench.db.query("SELECT SUM(entry_count) as total FROM objects");
document.getElementById("count").textContent = result.rows[0]?.total ?? 0;
}
init();
</script>
</body>
</html>
Widget-mode apps appear in the DenchClaw dashboard view alongside other widgets, arranged in a responsive grid.
Patterns
Multi-App Dashboard
Build a dashboard that aggregates data from multiple widget apps:
async function loadWidgetData() {
const apps = await dench.apps.list();
const widgets = apps.filter((a) => a.manifest.display === "widget");
for (const widget of widgets) {
await dench.apps.send(widget.name, { action: "getData" });
}
}
dench.apps.on("message", (event) => {
if (event.message.action === "dataResponse") {
updateDashboardPanel(event.from, event.message.data);
}
});
External API Integration
async function loadWeather(city) {
const result = await dench.http.fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_KEY`,
);
if (result.status === 200) {
const weather = JSON.parse(result.body);
displayWeather(weather);
}
}
Automation Workflow
async function setupAutomation() {
const { jobId } = await dench.cron.schedule({
expression: "0 */6 * * *",
message: "Check the tasks object for overdue items and send a summary to Telegram",
});
await dench.store.set("automationJobId", jobId);
dench.ui.toast("Automation scheduled every 6 hours", { type: "success" });
}