Skip to main content
The Dench CRM is the shared database for agents and humans. Every list-shaped thing lives here, not in ad-hoc files.

Objects

Tables like people, company, task, or any custom object.

Fields

Typed columns: text, enum, relation, user, date, and more.

Entries

Rows, written one at a time or in batches up to 200.

Cells

Individual values, set, appended, or read per field.

Query

Filter, sort, paginate, search, and aggregate.

Statuses

Kanban columns with colors for any object.
people, company, and task are protected and cannot be deleted. Tasks are CRM entries on the task object; dench crm tasks … adds shortcuts over it (see Tasks).

Create an object

1

Inspect what exists

2

Create it with an icon

3

Add fields

4

Optional: kanban statuses

Field types

text, email, phone, url, number, boolean, date, file, tags.
Field type is immutable. To change a field’s type, create a new field and migrate. dench crm fields update changes name, options, indexing, and enrichment, but not the type.

The protected Notes field

Every object ships a protected Notes (richtext) field for free-form prose, meeting notes, summaries, task details. It is hidden from list views and shown only when an entry is opened. Do not invent parallel Description/Summary columns, write Markdown to Notes.

Write data

Read data

Over the API, operator filters ($gt, $in, $contains, $and, $or) go in dslJson because the wire format rejects $-prefixed keys in nested objects. See the API playground.

Assigned to me

--assigned-to me|unassigned|<userId|email> (alias --mine) narrows any of these reads to rows whose people field includes that member, exactly like the avatar pill next to Filter in the app. It is a personal scope, not a view filter: nothing is saved onto the view. --assignee-field <Field> picks the user field when an object has several; by default the analytics owner role, then the task Assignee, then a field named like owner/assignee, then the first user field. Over the API pass assignedTo / assigneeField. Counts, kanban column sums and dench crm analytics compute --mine follow the same scope.

Tasks

Tasks are rows on the protected task object, so every entries/cells command works on them. The shortcuts below wrap the same functions the To-Do(s) view and the record panel use, and notify assignees.

Priority

Every task object ships a Priority enum — Low, Medium, High — with no default; a task without one sorts last. Priority drives the order of every task surface: To-Do(s) (High → Medium → Low → unset, most recently active within a level), the home rail, a record’s task strip, and the Tasks table and kanban (whose default sort is Priority desc, Due Date asc). Enum sorts follow option order, not spellingPriority desc walks the scale, and a Status sort follows the kanban columns. Lists that begin with an obviously-top word (Urgent, High, P0) are read highest-first, so a workspace that already modelled priority its own way ranks correctly too. Priority accepts the option in any casing ("high""High"); an unknown value is rejected with the valid options. In composers, Ship it !high (or !h, !!!) sets it from the keyboard.

Subtasks

A subtask is an ordinary task whose Parent Task relation points at another task — same object, same table, same kanban, one level deep. The relation is created the first time a workspace nests a task, so workspaces that never do never see the column.
  • A new subtask inherits the parent’s record relations (Account, Contact, Opportunity …) so it groups under the same record on To-Do(s); it does not inherit Assignee, Due Date, Priority or Notes.
  • Subtasks show as rows everywhere with a ↳ Parent chip; parents show a 2/5 progress ring. Completing the last subtask does nothing to the parent. View settings → Rows → Show subtasks hides them by writing a Parent Task is empty filter rule (saveable into a view).
  • POST /crm/tasks/{entryId}/subtasks, PUT /crm/tasks/{entryId}/parent ({ "parentEntryId": "<id>" | null }) and DELETE /crm/tasks/{entryId} ({ "subtasks": "delete" | "keep" }) are the API forms. Linking a task to a task through POST /crm/objects/task/entries/{id}/tasks also creates a subtask.

Repeating tasks

A repeat is a property of the task’s Due Date — set it from the ↻ Repeat control under the date (beside Remind). If the task has no due date yet, picking a repeat sets one: the first day the rule matches from today (Weekly on Mon chosen on a Thursday → due next Monday). Every state reads as one sentence: “Repeats every 2 weeks on Mon · next Sep 22”. Presets come from the due date (Daily · Every weekday · Weekly on Wed · Every 2 weeks · Monthly on the 17th · Yearly); Custom opens interval / weekdays / month day or “2nd Tuesday” / ends; Advanced holds the trigger and what carries over.
  • A new task per occurrence. The finished one stays Done with its own comments and history; occurrences are linked through a hidden _Series field (filter _Series is <seriesId> lists them; dench crm tasks series <id> --occurrences does the same).
  • Trigger. When this one is done (default): completing the current occurrence — from To-Do(s), the kanban, dench crm cells set task <id> Status Done, or the API — creates the next one. On schedule: the next one is created at 00:05 local on its due date whether or not the current one is done.
  • Never overdue. If a late completion would land in the past, the next due date skips forward to the first occurrence from today. Month-end rules keep their intent (Jan 31 → Feb 28 → Mar 31); Skip weekends rolls Saturday/Sunday to Monday.
  • What carries over: every field except Status and Due Date; Notes, Assignee and subtasks are on by default and toggleable; comments never. The assignee isn’t pinged when they just completed it themselves.
  • Ends never, after N times, or on a date. Clearing the live task’s due date pauses the series (“set a due date”); setting one resumes it. The task page’s line offers Skip this one · Pause / Resume · View all occurrences · Stop repeating; deleting the live occurrence asks This occurrence (the next is created now) or All future occurrences.
  • Subtasks can’t repeat on their own — set repeat on the parent (its subtasks copy to each occurrence).
The response always carries both rule and rrule, plus summary (the sentence) and nextDueKey.

Activity and comments

Every record has an Activity feed under Notes: threaded comments merged with its change history. dench crm history reads the audit trail; dench crm comments reads and writes the conversation. See Comments.

Relation-first modeling

If a field name matches or aliases an existing object (Company, Client, Deal, Owner), use a relation (or user) field, not text.
Use entries create-many, entries update-many, cells set-many, or batch --file for three or more writes.

Who can see a record

Every object has a visibility that decides who can read its records. It is a property of the object, and the same rule applies everywhere a record can appear: lists, the table, search, the picker, aggregates, the API and the agent’s tools. meeting ships as owner_plus_members (the person who recorded it plus everyone whose calendar held the same call) and calendar_event as owner_only. Records on a non-workspace object are stamped with their creator as owner, and only the owner can edit or delete them. Switching an object that already has records to owner_only or owner_plus_members hides every record that has no owner — including from the admin who made the change — until an owner is set. Switch back to workspace to see them again. Only workspace admins can change it, including on protected objects (an org-scoped API key cannot; a signed-in admin is required):
An org-scoped API key or an agent run with no signed-in person reads only workspace objects. Sign in as a member (or bind the key to one) to read meetings and calendar events.

Meetings

A meeting is a CRM record on the protected meeting object. Create, update and delete it like any other entry; the Meetings page reads the same rows.
dench meetings import --csv calls.csv | --jsonl calls.jsonl brings in many calls at once: meeting fields per row plus optional transcript, actionItems and attendees columns. Same --map, --dedupe-key and --on-conflict flags as dench crm import. Attendees are linked to existing people and members by email, never created. Run it signed in — meetings are owner-scoped. API: /v1/meetings, /v1/meetings/{id}/transcript, …/action-items, …/participants.

Records Dench writes for you

Some objects are filled by Dench itself and cannot be created by hand. They are ordinary CRM records otherwise: dench crm entries list <object>, dench crm query, the API and the agent all read them. A LinkedIn message files an interaction with Type LinkedIn only when the other person is a linked people record. Messages from a private LinkedIn account keep their text out of the CRM until a viewer-aware read rule exists for inbox rows.