DenchClaw App Store API: Persistent State for Your Apps

How to use DenchClaw's Store API (dench.store) to save and retrieve persistent state in your custom Dench Apps across sessions.

Mark Rachapoom
Mark Rachapoom
·6 min read
DenchClaw App Store API: Persistent State for Your Apps

Custom Dench Apps need a way to save state that persists between sessions — user preferences, filter selections, API keys, cached data, app configuration. DenchClaw's Store API (dench.store) provides a simple key-value storage system backed by your local filesystem, available to every app in your workspace.

What Is the Store API?#

The Store API is a persistent key-value store scoped to each Dench App. Think of it as your app's private localStorage, but:

  • Persists across sessions (unlike browser localStorage on localhost, which can be cleared)
  • Stored on your filesystem (readable, version-controllable)
  • Supports both plain values and encrypted secrets
  • Accessible via a clean async API

Each app has its own isolated namespace in the store — App A can't read App B's store.

Basic API#

// Store a value
await dench.store.set("selected_pipeline", "Q1 2026");
 
// Retrieve a value
const pipeline = await dench.store.get("selected_pipeline");
// Returns: "Q1 2026" or null if not set
 
// Check if a key exists
const exists = await dench.store.has("selected_pipeline");
 
// Delete a value
await dench.store.delete("selected_pipeline");
 
// List all keys in the app's store
const keys = await dench.store.keys();
// Returns: ["selected_pipeline", "chart_type", ...]
 
// Clear all values for this app
await dench.store.clear();

Storing Complex Data#

The Store API automatically JSON-serializes objects and arrays:

// Store an object
await dench.store.set("filter_config", {
  stage: "Proposal",
  owner: "Sarah",
  minValue: 10000,
  dateRange: "this_quarter"
});
 
// Retrieve it
const config = await dench.store.get("filter_config");
// Returns the original object (automatically parsed)
console.log(config.stage);  // "Proposal"
 
// Store an array
await dench.store.set("recent_searches", [
  "enterprise leads",
  "SF contacts",
  "open deals Q2"
]);

Secret Storage#

For sensitive values like API keys, passwords, or tokens:

// Store a secret (encrypted at rest)
await dench.store.set("apollo_api_key", "api_abc123...", { secret: true });
 
// Retrieve a secret (same API as normal values)
const apiKey = await dench.store.get("apollo_api_key");
 
// Secrets work the same way but are encrypted in the store file
// They're not visible in plaintext if someone opens the store file directly

Under the hood, secrets are encrypted using AES-256 with a key derived from your workspace's master key. They're safe to store in git (the decryption key is local only).

Practical Use Cases#

Remembering User Preferences#

// Load saved preferences on app startup
async function loadPreferences() {
  const prefs = await dench.store.get("user_preferences") || {
    chartType: "bar",
    sortField: "Close Date",
    showArchived: false
  };
 
  applyPreferences(prefs);
}
 
// Save when user changes settings
function savePreferences() {
  const prefs = {
    chartType: currentChartType,
    sortField: currentSortField,
    showArchived: showArchivedToggle.checked
  };
 
  dench.store.set("user_preferences", prefs);
}

Caching External API Responses#

const CACHE_TTL = 3600000;  // 1 hour in milliseconds
 
async function getCompanyEnrichment(domain) {
  const cacheKey = `clearbit:${domain}`;
  const cached = await dench.store.get(cacheKey);
 
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.data;
  }
 
  // Fetch fresh data
  const apiKey = await dench.store.get("clearbit_api_key");
  const response = await dench.http.fetch(
    `https://company.clearbit.com/v2/companies/find?domain=${domain}`,
    { headers: { Authorization: `Bearer ${apiKey}` } }
  );
  const data = await response.json();
 
  // Cache with timestamp
  await dench.store.set(cacheKey, {
    data,
    timestamp: Date.now()
  });
 
  return data;
}

Tracking App State for Sessions#

// Save open tabs state
window.addEventListener("beforeunload", async () => {
  await dench.store.set("open_tabs", {
    activeTab: currentTab,
    openPanels: openPanelIds,
    scrollPosition: window.scrollY
  });
});
 
// Restore on load
window.addEventListener("load", async () => {
  const state = await dench.store.get("open_tabs");
  if (state) {
    restoreTabs(state.activeTab, state.openPanels);
    window.scrollTo(0, state.scrollPosition);
  }
});

Multi-Step Workflow State#

// Track progress in a multi-step import wizard
class ImportWizard {
  async saveProgress(step, data) {
    await dench.store.set("import_wizard_state", {
      currentStep: step,
      data,
      startedAt: new Date().toISOString()
    });
  }
 
  async loadProgress() {
    return await dench.store.get("import_wizard_state");
  }
 
  async clearProgress() {
    await dench.store.delete("import_wizard_state");
  }
}

Store Storage Location#

App store data is persisted to:

~/.openclaw-dench/workspace/apps/YOUR_APP_SLUG/store.json

For secret values:

~/.openclaw-dench/workspace/apps/YOUR_APP_SLUG/store.encrypted.json

These files are part of your workspace and included in backups and git commits (secrets are encrypted). You can inspect or edit the non-secret store.json directly if needed.

Store vs. DuckDB: When to Use Each#

Use Store API for:

  • App configuration and preferences
  • User-specific settings
  • API keys and secrets
  • Cached API responses
  • UI state that persists across sessions
  • Small data (<1MB per app)

Use DuckDB for:

  • Structured CRM data
  • Data you want to query with SQL or natural language
  • Data shared across apps
  • Large datasets
  • Data with relations to other objects

The Store API is fast (no SQL) and simple (key-value). DuckDB is powerful (queryable, relational, large). Use the right tool for each job.

Listing and Managing All Store Data#

// See all keys and their values (for debugging)
const allKeys = await dench.store.keys();
for (const key of allKeys) {
  const value = await dench.store.get(key);
  console.log(key, typeof value, JSON.stringify(value).slice(0, 100));
}
 
// Export all store data
const exported = {};
for (const key of await dench.store.keys()) {
  exported[key] = await dench.store.get(key);
}
console.log(JSON.stringify(exported, null, 2));

See also: DenchClaw Context API for accessing workspace context, and DenchClaw HTTP Proxy for making API calls to populate your store.

Frequently Asked Questions#

Is the Store API scoped per app or global?#

Per-app. Each app has its own isolated store namespace. App A can't read App B's store. There's no shared store between apps — use the event system or DuckDB for cross-app data sharing.

How much data can I store per app?#

No hard limit is enforced, but the Store API is designed for small data (preferences, config, cached responses). For large datasets (thousands of records), use DuckDB. If your store.json exceeds 10MB, consider migrating data to DuckDB.

Are secrets truly secure?#

Secrets are encrypted with AES-256-GCM using a key derived from your workspace master key. They're safe from casual inspection of the filesystem. They're not safe against someone who has full access to your machine and knows what they're looking for. For extreme security requirements, use a dedicated secrets manager.

Can store data be backed up?#

Yes. The store files are in your workspace directory and included in workspace backups. If you use git for your workspace, add apps/*/store.json to your .gitignore to prevent committing sensitive app state. Never commit store.encrypted.json — it contains your encrypted secrets.

What happens to store data when I delete an app?#

The app's store directory is preserved by default when an app is deleted. This prevents accidental data loss. To fully clean up, manually delete ~/.openclaw-dench/workspace/apps/YOUR_APP_SLUG/ after removing the app.

Ready to try DenchClaw? Install in one command: npx denchclaw. Full setup guide →

Related articles

Keep reading

View all