Build a simple CRM in Obsidian.
One note per client, a few properties and one Base give you a client list that says who needs a follow-up today. About 20 minutes, no plugins: it’s all built into Obsidian.
What you’ll build
A small CRM doesn’t need a subscription. The useful part is a list that tells you who to contact today, and Obsidian can do that with three pieces:
- One note per client, with a few properties at the top: status, next step, next date and last contact.
- One Base, Obsidian’s built-in database view, that turns those notes into tables: active clients, follow-ups due, and who’s gone quiet.
- A Home note that shows today’s follow-ups, and client pages that list their own invoices automatically.
You need Obsidian 1.9 or later with the Bases core plugin turned on (Settings → Core plugins). Everything below was tested in Obsidian 1.14 with no community plugins.
Step 1: One note per client
Make a folder called Clients and one note per client, named after them. At the very top of each note, add its properties. Obsidian shows them as a tidy form, but underneath they’re plain text:
---
type: client
status: Active
contact: Dana Reyes
monthly_value: 1200
next_step: Send the revised quote
next_date: 2026-10-14
last_contact: 2026-10-09
---type: clientis what the Base looks for, so you can move notes between folders without breaking anything.- Write dates as YYYY-MM-DD. Set the property type to Date (click the icon beside the property name) and you get a date picker.
- Keep the status list short: Lead, Active, Paused, Past. Every extra status is one more thing to keep up to date.
Save the block as Templates/Client.md and turn on the Templates core plugin, so every new client starts the same way.
Step 2: The Clients base
A Base is a few lines of YAML. The quickest way to try one: in any note, type ```base on its own line, paste the code, and finish with ```. The table appears right there. To reuse it on other pages, save the same code as a file called Clients.base in your vault (it’s plain text, so any text editor works).
This version lists your active clients, soonest follow-up first, with a monthly total at the bottom:
filters:
and:
- type == "client"
- '!file.inFolder("Templates")'
formulas:
days_quiet: if(last_contact, (today() - date(last_contact)).days.round(0), "")
properties:
file.name:
displayName: Client
note.monthly_value:
displayName: Monthly $
note.next_step:
displayName: Next step
note.next_date:
displayName: Next date
formula.days_quiet:
displayName: Days quiet
views:
- type: table
name: Active clients
filters:
and:
- status == "Active"
order:
- file.name
- contact
- monthly_value
- next_step
- next_date
- formula.days_quiet
sort:
- property: next_date
direction: ASC
summaries:
monthly_value: Sumfiltersat the top apply to every view: notes withtype: client, skipping your Templates folder. Keep the single quotes: in YAML, a value that starts with!means something else.formulasadd computed columns.today() - date(last_contact)is a length of time,.daysturns it into days, and.round(0)keeps it a whole number. Across a daylight-saving change you’d otherwise see 13.96 days.propertiesonly rename column headers. The prefix matters:note.for your own properties,formula.for formulas,file.for things like the file name.viewseach have their own filters, columns (order), sorting andsummaries.Sumadds a total under the column.
Step 3: Follow-ups due, and who’s gone quiet
Add two more views under views: in the same file:
- type: table
name: Follow-ups due
filters:
and:
- status != "Past"
- if(next_date, date(next_date) <= today(), false)
order:
- file.name
- status
- next_step
- next_date
sort:
- property: next_date
direction: ASC
- type: table
name: Going quiet
filters:
and:
- status == "Active"
- if(last_contact, (today() - date(last_contact)).days > 30, true)
order:
- file.name
- contact
- last_contact
- formula.days_quietFollow-ups due lists everyone whose next date is today or earlier. The if(next_date, …, false) wrapper skips clients with no date set.
Going quiet lists active clients you haven’t spoken to in 30 days. Here the fallback is true, so a client with no contact logged at all shows up too. Update last_contact whenever you talk to someone: that one habit is what makes this view work.
Step 4: A Home page that shows today
Make a note called Home and embed views by name:
## Follow-ups due
![[Clients.base#Follow-ups due]]
## Going quiet
![[Clients.base#Going quiet]]The part after # is the view’s name. Keep Home open in a pinned tab and every day starts with the list.
Step 5: Client pages that fill themselves in
Say you also keep one note per invoice, with a property that links to the client:
---
type: invoice
client: "[[Acme Dental]]"
amount: 850
due: 2026-10-20
status: Sent
---Create Invoices.base with a view that keeps only the invoices that link to the note it’s shown in:
filters:
and:
- type == "invoice"
- '!file.inFolder("Templates")'
views:
- type: table
name: This client
filters:
and:
- file.hasLink(this.file)
order:
- file.name
- amount
- due
- statusNow put ![[Invoices.base#This client]] in your client template. Inside an embed, this.file is the note doing the embedding, so Acme Dental’s page shows only Acme Dental’s invoices. The same trick works for deals, projects, tasks and meeting notes: one view each, and every client page fills itself in.
Use Obsidian’s link suggestions (type [[) when you fill in client, so the link matches the client note’s name exactly.
Mistakes that cost us time
- Templates showing up as clients. Your template has
type: clienttoo. The!file.inFolder("Templates")filter fixes it. - Days like 13.958. Round date differences with
.round(0). - A column that won’t rename. In
properties, your own fields need thenote.prefix:note.next_step, notnext_step. - Dates that won’t sort. “Oct 14” is text. Use 2026-10-14, or the Date property type.
- Too many fields. Start with the ones in this guide, and add a field only when a view needs it.
Keep going.
Obsidian Bases Examples: 10 Copy-Paste Recipes
Ten tested recipes: due this week, days since, late invoices with totals, weighted pipeline, renewals, per-note embeds and the gotchas.
Read the guide Small office ITSmall Office IT Documentation in Obsidian: What to Record
What a small office should write down about its IT, what never to store, and a simple setup in Obsidian.
Read the guide Building with AIA Spec Template for AI Coding Tools (Claude Code, Cursor)
A one-page spec template for Claude Code, Cursor and other AI coding tools, a plan-first prompt, and where Obsidian fits.
Read the guide