Super Advanced · Lesson 21 of 22

Building a Project Management System

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.

⏱ ~45 min 🏗️ Capstone Project ✅ Prerequisite: Lesson 20
01 — Architecture 02 — Projects Table 03 — Milestones Table 04 — Tasks Table 05 — Rollups & Dashboard 06 — Time Log 07 — Automations 08 — CRM Integration Practice
01 — Architecture

Five tables, one connected system.

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.

🗂️

Projects

The top-level unit. Contains owner, status, start/end dates, priority, and derived metrics that roll up from Milestones and Tasks below.

🏁

Milestones

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 + Time Log

Tasks are individual actionable items linked to both a Project and Milestone. The Time Log tracks actual hours spent per task and per person.

PM System — Relation Map
🗂️
Projects
Name · Owner · Status · Start/End · Priority · Description
Root table
relation ↓
🏁
Milestones
Name · Project → · Due Date · Status · Owner
Tasks
Name · Project → · Milestone → · Assignee · Due · Status · Estimate
⏱️
Time Log
Task → · Person · Date · Hours · Notes
Build in order: Projects → Milestones → Tasks → Time Log Relation columns can only point to tables that already exist. Create the parent table before any child that references it.
02 — Projects Table

The root: project identity and derived metrics.

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.

Projects Table — Core Columns + Derived Formula Columns
ColumnTypeNotes / 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)
Add formula columns after building Tasks The Task Count, Done Tasks, Completion %, and At Risk columns reference the Tasks table — which doesn't exist yet. Add them after creating the Tasks table in s04.
03 — Milestones Table

Major gates, not granular work.

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.

Milestones Table — Columns
ColumnTypeNotes
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.

04 — Tasks Table

The work: individual items with five saved views.

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.

Tasks Table — Columns
ColumnTypeNotes
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

Five saved views for the Tasks table

1 — My Tasks

Filter: Assignee = @Me. Sort: Due Date ascending. Your personal task list.

2 — By Milestone

Group by Milestone. Shows task count and completion per milestone at a glance.

3 — Overdue

Filter: Done = false AND Due Date < Today(). The critical list no one should ignore.

4 — Board (Kanban)

Board view grouped by Status. Drag cards between To Do → In Progress → Review → Done.

05 — Rollups & Dashboard

The Projects table drives a live status dashboard.

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.

📊

Active projects: completion % + at-risk

Embed the Projects table filtered to Status = "Active." Display columns: Name, Owner, Completion %, Days Remaining, At Risk (conditional formatting: red if true).

📅

Timeline (Gantt) view

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.

👥

Team workload

Embed Tasks grouped by Assignee showing task count per person. Instantly see who is over-allocated and who has capacity.

🔔

This week's deadlines

Embed Tasks filtered to Due Date between Today() and DateAdd(Today(), 7, "days"). The week's critical path visible in one block.

Lock dashboard view layouts After setting up embedded views on the dashboard page, lock each view (three-dot menu → Lock layout). The underlying table data remains editable — only the view's filters and groupings are protected from accidental re-sorting.
06 — Time Log

Actual vs estimated — see where time really goes.

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.

Time Log Table — Columns
ColumnTypeNotes
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

Formula columns on Tasks using the Time Log

Task Table — Actual Hours + Over Budget Formulas
// 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)

Project-level time report

Projects Table — Actual Hours Rolled Up from Time Log
// Sum all Time Log hours whose Task links back to this project
Time Log.Filter([Task].[Project]=thisRow).Sum([Hours])
Log time as you go, not retroactively The Time Log's value is in real-time accuracy. A row takes 30 seconds to add. At week-end retro-logging, people underestimate time and over-estimate quality — the data becomes unreliable for future estimation.
07 — Automations

The system notifies, escalates, and self-updates.

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.

1 — Unassigned task alert

Trigger: row added to Tasks. Condition: Assignee is empty. Action: Coda notification to Project Owner. Prevents tasks from silently slipping through with no owner.

2 — Auto-complete milestone

Trigger: Task row changed. Condition: Tasks.Filter([Milestone]=thisRow.[Milestone]).Filter([Done]=false).Count() = 0. Action: set Milestone Status = "Complete."

3 — Weekly status log

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.

4 — At-risk Slack alert

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.

08 — CRM Integration

Won deals become projects automatically.

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.

CRM → PM Automation: Deal Won → Create Project
Trigger
Deals table: [Stage] changes to "Won"
Action: Create row in Projects
Name = "Implementation: " & [Deal Name]
Owner = [Deal Owner]
Start Date = Today()
Status = "Planning"
Deal = thisRow // optional back-link

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:

CRM: Projects Linked to a Deal (Deals Table Formula)
// On the Deals table — shows linked implementation projects
Projects.Filter([Deal]=thisRow).Count()
One system of record Sales owns the CRM. Engineering and ops own the PM system. With this integration, neither team has to manually update the other — the automation handles the handoff. Everyone sees the same data, in the tool they actually use.
Practice

Test your knowledge.

Lesson 21 Quiz

5 Questions
Question 1 of 5
In the PM system's data model, which table should be created first, and why?
✓ Projects is the root table. Milestones reference Projects, Tasks reference Milestones — so you must build in order: Projects → Milestones → Tasks → Time Log.
Question 2 of 5
What is the Completion % formula on the Projects table?
✓ The formula divides Done Tasks by Task Count but guards for zero with If([Task Count]=0, 0, ...) to prevent a division-by-zero error on new projects with no tasks yet.
Question 3 of 5
What condition triggers the "at risk" flag on a project?
✓ At Risk = If([Completion %] < 0.5 And [Days Remaining] < 14, true, false). Less than half done with less than two weeks to go — that combination is worth escalating.
Question 4 of 5
What does the Time Log table enable that the Tasks table alone cannot?
✓ The Time Log records actual hours per task per person. Combined with the Estimate column on Tasks, you can calculate actual vs estimated time and surface over-budget tasks and projects.
Question 5 of 5
What triggers the CRM → PM integration automation, and what does it create?
✓ When a Deal's Stage changes to "Won," the automation creates a Project: Name = "Implementation: " & [Deal Name], Owner = Deal Owner, Start Date = Today(), Status = "Planning." This bridges sales close to delivery kickoff with no manual data entry.
← Previous Building a Full CRM in Coda Lesson 20