A PM system is Coda's most practical capstone: five interconnected tables, rollup formulas tracking completion percentages, a time log, four automations, and a live CRM integration — all in one doc your whole team will actually use.
A project management system tracks work at three levels of granularity: the project (what we're building), the milestone (a major deliverable gate), and the task (individual actionable items). Time and people are cross-cutting concerns — a Time Log and a Team Members table connect to everything.
The top-level unit. Contains owner, status, start/end dates, priority, and derived metrics that roll up from Milestones and Tasks below.
Major deliverable gates within a project. Each milestone links to a Project. Keep them to 5–8 per project — they're gates, not granular work.
Tasks are individual actionable items linked to both a Project and Milestone. The Time Log tracks actual hours spent per task and per person.
The Projects table is the top-level anchor. Core columns are static (entered manually). Derived formula columns roll up live data from the Tasks table — completion percentage, at-risk flag, days remaining — and update automatically.
| Column | Type | Notes / Formula |
|---|---|---|
| Name | Text | Display column |
| Owner | Person | Project lead accountable for delivery |
| Status | Select | Planning Active On Hold Complete Cancelled |
| Start Date | Date | Project kick-off date |
| End Date | Date | Target delivery date |
| Priority | Select | High Medium Low |
| Description | Canvas | Rich-text scope and goals |
| Task Count | Formula | Tasks.Filter([Project]=thisRow).Count() |
| Done Tasks | Formula | Tasks.Filter([Project]=thisRow).Filter([Done]=true).Count() |
| Completion % | Formula | If([Task Count]=0, 0, [Done Tasks]/[Task Count]) |
| Days Remaining | Formula | [End Date] - Today() |
| At Risk | Formula | If([Completion %] < 0.5 And [Days Remaining] < 14, true, false) |
Milestones are the deliverable checkpoints within a project — "Design approved," "Beta deployed," "Launch." They are not individual tasks. Keep each project to 5–8 milestones. Every milestone links to its parent Project via a relation column.
| Column | Type | Notes |
|---|---|---|
| Name | Text | Display column — e.g. "Beta Deployed" |
| Project | Relation | → Projects table |
| Due Date | Date | Target completion date |
| Status | Select | Not Started In Progress Complete Blocked |
| Owner | Person | Who is accountable for this milestone |
| Task Count | Formula | Tasks.Filter([Milestone]=thisRow).Count() |
| Completion % | Formula | Tasks.Filter([Milestone]=thisRow).Filter([Done]=true).Count() / [Task Count] |
The automation that auto-completes a milestone: when a task row changes and the condition Tasks.Filter([Milestone]=thisRow.[Milestone]).Filter([Done]=false).Count() = 0 becomes true, the automation sets that Milestone's Status to "Complete." This means you never have to manually mark a milestone done — it completes itself when the last task is checked off.
The Tasks table is where day-to-day work happens. Each task links to a Project and optionally a Milestone. The Done checkbox is a formula derived from Status — when Status becomes "Done", Done becomes true, which rolls up into the Projects completion formula.
| Column | Type | Notes |
|---|---|---|
| Name | Text | Display column |
| Project | Relation | → Projects |
| Milestone | Relation | → Milestones (optional) |
| Assignee | Person | Who owns this task |
| Due Date | Date | When this task must be done |
| Priority | Select | High Medium Low |
| Status | Select | To Do In Progress Review Done Blocked |
| Estimate | Number | Estimated hours to complete |
| Done | Checkbox | If([Status]="Done", true, false) — drives rollup formulas |
Filter: Assignee = @Me. Sort: Due Date ascending. Your personal task list.
Group by Milestone. Shows task count and completion per milestone at a glance.
Filter: Done = false AND Due Date < Today(). The critical list no one should ignore.
Board view grouped by Status. Drag cards between To Do → In Progress → Review → Done.
The Projects table is the foundation of the status dashboard. A canvas page embeds multiple views — all pulling live data from the five tables — to give leadership a real-time picture of the portfolio without anyone manually updating a status deck.
Embed the Projects table filtered to Status = "Active." Display columns: Name, Owner, Completion %, Days Remaining, At Risk (conditional formatting: red if true).
Add a Timeline view of Projects using Start Date → End Date. Immediately see all projects in flight, where they overlap, and which end dates are approaching.
Embed Tasks grouped by Assignee showing task count per person. Instantly see who is over-allocated and who has capacity.
Embed Tasks filtered to Due Date between Today() and DateAdd(Today(), 7, "days"). The week's critical path visible in one block.
The Time Log table records actual hours worked. Each row represents one person's time on one task on one day. This table powers the most useful PM metric: whether you're tracking on estimate or going over budget.
| Column | Type | Notes |
|---|---|---|
| Task | Relation | → Tasks table |
| Person | Person | Who logged the time |
| Date | Date | The day the work was done |
| Hours | Number | Decimal (e.g., 1.5 = 1 hr 30 min) |
| Notes | Text | Optional: what was worked on |
// Actual Hours column on Tasks // Sum all Time Log rows linked to this task Time Log.Filter([Task]=thisRow).Sum([Hours]) // Over Budget column on Tasks (Checkbox type) If([Actual Hours] > [Estimate], true, false)
// Sum all Time Log hours whose Task links back to this project Time Log.Filter([Task].[Project]=thisRow).Sum([Hours])
Four automations turn the PM system from a passive database into an active operational layer. Each handles something a person would otherwise do manually — or forget.
Trigger: row added to Tasks. Condition: Assignee is empty. Action: Coda notification to Project Owner. Prevents tasks from silently slipping through with no owner.
Trigger: Task row changed. Condition: Tasks.Filter([Milestone]=thisRow.[Milestone]).Filter([Done]=false).Count() = 0. Action: set Milestone Status = "Complete."
Trigger: scheduled, every Monday at 9am. Action: AddRow to a Weekly Status Log table with a snapshot: open task count, average completion % across active projects, this week's deadline count.
Trigger: Projects.[At Risk] becomes true. Action: Slack message to #leadership channel with project name, owner, completion %, and days remaining. Leadership sees risks before they become crises.
The PM system and the CRM from Lesson 20 connect at the most natural handoff point: when a deal is won, implementation work begins. An automation bridges the two systems — so the project is created the moment the deal closes, with no manual data entry.
The Projects table includes an optional Deal relation column (→ Deals). This creates a two-way link: the CRM can show "which implementation projects are associated with this deal?" using a formula on the Deals table:
// On the Deals table — shows linked implementation projects Projects.Filter([Deal]=thisRow).Count()