Bookings[email protected] San Antonio · Texas
Obsidian guide · Clients and follow-ups

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.

Updated October 2026 · Tested in Obsidian 1.14 · No plugins

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:

Clients/Acme Dental.md
---
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: client is 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:

Clients.base
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: Sum
  • filters at the top apply to every view: notes with type: client, skipping your Templates folder. Keep the single quotes: in YAML, a value that starts with ! means something else.
  • formulas add computed columns. today() - date(last_contact) is a length of time, .days turns it into days, and .round(0) keeps it a whole number. Across a daylight-saving change you’d otherwise see 13.96 days.
  • properties only rename column headers. The prefix matters: note. for your own properties, formula. for formulas, file. for things like the file name.
  • views each have their own filters, columns (order), sorting and summaries. Sum adds 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:

Clients.base (add under views:)
  - 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_quiet

Follow-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:

Home.md
## 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:

Invoices/INV-1042.md
---
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:

Invoices.base
filters:
  and:
    - type == "invoice"
    - '!file.inFolder("Templates")'
views:
  - type: table
    name: This client
    filters:
      and:
        - file.hasLink(this.file)
    order:
      - file.name
      - amount
      - due
      - status

Now 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: client too. 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 the note. prefix: note.next_step, not next_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.