Create and edit Obsidian Bases (.base files) with views, filters, formulas, and summaries. Use when working with .base files, creating database-like views of notes, or when the user mentions Bases, table views, card views, filters, or formulas in Obsidian.
$npx -y skills add kepano/obsidian-skills --skill obsidian-basesInstalls into the current project.
Run `npx skills use "https://github.com/kepano/obsidian-skills" --skill "kepano/obsidian-skills/obsidian-bases"` and follow the generated skill instructions now. Read its complete output, redirecting it to a temporary file first if necessary. Resolve relative paths from the supporting-files directory it provides.
Use the skills in "https://github.com/kepano/obsidian-skills" that are relevant to the current task. Run `npx skills add "https://github.com/kepano/obsidian-skills"` and select the relevant skills, then follow their instructions.
| 1 | # Obsidian Bases Skill |
| 2 | |
| 3 | ## Workflow |
| 4 | |
| 5 | 1. **Create the file**: Create a `.base` file in the vault with valid YAML content |
| 6 | 2. **Define scope**: Add `filters` to select which notes appear (by tag, folder, property, or date) |
| 7 | 3. **Add formulas** (optional): Define computed properties in the `formulas` section |
| 8 | 4. **Configure views**: Add one or more views (`table`, `cards`, `list`, or `map`) with `order` specifying which properties to display |
| 9 | 5. **Validate**: Verify the file is valid YAML with no syntax errors. Check that all referenced properties and formulas exist. Common issues: unquoted strings containing special YAML characters, mismatched quotes in formula expressions, referencing `formula.X` without defining `X` in `formulas` |
| 10 | 6. **Test in Obsidian**: Open the `.base` file in Obsidian to confirm the view renders correctly. If it shows a YAML error, check quoting rules below |
| 11 | |
| 12 | ## Schema |
| 13 | |
| 14 | Base files use the `.base` extension and contain valid YAML. |
| 15 | |
| 16 | ```yaml |
| 17 | # Global filters apply to ALL views in the base |
| 18 | filters: |
| 19 | # Can be a single filter string |
| 20 | # OR a recursive filter object with exactly ONE key: and, or, or not |
| 21 | and: |
| 22 | - 'status == "active"' |
| 23 | - not: |
| 24 | - 'file.hasTag("archived")' |
| 25 | |
| 26 | # Define formula properties that can be used across all views |
| 27 | formulas: |
| 28 | formula_name: 'expression' |
| 29 | |
| 30 | # Configure display names and settings for properties |
| 31 | properties: |
| 32 | property_name: |
| 33 | displayName: "Display Name" |
| 34 | formula.formula_name: |
| 35 | displayName: "Formula Display Name" |
| 36 | file.ext: |
| 37 | displayName: "Extension" |
| 38 | |
| 39 | # Define custom summary formulas |
| 40 | summaries: |
| 41 | custom_summary_name: 'values.mean().round(3)' |
| 42 | |
| 43 | # Define one or more views |
| 44 | views: |
| 45 | - type: table | cards | list | map |
| 46 | name: "View Name" |
| 47 | limit: 10 # Optional: limit results |
| 48 | groupBy: # Optional: group results |
| 49 | property: property_name |
| 50 | direction: ASC | DESC |
| 51 | filters: # View-specific filters follow the same rules |
| 52 | and: |
| 53 | - 'status == "active"' |
| 54 | order: # Properties to display in order |
| 55 | - file.name |
| 56 | - property_name |
| 57 | - formula.formula_name |
| 58 | summaries: # Map properties to summary formulas |
| 59 | property_name: Average |
| 60 | ``` |
| 61 | |
| 62 | ## Filter Syntax |
| 63 | |
| 64 | Filters narrow down results. They can be applied globally or per-view. |
| 65 | |
| 66 | ### Filter Structure |
| 67 | |
| 68 | ```yaml |
| 69 | # Single filter |
| 70 | filters: 'status == "done"' |
| 71 | |
| 72 | # AND - all conditions must be true |
| 73 | filters: |
| 74 | and: |
| 75 | - 'status == "done"' |
| 76 | - 'priority > 3' |
| 77 | |
| 78 | # OR - any condition can be true |
| 79 | filters: |
| 80 | or: |
| 81 | - 'file.hasTag("book")' |
| 82 | - 'file.hasTag("article")' |
| 83 | |
| 84 | # NOT - exclude matching items |
| 85 | filters: |
| 86 | not: |
| 87 | - 'file.hasTag("archived")' |
| 88 | |
| 89 | # Nested filters |
| 90 | filters: |
| 91 | or: |
| 92 | - file.hasTag("tag") |
| 93 | - and: |
| 94 | - file.hasTag("book") |
| 95 | - file.hasLink("Textbook") |
| 96 | - not: |
| 97 | - file.hasTag("book") |
| 98 | - file.inFolder("Required Reading") |
| 99 | ``` |
| 100 | |
| 101 | ### Filter Operators |
| 102 | |
| 103 | | Operator | Description | |
| 104 | |----------|-------------| |
| 105 | | `==` | equals | |
| 106 | | `!=` | not equal | |
| 107 | | `>` | greater than | |
| 108 | | `<` | less than | |
| 109 | | `>=` | greater than or equal | |
| 110 | | `<=` | less than or equal | |
| 111 | | `&&` | logical and | |
| 112 | | `\|\|` | logical or | |
| 113 | | <code>!</code> | logical not | |
| 114 | |
| 115 | ## Properties |
| 116 | |
| 117 | ### Three Types of Properties |
| 118 | |
| 119 | 1. **Note properties** - From frontmatter: `note.author` or just `author` |
| 120 | 2. **File properties** - File metadata: `file.name`, `file.mtime`, etc. |
| 121 | 3. **Formula properties** - Computed values: `formula.my_formula` |
| 122 | |
| 123 | ### File Properties Reference |
| 124 | |
| 125 | | Property | Type | Description | |
| 126 | |----------|------|-------------| |
| 127 | | `file.name` | String | File name | |
| 128 | | `file.basename` | String | File name without extension | |
| 129 | | `file.path` | String | Full path to file | |
| 130 | | `file.folder` | String | Parent folder path | |
| 131 | | `file.ext` | String | File extension | |
| 132 | | `file.size` | Number | File size in bytes | |
| 133 | | `file.ctime` | Date | Created time | |
| 134 | | `file.mtime` | Date | Modified time | |
| 135 | | `file.tags` | List | All tags in file | |
| 136 | | `file.links` | List | Internal links in file | |
| 137 | | `file.backlinks` | List | Files linking to this file | |
| 138 | | `file.embeds` | List | Embeds in the note | |
| 139 | | `file.properties` | Object | All frontmatter properties | |
| 140 | |
| 141 | ### The `this` Keyword |
| 142 | |
| 143 | - In main content area: refers to the base file itself |
| 144 | - When embedded: refers to the embedding file |
| 145 | - In sidebar: refers to the active file in main content |
| 146 | |
| 147 | ## Formula Syntax |
| 148 | |
| 149 | Formulas compute values from properties. Defined in the `formulas` section. |
| 150 | |
| 151 | ```yaml |
| 152 | formulas: |
| 153 | # Simple arithmetic |
| 154 | total: "price * quantity" |
| 155 | |
| 156 | # Conditional logic |
| 157 | status_icon: 'if(done, "✅", "⏳")' |
| 158 | |
| 159 | # String formatting |
| 160 | formatted_price: 'if(price, price.toFixed(2) + " dollars")' |
| 161 | |
| 162 | # Date formatting |
| 163 | created: 'file.ctime.format("YYYY-MM-DD")' |
| 164 | |
| 165 | # Calculate days since created (use .days for Duration) |
| 166 | days_old: '(now() - file.ctime).days' |
| 167 | |
| 168 | # Calculate days until due date |
| 169 | days_until_due: 'if(due_date, (date(due_date) - today()).days, "")' |
| 170 | ``` |
| 171 | |
| 172 | ## Key Functions |
| 173 | |
| 174 | Most commonly used functions. For the complete reference of all types (Date, String, Number, List, File, Link, Object, RegExp), see [FUNCTIONS_REFERENCE.md](references/FUNCTIONS_REFERENCE.md). |
| 175 | |
| 176 | | Function | Signature | Description | |
| 177 | |----------|-----------|-------------| |
| 178 | | `date()` | `date(string): date` | Parse string to date (`YYYY-MM-DD HH:mm:ss`) | |
| 179 | | `now()` | `now(): date` | Current date and time | |
| 180 | | `today()` | `today(): date` | Current date (time = 00:00:00) | |
| 181 | | `if()` | `if(condition, trueResult, falseResult?)` | Conditional | |
| 182 | | `duration()` | `duration(string): duration` | Parse duration string | |
| 183 | | `file()` | `file(path): file` | Get file object | |
| 184 | | `link()` | `link(path, display?): Link` | Create a link | |
| 185 | |
| 186 | ### Duration Type |
| 187 | |
| 188 | When subtracting two dates, the result is a **Duration** type (not a number). |
| 189 | |
| 190 | **Duration Fields:** `duration.days`, `duration.hours`, `duration.minutes`, `duration.seconds`, `duration.milliseconds` |
| 191 | |
| 192 | **IMPORTANT:** Duration does NOT support `.round()`, `.floor()`, `.ceil()` directly. Access a numeric field first (like `.days`), then apply number functions. |
| 193 | |
| 194 | ```yaml |
| 195 | # CORRECT: Calculate days between dates |
| 196 | "(date(due_date) - today()).days" # Returns number of days |
| 197 | "(now() - file.ctime).days" # Days since created |
| 198 | "(date(due_date) - today()).days.round(0)" # Rounded days |
| 199 | |
| 200 | # WRONG - will cause error: |
| 201 | # "((date(due) - today()) / 86400000).round(0)" # Duration doesn't support division then round |
| 202 | ``` |
| 203 | |
| 204 | ### Date Arithmetic |
| 205 | |
| 206 | ```yaml |
| 207 | # Duration units: y/year/years, M/month/months, d/day/days, |
| 208 | # w/week/weeks, h/hour/hours, m/minute/minutes, s/second/seconds |
| 209 | "now() + \"1 day\"" # Tomorrow |
| 210 | "today() + \"7d\"" # A week from today |
| 211 | "now() - file.ctime" # Returns Duration |
| 212 | "(now() - file.ctime).days" # Get days as number |
| 213 | ``` |
| 214 | |
| 215 | ## View Types |
| 216 | |
| 217 | ### Table View |
| 218 | |
| 219 | ```yaml |
| 220 | views: |
| 221 | - type: table |
| 222 | name: "My Table" |
| 223 | order: |
| 224 | - file.name |
| 225 | - status |
| 226 | - due_date |
| 227 | summaries: |
| 228 | price: Sum |
| 229 | count: Average |
| 230 | ``` |
| 231 | |
| 232 | ### Cards View |
| 233 | |
| 234 | ```yaml |
| 235 | views: |
| 236 | - type: cards |
| 237 | name: "Gallery" |
| 238 | order: |
| 239 | - file.name |
| 240 | - cover_image |
| 241 | - description |
| 242 | ``` |
| 243 | |
| 244 | ### List View |
| 245 | |
| 246 | ```yaml |
| 247 | views: |
| 248 | - type: list |
| 249 | name: "Simple List" |
| 250 | order: |
| 251 | - file.name |
| 252 | - status |
| 253 | ``` |
| 254 | |
| 255 | ### Map View |
| 256 | |
| 257 | Requires latitude/longitude properties and the Maps community plugin. |
| 258 | |
| 259 | ```yaml |
| 260 | views: |
| 261 | - type: map |
| 262 | name: "Locations" |
| 263 | # Map-specific settings for lat/lng properties |
| 264 | ``` |
| 265 | |
| 266 | ## Default Summary Formulas |
| 267 | |
| 268 | | Name | Input Type | Description | |
| 269 | |------|------------|-------------| |
| 270 | | `Average` | Number | Mathematical mean | |
| 271 | | `Min` | Number | Smallest number | |
| 272 | | `Max` | Number | Largest number | |
| 273 | | `Sum` | Number | Sum of all numbers | |
| 274 | | `Range` | Number | Max - Min | |
| 275 | | `Median` | Number | Mathematical median | |
| 276 | | `Stddev` | Number | Standard deviation | |
| 277 | | `Earliest` | Date | Earliest date | |
| 278 | | `Latest` | Date | Latest date | |
| 279 | | `Range` | Date | Latest - Earliest | |
| 280 | | `Checked` | Boolean | Count of true values | |
| 281 | | `Unchecked` | Boolean | Count of false values | |
| 282 | | `Empty` | Any | Count of empty values | |
| 283 | | `Filled` | Any | Count of non-empty values | |
| 284 | | `Unique` | Any | Count of unique values | |
| 285 | |
| 286 | ## Complete Examples |
| 287 | |
| 288 | ### Task Tracker Base |
| 289 | |
| 290 | ```yaml |
| 291 | filters: |
| 292 | and: |
| 293 | - file.hasTag("task") |
| 294 | - 'file.ext == "md"' |
| 295 | |
| 296 | formulas: |
| 297 | days_until_due: 'if(due, (date(due) - today()).days, "")' |
| 298 | is_overdue: 'if(due, date(due) < today() && status != "done", false)' |
| 299 | priority_label: 'if(priority == 1, "🔴 High", if(priority == 2, "🟡 Medium", "🟢 Low"))' |
| 300 | |
| 301 | properties: |
| 302 | status: |
| 303 | displayName: Status |
| 304 | formula.days_until_due: |
| 305 | displayName: "Days Until Due" |
| 306 | formula.priority_label: |
| 307 | displayName: Priority |
| 308 | |
| 309 | views: |
| 310 | - type: table |
| 311 | name: "Active Tasks" |
| 312 | filters: |
| 313 | and: |
| 314 | - 'status != "done"' |
| 315 | order: |
| 316 | - file.name |
| 317 | - status |
| 318 | - formula.priority_label |
| 319 | - due |
| 320 | - formula.days_until_due |
| 321 | groupBy: |
| 322 | property: status |
| 323 | direction: ASC |
| 324 | summaries: |
| 325 | formula.days_until_due: Average |
| 326 | |
| 327 | - type: table |
| 328 | name: "Completed" |
| 329 | filters: |
| 330 | and: |
| 331 | - 'status == "done"' |
| 332 | order: |
| 333 | - file.name |
| 334 | - completed_date |
| 335 | ``` |
| 336 | |
| 337 | ### Reading List Base |
| 338 | |
| 339 | ```yaml |
| 340 | filters: |
| 341 | or: |
| 342 | - file.hasTag("book") |
| 343 | - file.hasTag("article") |
| 344 | |
| 345 | formulas: |
| 346 | reading_time: 'if(pages, (pages * 2).toString() + " min", "")' |
| 347 | status_icon: 'if(status == "reading", "📖", if(status == "done", "✅", "📚"))' |
| 348 | year_read: 'if(finished_date, date(finished_date).year, "")' |
| 349 | |
| 350 | properties: |
| 351 | author: |
| 352 | displayName: Author |
| 353 | formula.status_icon: |
| 354 | displayName: "" |
| 355 | formula.reading_time: |
| 356 | displayName: "Est. Time" |
| 357 | |
| 358 | views: |
| 359 | - type: cards |
| 360 | name: "Library" |
| 361 | order: |
| 362 | - cover |
| 363 | - file.name |
| 364 | - author |
| 365 | - formula.status_icon |
| 366 | filters: |
| 367 | not: |
| 368 | - 'status == "dropped"' |
| 369 | |
| 370 | - type: table |
| 371 | name: "Reading List" |
| 372 | filters: |
| 373 | and: |
| 374 | - 'status == "to-read"' |
| 375 | order: |
| 376 | - file.name |
| 377 | - author |
| 378 | - pages |
| 379 | - formula.reading_time |
| 380 | ``` |
| 381 | |
| 382 | ### Daily Notes Index |
| 383 | |
| 384 | ```yaml |
| 385 | filters: |
| 386 | and: |
| 387 | - file.inFolder("Daily Notes") |
| 388 | - '/^\d{4}-\d{2}-\d{2}$/.matches(file.basename)' |
| 389 | |
| 390 | formulas: |
| 391 | word_estimate: '(file.size / 5).round(0)' |
| 392 | day_of_week: 'date(file.basename).format("dddd")' |
| 393 | |
| 394 | properties: |
| 395 | formula.day_of_week: |
| 396 | displayName: "Day" |
| 397 | formula.word_estimate: |
| 398 | displayName: "~Words" |
| 399 | |
| 400 | views: |
| 401 | - type: table |
| 402 | name: "Recent Notes" |
| 403 | limit: 30 |
| 404 | order: |
| 405 | - file.name |
| 406 | - formula.day_of_week |
| 407 | - formula.word_estimate |
| 408 | - file.mtime |
| 409 | ``` |
| 410 | |
| 411 | ## Embedding Bases |
| 412 | |
| 413 | Embed in Markdown files: |
| 414 | |
| 415 | ```markdown |
| 416 | ![[MyBase.base]] |
| 417 | |
| 418 | <!-- Specific view --> |
| 419 | ![[MyBase.base#View Name]] |
| 420 | ``` |
| 421 | |
| 422 | ## YAML Quoting Rules |
| 423 | |
| 424 | - Use single quotes for formulas containing double quotes: `'if(done, "Yes", "No")'` |
| 425 | - Use double quotes for simple strings: `"My View Name"` |
| 426 | - Escape nested quotes properly in complex expressions |
| 427 | |
| 428 | ## Troubleshooting |
| 429 | |
| 430 | ### YAML Syntax Errors |
| 431 | |
| 432 | **Unquoted special characters**: Strings containing `:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` `` must be quoted. |
| 433 | |
| 434 | ```yaml |
| 435 | # WRONG - colon in unquoted string |
| 436 | displayName: Status: Active |
| 437 | |
| 438 | # CORRECT |
| 439 | displayName: "Status: Active" |
| 440 | ``` |
| 441 | |
| 442 | **Mismatched quotes in formulas**: When a formula contains double quotes, wrap the entire formula in single quotes. |
| 443 | |
| 444 | ```yaml |
| 445 | # WRONG - double quotes inside double quotes |
| 446 | formulas: |
| 447 | label: "if(done, "Yes", "No")" |
| 448 | |
| 449 | # CORRECT - single quotes wrapping double quotes |
| 450 | formulas: |
| 451 | label: 'if(done, "Yes", "No")' |
| 452 | ``` |
| 453 | |
| 454 | ### Common Formula Errors |
| 455 | |
| 456 | **Duration math without field access**: Subtracting dates returns a Duration, not a number. Always access `.days`, `.hours`, etc. |
| 457 | |
| 458 | ```yaml |
| 459 | # WRONG - Duration is not a number |
| 460 | "(now() - file.ctime).round(0)" |
| 461 | |
| 462 | # CORRECT - access .days first, then round |
| 463 | "(now() - file.ctime).days.round(0)" |
| 464 | ``` |
| 465 | |
| 466 | **Missing null checks**: Properties may not exist on all notes. Use `if()` to guard. |
| 467 | |
| 468 | ```yaml |
| 469 | # WRONG - crashes if due_date is empty |
| 470 | "(date(due_date) - today()).days" |
| 471 | |
| 472 | # CORRECT - guard with if() |
| 473 | 'if(due_date, (date(due_date) - today()).days, "")' |
| 474 | ``` |
| 475 | |
| 476 | **Referencing undefined formulas**: Ensure every `formula.X` in `order` or `properties` has a matching entry in `formulas`. |
| 477 | |
| 478 | ```yaml |
| 479 | # This will fail silently if 'total' is not defined in formulas |
| 480 | order: |
| 481 | - formula.total |
| 482 | |
| 483 | # Fix: define it |
| 484 | formulas: |
| 485 | total: "price * quantity" |
| 486 | ``` |
| 487 | |
| 488 | ## References |
| 489 | |
| 490 | - [Bases Syntax](https://help.obsidian.md/bases/syntax) |
| 491 | - [Functions](https://help.obsidian.md/bases/functions) |
| 492 | - [Views](https://help.obsidian.md/bases/views) |
| 493 | - [Formulas](https://help.obsidian.md/formulas) |
| 494 | - [Complete Functions Reference](references/FUNCTIONS_REFERENCE.md) |