Build With DenchClaw: Developer Resources and APIs
Developer guide to building with DenchClaw—the Bridge API, Dench Apps, DuckDB direct access, webhooks, Action fields, and skill creation for the platform.
Build With DenchClaw: Developer Resources and APIs
DenchClaw is designed to be extended. The platform ships with a powerful CRM, browser automation, and AI agent — but for developers, the real value is in what you can build on top of it.
This guide covers everything you need to know to build with DenchClaw: the Bridge API for Dench Apps, direct DuckDB access, webhook handling, Action fields, and skill creation. Whether you're building a custom dashboard, an integration with an external API, or a full-featured business application, this is where to start.
The DenchClaw Platform Architecture#
Understanding what you're building on:
- DuckDB — the local database where all CRM data lives. EAV schema with PIVOT views (
v_people,v_companies, etc.) - OpenClaw — the underlying agent framework. Handles sessions, channels, routing
- The Agent — reads skills, executes tools, talks to AI models
- The Bridge API (
window.dench) — the JavaScript SDK for Dench Apps - Skills — markdown instruction files that teach the agent new capabilities
- Webhooks — HTTP endpoints that trigger agent workflows
Building Dench Apps#
A Dench App is a folder ending in .dench.app/ with a .dench.yaml manifest and an index.html. That's it — no build step, no npm install, no deployment.
Creating Your First App#
mkdir ~/.openclaw-dench/workspace/apps/my-dashboard.dench.app
touch ~/.openclaw-dench/workspace/apps/my-dashboard.dench.app/.dench.yaml
touch ~/.openclaw-dench/workspace/apps/my-dashboard.dench.app/index.html.dench.yaml:
name: "My Dashboard"
icon: "bar-chart"
description: "Pipeline analytics dashboard"
version: "1.0.0"
permissions:
- db:read
- ui:toast
- store:readwriteindex.html:
<!DOCTYPE html>
<html>
<head>
<title>My Dashboard</title>
</head>
<body>
<canvas id="chart"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
async function init() {
// The dench object is auto-injected
const data = await dench.db.query(`
SELECT ef.value as stage, COUNT(*) as count
FROM entry_fields ef
JOIN fields f ON ef.field_id = f.id AND f.name = 'Stage'
GROUP BY ef.value
`);
new Chart(document.getElementById('chart'), {
type: 'bar',
data: {
labels: data.map(r => r.stage),
datasets: [{
label: 'Deals by Stage',
data: data.map(r => r.count),
backgroundColor: 'rgba(59, 130, 246, 0.8)'
}]
}
});
}
// Wait for dench bridge to be ready
window.addEventListener('dench:ready', init);
</script>
</body>
</html>The app appears in your DenchClaw sidebar immediately. No restart required.
The Bridge API Reference#
The window.dench object is injected into every Dench App. Here are the core namespaces:
dench.db — Database Operations#
// Run a SQL query
const results = await dench.db.query(sql);
// Get all entries for an object
const entries = await dench.db.getEntries({ objectName: 'deals' });
// Get a single entry
const entry = await dench.db.getEntry({ id: 123 });
// Create an entry
const newEntry = await dench.db.createEntry({
objectName: 'people',
fields: {
'Full Name': 'Sarah Chen',
'Email': 'sarah@stripe.com',
'Status': 'Lead'
}
});
// Update an entry
await dench.db.updateEntry({
id: 123,
fields: { 'Status': 'Qualified' }
});dench.chat — Agent Communication#
// Create a session and talk to the agent
const session = await dench.chat.createSession();
const response = await dench.chat.send(session.id, "Summarize the top 5 deals this month");
console.log(response.text);
// Stream a response
for await (const chunk of dench.chat.stream(session.id, "Generate a weekly report")) {
appendToUI(chunk);
}dench.ui — Interface Controls#
// Show a toast notification
await dench.ui.toast({ message: "Done!", type: "success" });
await dench.ui.toast({ message: "Error occurred", type: "error" });
// Show a confirmation dialog
const confirmed = await dench.ui.confirm("Are you sure you want to delete this entry?");
// Show a loading state
dench.ui.setLoading(true);
// ... do work ...
dench.ui.setLoading(false);
// Update the app's title
dench.ui.setTitle("Pipeline Dashboard — Q1 2026");dench.store — App State Persistence#
// Save app state
await dench.store.set("last_filter", "enterprise");
await dench.store.set("selected_date_range", { start: "2026-01-01", end: "2026-03-31" });
// Load app state
const filter = await dench.store.get("last_filter");
const dateRange = await dench.store.get("selected_date_range");
// Clear state
await dench.store.delete("last_filter");dench.http — External API Calls#
// Fetch external URLs without CORS issues
const response = await dench.http.fetch("https://api.clearbit.com/v1/companies/domain/stripe.com", {
method: "GET",
headers: { "Authorization": `Bearer ${await dench.store.get("clearbit_key")}` }
});
const data = await response.json();dench.events — Real-Time Updates#
// Subscribe to CRM events
dench.events.on("entry:created", (event) => {
if (event.objectName === "people") {
refreshContactsList();
}
});
dench.events.on("entry:updated", (event) => {
updateEntryInUI(event.entryId, event.fields);
});
// Unsubscribe
dench.events.off("entry:created", handler);dench.cron — Scheduled Tasks from Apps#
// Schedule a periodic task
await dench.cron.schedule({
name: "refresh-dashboard",
everyMs: 300000 // 5 minutes
}, "Refresh the dashboard data");
// List scheduled tasks
const tasks = await dench.cron.list();Direct DuckDB Access#
For complex queries or bulk operations, connect directly to the DuckDB file:
import duckdb
conn = duckdb.connect("/Users/you/.openclaw-dench/workspace/workspace.duckdb")
# Query the PIVOT view
df = conn.execute("SELECT * FROM v_people WHERE \"Status\" = 'Lead'").df()
# Insert new entries (use the API for this in production)
conn.execute("""
INSERT INTO entries (object_id, created_at, updated_at)
VALUES (?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
""", [object_id])Important: Write operations through the DuckDB file directly bypass the agent's event system. Prefer the Bridge API or agent commands for writes — reserve direct DuckDB access for read-heavy analysis or data imports.
Webhook Handling#
Set up webhook endpoints to receive events from external systems:
# Ask the agent to set up a webhook
"Set up a webhook at /webhooks/stripe that:
- Receives Stripe payment.succeeded events
- Updates the customer's subscription status in DenchClaw
- Creates a transaction entry in the payments object"The agent creates the endpoint and the processing logic. You configure Stripe to POST to:
https://your-denchclaw-instance/webhooks/stripe
For local development, use ngrok:
ngrok http 19001
# Use the ngrok URL as your webhook endpointAction Fields (Server-Side Scripts)#
Action fields are buttons in your CRM table that run server-side scripts. The script receives the entry's fields as environment variables.
Example: An "Enrich" button that calls Clearbit and updates the company entry:
#!/usr/bin/env python3
# ~/.openclaw-dench/workspace/scripts/enrich-company.py
import os
import json
import requests
import duckdb
company_name = os.environ.get("COMPANY_NAME")
domain = os.environ.get("DOMAIN")
entry_id = os.environ.get("ENTRY_ID")
print(json.dumps({"type": "status", "message": f"Enriching {company_name}..."}))
# Call Clearbit
response = requests.get(
f"https://company.clearbit.com/v2/companies/find?domain={domain}",
headers={"Authorization": f"Bearer {os.environ.get('CLEARBIT_API_KEY')}"}
)
if response.status_code == 200:
data = response.json()
employee_count = data.get("metrics", {}).get("employees", 0)
conn = duckdb.connect(os.environ.get("DUCKDB_PATH"))
# Update the Employee Count field
# ... (field update SQL) ...
print(json.dumps({
"type": "success",
"message": f"Enriched: {employee_count} employees"
}))The script streams NDJSON output to the UI, showing progress in real time.
Building Skills#
Skills are the most powerful extension mechanism. A skill is a markdown file at ~/.openclaw-dench/workspace/skills/my-skill/SKILL.md.
A minimal skill structure:
# My Integration Skill
## Purpose
Connects DenchClaw to [External Service] for data sync.
## Setup
Requires API key stored at ~/.openclaw-dench/workspace/.env as MY_SERVICE_KEY
## Commands
### Sync contacts
When the user asks to "sync contacts from [service]":
1. Authenticate using MY_SERVICE_KEY
2. Fetch contacts from [service] API endpoint
3. For each contact, create or update entry in the people object
4. Report total synced
### Lookup record
When the user asks to "look up [name] in [service]":
1. Call the /search endpoint with the name parameter
2. Return formatted contact detailsPublish your skill to clawhub.ai and the community can install it.
Developer Resources#
- GitHub: github.com/DenchHQ/denchclaw — Source, issues, PRs
- OpenClaw docs: docs.openclaw.ai — Framework documentation
- clawhub.ai: clawhub.ai — Skill marketplace
- Discord #dev: Real-time developer community
Frequently Asked Questions#
Can I build a Dench App with a framework like React or Vue?#
Yes, but it requires a build step. You can develop with any framework, build to a dist/ folder, and point your .dench.yaml entry point to dist/index.html. For simpler apps, vanilla HTML + Chart.js or Alpine.js is faster to iterate on.
Is there a rate limit on dench.db.query()?#
No hard rate limit — queries run directly against your local DuckDB. Very large queries or tight polling loops may impact UI responsiveness, but there's no artificial throttle.
Can I access the DenchClaw API from an external application?#
The DenchClaw gateway exposes a local API. With appropriate configuration, you can make API calls to localhost:19001/api/... from external local applications. For remote access, expose the API via HTTPS (with authentication) on your VPS.
How do I debug a Dench App?#
Open your browser's developer tools (the DenchClaw web UI is a Chromium-based browser). Console logs from your app appear in the DevTools console. The dench.ui.toast() function is useful for quick status feedback during development.
Ready to try DenchClaw? Install in one command: npx denchclaw. Full setup guide →
