Conventions
An app's layout
<app>/ # the repository root, or apps/<app>/ in a monorepo
ddcore.app.ts defineApp: title, roles, scheduler, docEvents, desk, afterInstall, fixtures
doctypes/<snake>/
<snake>.doctype.ts defineDoctype (meta)
<snake>.controller.ts defineController (rules, methods)
<snake>.form.ts defineForm (desk) — imports "@ddcore/desk-sdk"
<snake>.test.ts tests (imports "@ddcore/sdk/test")
extensions/<snake>.extend.ts extendDoctype: fields and properties added to another app's DocType
extensions/<snake>.form.ts defineForm for that DocType, loaded alongside its owner's
services/*.ts business functions; whitelisted() exposes them at /api/method/<app>.services.<file>.<fn>
reports/*.report.ts defineReport
workspaces/*.workspace.ts defineWorkspace
workflows/<name>.workflow.ts defineWorkflow: states, transitions, approval roles — see `workflows`
print/<name>.print.ts a print format — see `print`
patches/NNNN_name.ts definePatch({ phase, execute }) — runs once, during migrate → migrations
client/*.ts scripts loaded across the whole desk (declare them in desk.include)
translations/pt-BR.csv "key,translation,# source" — generated by `ddcore i18n extract`
.ddcore/types.d.ts generated by `ddcore types` — import with `import type { Contract } from "../../.ddcore/types"`A module's dotted path is <app>.<folder>.<file> (no .ts). For example: my_app.services.tasks.run. That is the form used by whitelisted, scheduler, ddcore.enqueue, ddcore exec and the call_method tool.
What the app puts on the desk
defineApp's desk block is the app's say over the shell around its screens:
desk: {
include: ["client/lists.ts"], // scripts loaded on every desk page
home: "Projects", // the workspace /app opens on
logo: "📁", // the square mark beside the site's name
}home and logo are resolved the same way: the first app in load order that declares one wins, and the core declares neither, so an app's always does.
logo is one letter or one emoji, not an image URL — the mark is a fixed square, rendered as text. Leave it out and the mark is the initial of the name printed beside it, which is this app's title. That title is a catalogue key like any label, so both the word and its initial follow the reader's language — see i18n.
Naming
- DocType name: ASCII, spaces allowed, capitalised (
"Project Milestone"). Becomes the tabletab_project_milestoneand the interfaceProjectMilestone. fieldname: ASCII snake_case (due_date).- Labels are English, because a label is a catalogue key. An accent belongs in
translations/<lang>.csv, never in the code. Seei18n. - Reserved:
name, owner, creation, modified, modified_by, docstatus, doctype, parent, parenttype, parentfield, idx.
Rules that do not change
- Server code is synchronous.
ddcore.db.getValue(...)returns the value directly; neverawaitin a controller or a service. - Business errors:
ddcore.throw(_("message"), { title: _("Title") })— becomes HTTP 417ValidationErrorand a toast in the desk. - Once submitted (
docstatus 1), only fields withallowOnSubmitchange throughsave(); everything else goes throughdoc.dbSet(...). - A derived status (paid, overdue, …) is computed in
validate, never typed. - The sidebar and the workspace are explicit (
defineWorkspace), never inferred. - Never edit
.ddcore/types.d.ts; runddcore types. - A renamed field or DocType declares
renamedFrom; without it the rename is a new empty column and an orphaned old one. Seemigrations. - A fieldtype change that could lose data is refused, not guessed: declare
convert, or move the data across releases with expand → backfill → validate → contract. - A DocType belongs to one app: it is declared once, and another app adds to it with
extendDoctype. Seeextending. - Tests: each
itruns in a rolled-back transaction; create the data you need inside the test. - A Select's values are canonical English and are what the database holds; colour them with
optionColors, never by matching their text.
Versioning and releases
The framework follows Semantic Versioning 2.0.0 (vMAJOR.MINOR.PATCH):
- Git tags: Official releases use the
v*format (e.g.v0.1.0,v1.0.0). - Release artifacts:
- Pushing a version tag
v*triggers theReleaseGitHub Action, creating a GitHub Release with cross-platform static archives (ddcore-<os>-<arch>.tar.gzandSHA256SUMS) for Darwin and Linux (amd64/arm64). - Pushing to
mainupdates the rolling releaselatestused by the installer script (install.sh). - No container image is published: an app builds its own image and downloads the binary of the release it pins.
- Pushing a version tag
- App compatibility (
requires):- Apps declare compatibility constraints in
ddcore.app.ts(requires: { ddcore: ">=0.1.0" }). - Breaking changes to public server/desk SDKs or engine contracts increment MAJOR.
- New backwards-compatible capabilities or hooks increment MINOR.
- Bug fixes and optimizations increment PATCH.
- Apps declare compatibility constraints in