Bookings[email protected] San Antonio · Texas
Obsidian guide · Bases recipes

Obsidian Bases examples you can copy.

Bases turns the properties in your notes into tables and cards, with filters, formulas and totals, and no plugins. Here are ten recipes from our own vaults, each ready to paste, plus the mistakes that cost us time.

Updated October 2026 · Tested in Obsidian 1.14 · No plugins

How to use these recipes

Bases is Obsidian’s built-in database view, a core plugin since version 1.9. It reads the properties at the top of your notes. Every recipe here was checked in Obsidian 1.14.

To try one, type ```base on its own line in any note, paste the recipe, and finish with ```. Or save it as a .base file in your vault: it’s plain text. Two recipes are just a formulas block; add those to a base like recipe 1.

1. Start here: one kind of note, minus templates

Tasks.base
filters:
  and:
    - type == "task"
    - '!file.inFolder("Templates")'
views:
  - type: table
    name: All tasks
    order:
      - file.name
      - status
      - due

Filter on a type property rather than a folder, so notes can live anywhere. The second line keeps your templates out of every view. Its single quotes matter: in YAML, a value starting with ! means something else.

2. Due today, and due this week

Tasks.base
filters:
  and:
    - type == "task"
    - '!file.inFolder("Templates")'
views:
  - type: table
    name: Today
    filters:
      and:
        - status != "Done"
        - if(due, date(due) <= today(), false)
    order:
      - file.name
      - due
    sort:
      - property: due
        direction: ASC
  - type: table
    name: This week
    filters:
      and:
        - status != "Done"
        - if(due, (date(due) - today()).days <= 7, false)
    order:
      - file.name
      - status
      - due
    sort:
      - property: due
        direction: ASC

Wrapping the date test in if(due, …, false) skips tasks with no due date. “Today” also catches anything overdue, which is usually what you want first thing.

3. Days since something happened

formulas:
  days_quiet: if(last_contact, (today() - date(last_contact)).days.round(0), "")

Subtracting two dates gives a length of time, and .days turns it into a number of days. Always round it: across a daylight-saving change the gap isn’t a whole number of days.

The same test works as a view filter: if(last_contact, (today() - date(last_contact)).days > 30, true) lists everyone you haven’t spoken to in a month, including anyone with no date at all.

4. A countdown in words

formulas:
  follow_up: if(next_date, if(date(next_date) <= today(), "⏰ Due", "in " + (date(next_date) - today()).days.round(0).toString() + " days"), "")

Shows “⏰ Due” or “in 3 days”. .toString() turns a number into text and + joins text. Nest more if()s for more cases.

5. Late invoices, with a total

Invoices.base
filters:
  and:
    - type == "invoice"
    - '!file.inFolder("Templates")'
formulas:
  days_late: if(status == "Sent", if(due, if(date(due) < today(), (today() - date(due)).days.round(0), ""), ""), "")
views:
  - type: table
    name: Late
    filters:
      and:
        - status == "Sent"
        - if(due, date(due) < today(), false)
    order:
      - file.name
      - client
      - amount
      - due
      - formula.days_late
    summaries:
      amount: Sum
  - type: table
    name: Paid this year
    filters:
      and:
        - status == "Paid"
        - if(paid_on, date(paid_on).year == today().year, false)
    order:
      - file.name
      - client
      - amount
      - paid_on
    summaries:
      amount: Sum

summaries adds a row under the table, and Sum totals the column. The second view uses .year to keep only this year’s payments.

6. Weighted pipeline value

Pipeline.base
filters:
  and:
    - type == "deal"
    - '!file.inFolder("Templates")'
formulas:
  weighted: if(value, value * if(probability, probability, 0) / 100, 0)
views:
  - type: table
    name: Open deals
    filters:
      and:
        - stage != "Won"
        - stage != "Lost"
    order:
      - file.name
      - value
      - probability
      - formula.weighted
    summaries:
      value: Sum
      formula.weighted: Sum

A $10,000 deal at 30% counts as $3,000. The inner if() treats a missing probability as zero, so one blank field doesn’t spoil the total.

7. Groups in your order, not alphabetical

Pipeline.base
filters:
  and:
    - type == "deal"
    - '!file.inFolder("Templates")'
formulas:
  stage_step: if(stage == "Lead", "1 Lead", if(stage == "Proposal sent", "2 Proposal sent", if(stage == "Negotiating", "3 Negotiating", if(stage == "Won", "4 Won", "0 No stage"))))
views:
  - type: cards
    name: Board
    filters:
      and:
        - stage != "Lost"
    groupBy:
      property: formula.stage_step
      direction: ASC
    order:
      - file.name
      - value

Groups sort by name, so “Negotiating” would land before “Proposal sent”. A formula that puts a number in front keeps them in the order work actually flows.

8. Renewals with a traffic light

Renewals.base
filters:
  and:
    - type == "renewal"
    - '!file.inFolder("Templates")'
formulas:
  days_left: if(renews_on, (date(renews_on) - today()).days.round(0), "")
  alert: if(!renews_on, "", if(date(renews_on) < today(), "🔴 Lapsed", if((date(renews_on) - today()).days.round(0) <= 30, "🟡 Soon", "🟢 Later")))
views:
  - type: table
    name: Next 60 days
    filters:
      and:
        - if(renews_on, (date(renews_on) - today()).days <= 60, false)
    order:
      - file.name
      - renews_on
      - formula.days_left
      - auto_renew
      - cost
      - formula.alert
    sort:
      - property: renews_on
        direction: ASC
    summaries:
      cost: Sum

!renews_on means “has no date”. The view shows the next 60 days, including anything already lapsed, and totals what it will cost.

9. Flag what’s missing

Devices.base
filters:
  and:
    - type == "device"
    - '!file.inFolder("Templates")'
formulas:
  warranty: if(!warranty_until, "", if(date(warranty_until) < today(), "Expired", if((date(warranty_until) - today()).days.round(0) <= 60, "Ends soon", "OK")))
  problems: if(!encrypted, "Not encrypted. ", "") + if(!backup, "No backup. ", "") + if(!auto_updates, "Updates off.", "")
properties:
  file.name:
    displayName: Device
  note.assigned_to:
    displayName: Assigned to
  formula.problems:
    displayName: Needs
views:
  - type: table
    name: Needs attention
    filters:
      and:
        - status == "In use"
        - '!encrypted || !backup || !auto_updates'
    order:
      - file.name
      - assigned_to
      - formula.problems
  - type: table
    name: All devices
    filters:
      and:
        - status != "Retired"
    groupBy:
      property: kind
      direction: ASC
    order:
      - file.name
      - assigned_to
      - status
      - formula.warranty

For true/false (checkbox) properties, !encrypted means “not ticked” and || means “or”. The problems formula joins short notes with +, so each row reads like a to-do. Quote any filter that starts with !.

10. One base, many pages

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

Embed it in a client note with ![[Invoices.base#This client]]. Inside an embed, this.file is the note doing the embedding, so each client page lists only its own invoices (any invoice whose properties link to that client). Put the embed in your template once and every new page gets it.

Gotchas and a quick reference

  • Renaming columns: under properties, use note. for your fields, formula. for formulas and file. for file details.
  • Dates: today(), date(x) to read a property as a date, .days for a difference in days, .year for the year.
  • Logic: if(test, yes, no), && for and, || for or, ! for not.
  • Files: file.name, file.inFolder("…"), file.hasLink(this.file).
  • Empty values: guard with if(field, …, fallback) before doing date maths.
  • Embeds: ![[File.base#View name]] shows one view; the name must match exactly.