appdb
Rule: Domo App Platform AppDB (Toolkit-First)
This rule is toolkit-first. Use AppDBClient instead of raw domo.get/post/put/delete endpoints.
Canonical Client
yarn add @domoinc/toolkit
import { AppDBClient } from '@domoinc/toolkit';
type Task = {
title: string;
status: 'active' | 'completed';
priority: 'Low' | 'High' | 'Urgent';
};
const tasksClient = new AppDBClient.DocumentsClient<Task>('TasksCollection');
Core Operations
Create
const created = await tasksClient.create({
title: 'New Task',
status: 'active',
priority: 'High'
});
const task = created.body;
Read / query
const all = await tasksClient.get();
const active = await tasksClient.get({ status: { $eq: 'active' } });
const highOpen = await tasksClient.get({
$and: [{ status: { $ne: 'completed' } }, { priority: { $in: ['High', 'Urgent'] } }]
});
AppDB response structure (critical)
Documents returned by .get() are wrapped with metadata and your fields live inside doc.content.
What .create() accepts:
{ vendor: 'Acme', riskLevel: 'High', notes: 'Late payments' }
What .get() returns (shape):
[
{
"id": "04b1756e-7b6d-4d77-842f-7975a6474d8a",
"datastoreId": "a3b85171-...",
"collectionId": "ba194a7d-...",
"syncRequired": true,
"owner": 767612617,
"createdOn": "2026-03-22T02:22:42.030Z",
"updatedOn": "2026-03-22T02:22:42.030Z",
"updatedBy": 767612617,
"content": {
"vendor": "Acme",
"riskLevel": "High",
"notes": "Late payments"
}
}
]
Key points:
- Your app fields are nested inside
doc.content, not at the top level. doc.idis the document ID used for.update()and.delete().- Metadata fields (
datastoreId,collectionId,owner,createdOn, etc.) are top-level. - Overall result may be in
response.bodyor directly the array.
Required parsing pattern:
const response = await tasksClient.get();
const rawDocs = response.body || response;
const docs = Array.isArray(rawDocs) ? rawDocs : [];
const parsed = docs.map((doc) => ({
id: doc.id,
...doc.content
}));
Common mistake:
// WRONG: fields are inside content
const docs = response.body || response;
docs[0].vendor; // undefined
// CORRECT
docs[0].content.vendor; // "Acme"
Update
await tasksClient.update({
id: 'document-uuid',
content: { title: 'Updated', status: 'completed', priority: 'Low' }
});
await tasksClient.partialUpdate(
{ status: { $eq: 'active' } },
{ $set: { status: 'archived' } }
);
Delete
await tasksClient.delete('document-uuid');
await tasksClient.delete(['uuid-1', 'uuid-2']);
Manifest Requirements
Collections still must exist in manifest.json under collections.
{
"collections": [
{
"name": "TasksCollection",
"schema": {
"columns": [
{ "name": "title", "type": "STRING" },
{ "name": "status", "type": "STRING" }
]
}
}
]
}
Checklist
-
collectionsmapping exists in manifest -
AppDBClient.DocumentsClientused for CRUD -
.get()results are unwrapped fromdoc.contentbefore UI/use - Query/update operators (
$eq,$in,$set,$inc, etc.) used correctly - Error handling and loading states included in UI flows
More from stahura/domo-ai-vibe-rules
domo-js
Use ryuu.js (domo.js) APIs for env, events, navigation, and data calls.
57html-deck
Build a professional HTML slide deck from source content and convert it to a pixel-perfect PDF. Covers slide architecture, layout patterns, print-safe CSS, Puppeteer PDF conversion, and quality verification. Use when creating presentation decks, converting documents to slide format, or generating PDF decks from HTML.
56migrate-lovable
Convert SSR-heavy Lovable/v0 apps into client-only Domo apps.
56data-api
High-level entry skill for Domo data access. Routes detailed query work to dataset-query.
55jsapi-filters
Apply runtime filters to embedded Domo dashboards/cards from the host page via the JS API (MessagePort). Covers dynamic filtering, drill events, filter change listeners, iframe resize, pfilter URL params, overrideFilters, and App Studio appData. Use for any client-side Domo embed interaction. Not for server-side programmatic filters (use programmatic-filters).
55ai-service-layer
Toolkit-first AIClient patterns for generation, text-to-sql, and response parsing.
55