ddcore — reference for agents
ddcore (Data Driven Core) is an application framework in the spirit of Frappe: DocTypes (models declared in TypeScript) become Postgres tables, forms, lists and a REST API automatically. The core is a Go binary (ddcore) embedding esbuild + goja: an app's logic is TypeScript run on the server, synchronously (no await), and its form scripts run in the desk.
Available documents (also as MCP resources ddcore://docs/<name>):
conventions— an app's layout, naming, what never to dofieldtypes— every fieldtype and field propertyauth— sign-in, lockout, recovery, invitation, self-service and secretsfield-permissions— field levels (permlevel): confidential fields omitted from every read path and protected on writescopes— user access scopes (User Permission): restricting users to companies, units or customers across every read and write pathcontroller-api—defineController, hooks, methods, the server'sddcore.*APIform-api—defineForm,frm.*, dialogs (desk)report-api—defineReport,defineWorkspace, cards and chartsextending— adding fields to, and overriding properties of, another app's DocTypesmail— mail templates, blocks, attachments and the delivery recordnotifications— persistent event/date rules, authorized recipients, Desk inbox and optional emailassignments— ToDo DocType, assignment workflow, pending work, sidebar widget and due date remindersworkflows— declarative approval workflows: states, actions, docstatus binding, atomic transitions, server enforcement and Desk action buttonswebhooks— outgoing webhooks: subscriptions, signed delivery after commit, retries, replay and the audit recordi18n— English as the source language, catalogues, Select values, dates and the site timezonemigrations— renames, fieldtype changes, patches and the expand → contract routeexport— exporting a whole DocType, children and attachments, by HTTP or CLIprint— print templates, print block builders, Letter Head branding, and server-side PDF generationvault— encrypted credential vault (ddcore.vault.*),Vaultfieldtype, and audit loggingaudit— unified administrative audit events (tab_audit_event), sanitization, immutability, retention and CLI inspectioncli— theddcorecommands and the development loopops— liveness and readiness probes, request correlation, queue signals anddoctorfeature-requests— when a gap belongs in the framework, and how to report it upstream
Typical flow
- In a monorepo,
ddcore new-app <name>createsapps/<name>; in an app's own repository, useddcore new-app <name> --dir .andapps: ["."]. - Write
doctypes/<snake>/<snake>.doctype.tswithdefineDoctype. ddcore migrate(or the MCPmigratetool) creates and alters the tables and generates, under.ddcore/, the DocType types and the declarations of the embedded SDKs.- Rules in
<snake>.controller.ts; screen scripts in<snake>.form.ts; tests in<snake>.test.ts. ddcore devruns the server with hot reload;ddcore testruns the tests in rolled-back transactions.
Mental model
- Single DocTypes (
isSingle) expose one settings document with a fixedsingletonidentity; seecontroller-api. - One DocType = one table
tab_<snake_case>; child tables (isChild) haveparent,parenttype,parentfield,idx. - Standard columns:
name(PK, text),owner,creation,modified,modified_by,docstatus(0 draft, 1 submitted, 2 cancelled). - Lifecycle:
beforeValidate → validate → beforeSave → (insert|update) → afterInsert/onUpdate;beforeSubmit → onSubmit;beforeCancel → onCancel;onTrash → afterDelete. - The core validates
reqd,unique,uniqueKeys(compound business keys, checked before the write and enforced by a partial unique index, so a race cannot slip through), a Select'soptions, that links exist,fetchFrom,mandatoryDependsOn(on the server) and refuses to change a field withoutallowOnSubmitonce submitted. - One transaction per request or job. An error rolls it back. There is no
commit()for an app. - A rename is declared (
renamedFrom), never inferred, and a fieldtype change that could lose data is refused until you declareconvertor move the data across releases. Seemigrations. - Every user-facing string is English and is a key — a
label:as much as a_("…"). Translations live intranslations/<lang>.csv, and a Select's value is canonical English with a translated label. Seei18n.