# Bases

> Work with Obsidian Bases for structured data and database-like views. Use when user asks about creating bases, defining schemas, filtering data, or working with structured note collections.

- Skill: `s2b-dev/bases` (Agent Skill)
- Install (CLI): `npx skillmds@latest add s2b-dev/bases`
- Raw SKILL.md: https://api.skillmd.com/api/skills/s2b-dev/bases/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: s2b-dev (https://skillmd.com/u/s2b-dev)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/s2b-dev/bases

---


# Obsidian Bases Skill

This skill enables skills-compatible agents to create and edit valid Obsidian Bases (`.base` files) including views, filters, formulas, and all related configurations.

## Overview

Obsidian Bases are YAML-based files that define dynamic views of notes in an Obsidian vault. A Base file can contain multiple views, global filters, formulas, property configurations, and custom summaries.

## File Format

Base files use the `.base` extension and contain valid YAML. They can also be embedded in Markdown code blocks.

## Complete Schema

```yaml
# Global filters apply to ALL views in the base
filters:
  # Can be a single filter string
  # OR a recursive filter object with and/or/not
  and: []
  or: []
  not: []

# Define formula properties that can be used across all views
formulas:
  formula_name: "expression"

# Configure display names and settings for properties
properties:
  property_name:
    displayName: "Display Name"
  formula.formula_name:
    displayName: "Formula Display Name"
  file.ext:
    displayName: "Extension"

# Define custom summary formulas
summaries:
  custom_summary_name: "values.mean().round(3)"

# Define one or more views
views:
  - type: table | cards | list | map
    name: "View Name"
    limit: 10 # Optional: limit results
    groupBy: # Optional: group results
      property: property_name
      direction: ASC | DESC
    filters: # View-specific filters
      and: []
    order: # Properties to display in order
      - file.name
      - property_name
      - formula.formula_name
    summaries: # Map properties to summary formulas
      property_name: Average
```

## Filter Syntax

Filters narrow down results. They can be applied globally or per-view.

### Filter Structure

```yaml
# Single filter
filters: 'status == "done"'

# AND - all conditions must be true
filters:
  and:
    - 'status == "done"'
    - 'priority > 3'

# OR - any condition can be true
filters:
  or:
    - 'file.hasTag("book")'
    - 'file.hasTag("article")'

# NOT - exclude matching items
filters:
  not:
    - 'file.hasTag("archived")'

# Nested filters
filters:
  or:
    - file.hasTag("tag")
    - and:
        - file.hasTag("book")
        - file.hasLink("Textbook")
    - not:
        - file.hasTag("book")
        - file.inFolder("Required Reading")
```

### Filter Operators

| Operator       | Description           |
| -------------- | --------------------- |
| `==`           | equals                |
| `!=`           | not equal             |
| `>`            | greater than          |
| `<`            | less than             |
| `>=`           | greater than or equal |
| `<=`           | less than or equal    |
| `&&`           | logical and           |
| `\|\|`         | logical or            |
| <code>!</code> | logical not           |

## Properties

### Three Types of Properties

1. **Note properties** - From frontmatter: `note.author` or just `author`
2. **File properties** - File metadata: `file.name`, `file.mtime`, etc.
3. **Formula properties** - Computed values: `formula.my_formula`

### File Properties Reference

| Property          | Type   | Description                 |
| ----------------- | ------ | --------------------------- |
| `file.name`       | String | File name                   |
| `file.basename`   | String | File name without extension |
| `file.path`       | String | Full path to file           |
| `file.folder`     | String | Parent folder path          |
| `file.ext`        | String | File extension              |
| `file.size`       | Number | File size in bytes          |
| `file.ctime`      | Date   | Created time                |
| `file.mtime`      | Date   | Modified time               |
| `file.tags`       | List   | All tags in file            |
| `file.links`      | List   | Internal links in file      |
| `file.backlinks`  | List   | Files linking to this file  |
| `file.embeds`     | List   | Embeds in the note          |
| `file.properties` | Object | All frontmatter properties  |

### The `this` Keyword

- In main content area: refers to the base file itself
- When embedded: refers to the embedding file
- In sidebar: refers to the active file in main content

## Formula Syntax

Formulas compute values from properties. Defined in the `formulas` section.

```yaml
formulas:
  # Simple arithmetic
  total: "price * quantity"

  # Conditional logic
  status_icon: 'if(done, "✅", "⏳")'

  # String formatting
  formatted_price: 'if(price, price.toFixed(2) + " dollars")'

  # Date formatting
  created: 'file.ctime.format("YYYY-MM-DD")'

  # Calculate days since created (use .days for Duration)
  days_old: "(now() - file.ctime).days"

  # Calculate days until due date
  days_until_due: 'if(due_date, (date(due_date) - today()).days, "")'
```

## Functions Reference

### Global Functions

| Function       | Signature                                 | Description                                         |
| -------------- | ----------------------------------------- | --------------------------------------------------- |
| `date()`       | `date(string): date`                      | Parse string to date. Format: `YYYY-MM-DD HH:mm:ss` |
| `duration()`   | `duration(string): duration`              | Parse duration string                               |
| `now()`        | `now(): date`                             | Current date and time                               |
| `today()`      | `today(): date`                           | Current date (time = 00:00:00)                      |
| `if()`         | `if(condition, trueResult, falseResult?)` | Conditional                                         |
| `min()`        | `min(n1, n2, ...): number`                | Smallest number                                     |
| `max()`        | `max(n1, n2, ...): number`                | Largest number                                      |
| `number()`     | `number(any): number`                     | Convert to number                                   |
| `link()`       | `link(path, display?): Link`              | Create a link                                       |
| `list()`       | `list(element): List`                     | Wrap in list if not already                         |
| `file()`       | `file(path): file`                        | Get file object                                     |
| `image()`      | `image(path): image`                      | Create image for rendering                          |
| `icon()`       | `icon(name): icon`                        | Lucide icon by name                                 |
| `html()`       | `html(string): html`                      | Render as HTML                                      |
| `escapeHTML()` | `escapeHTML(string): string`              | Escape HTML characters                              |

### Any Type Functions

| Function     | Signature                   | Description       |
| ------------ | --------------------------- | ----------------- |
| `isTruthy()` | `any.isTruthy(): boolean`   | Coerce to boolean |
| `isType()`   | `any.isType(type): boolean` | Check type        |
| `toString()` | `any.toString(): string`    | Convert to string |

### Date Functions & Fields

**Fields:** `date.year`, `date.month`, `date.day`, `date.hour`, `date.minute`, `date.second`, `date.millisecond`

| Function     | Signature                     | Description                   |
| ------------ | ----------------------------- | ----------------------------- |
| `date()`     | `date.date(): date`           | Remove time portion           |
| `format()`   | `date.format(string): string` | Format with Moment.js pattern |
| `time()`     | `date.time(): string`         | Get time as string            |
| `relative()` | `date.relative(): string`     | Human-readable relative time  |
| `isEmpty()`  | `date.isEmpty(): boolean`     | Always false for dates        |

### Duration Type

When subtracting two dates, the result is a **Duration** type (not a number). Duration has its own properties and methods.

**Duration Fields:**
| Field | Type | Description |
|-------|------|-------------|
| `duration.days` | Number | Total days in duration |
| `duration.hours` | Number | Total hours in duration |
| `duration.minutes` | Number | Total minutes in duration |
| `duration.seconds` | Number | Total seconds in duration |
| `duration.milliseconds` | Number | Total milliseconds in duration |

**IMPORTANT:** Duration does NOT support `.round()`, `.floor()`, `.ceil()` directly. You must access a numeric field first (like `.days`), then apply number functions.

```yaml
# CORRECT: Calculate days between dates
"(date(due_date) - today()).days"                    # Returns number of days
"(now() - file.ctime).days"                          # Days since created

# CORRECT: Round the numeric result if needed
"(date(due_date) - today()).days.round(0)"           # Rounded days
"(now() - file.ctime).hours.round(0)"                # Rounded hours

# WRONG - will cause error:
# "((date(due) - today()) / 86400000).round(0)"      # Duration doesn't support division then round
```

### Date Arithmetic

```yaml
# Duration units: y/year/years, M/month/months, d/day/days,
#                 w/week/weeks, h/hour/hours, m/minute/minutes, s/second/seconds

# Add/subtract durations
"date + \"1M\""           # Add 1 month
"date - \"2h\""           # Subtract 2 hours
"now() + \"1 day\""       # Tomorrow
"today() + \"7d\""        # A week from today

# Subtract dates returns Duration type
"now() - file.ctime"                    # Returns Duration
"(now() - file.ctime).days"             # Get days as number
"(now() - file.ctime).hours"            # Get hours as number

# Complex duration arithmetic
"now() + (duration('1d') * 2)"
```

### String Functions

**Field:** `string.length`

| Function        | Signature                                      | Description            |
| --------------- | ---------------------------------------------- | ---------------------- |
| `contains()`    | `string.contains(value): boolean`              | Check substring        |
| `containsAll()` | `string.containsAll(...values): boolean`       | All substrings present |
| `containsAny()` | `string.containsAny(...values): boolean`       | Any substring present  |
| `startsWith()`  | `string.startsWith(query): boolean`            | Starts with query      |
| `endsWith()`    | `string.endsWith(query): boolean`              | Ends with query        |
| `isEmpty()`     | `string.isEmpty(): boolean`                    | Empty or not present   |
| `lower()`       | `string.lower(): string`                       | To lowercase           |
| `title()`       | `string.title(): string`                       | To Title Case          |
| `trim()`        | `string.trim(): string`                        | Remove whitespace      |
| `replace()`     | `string.replace(pattern, replacement): string` | Replace pattern        |
| `repeat()`      | `string.repeat(count): string`                 | Repeat string          |
| `reverse()`     | `string.reverse(): string`                     | Reverse string         |
| `slice()`       | `string.slice(start, end?): string`            | Substring              |
| `split()`       | `string.split(separator, n?): list`            | Split to list          |

### Number Functions

| Function    | Signature                           | Description          |
| ----------- | ----------------------------------- | -------------------- |
| `abs()`     | `number.abs(): number`              | Absolute value       |
| `ceil()`    | `number.ceil(): number`             | Round up             |
| `floor()`   | `number.floor(): number`            | Round down           |
| `round()`   | `number.round(digits?): number`     | Round to digits      |
| `toFixed()` | `number.toFixed(precision): string` | Fixed-point notation |
| `isEmpty()` | `number.isEmpty(): boolean`         | Not present          |

### List Functions

**Field:** `list.length`

| Function        | Signature                               | Description                                           |
| --------------- | --------------------------------------- | ----------------------------------------------------- |
| `contains()`    | `list.contains(value): boolean`         | Element exists                                        |
| `containsAll()` | `list.containsAll(...values): boolean`  | All elements exist                                    |
| `containsAny()` | `list.containsAny(...values): boolean`  | Any element exists                                    |
| `filter()`      | `list.filter(expression): list`         | Filter by condition (uses `value`, `index`)           |
| `map()`         | `list.map(expression): list`            | Transform elements (uses `value`, `index`)            |
| `reduce()`      | `list.reduce(expression, initial): any` | Reduce to single value (uses `value`, `index`, `acc`) |
| `flat()`        | `list.flat(): list`                     | Flatten nested lists                                  |
| `join()`        | `list.join(separator): string`          | Join to string                                        |
| `reverse()`     | `list.reverse(): list`                  | Reverse order                                         |
| `slice()`       | `list.slice(start, end?): list`         | Sublist                                               |
| `sort()`        | `list.sort(): list`                     | Sort ascending                                        |
| `unique()`      | `list.unique(): list`                   | Remove duplicates                                     |
| `isEmpty()`     | `list.isEmpty(): boolean`               | No elements                                           |

### File Functions

| Function        | Signature                          | Description            |
| --------------- | ---------------------------------- | ---------------------- |
| `asLink()`      | `file.asLink(display?): Link`      | Convert to link        |
| `hasLink()`     | `file.hasLink(otherFile): boolean` | Has link to file       |
| `hasTag()`      | `file.hasTag(...tags): boolean`    | Has any of the tags    |
| `hasProperty()` | `file.hasProperty(name): boolean`  | Has property           |
| `inFolder()`    | `file.inFolder(folder): boolean`   | In folder or subfolder |

### Link Functions

| Function    | Signature                     | Description     |
| ----------- | ----------------------------- | --------------- |
| `asFile()`  | `link.asFile(): file`         | Get file object |
| `linksTo()` | `link.linksTo(file): boolean` | Links to file   |

### Object Functions

| Function    | Signature                   | Description    |
| ----------- | --------------------------- | -------------- |
| `isEmpty()` | `object.isEmpty(): boolean` | No properties  |
| `keys()`    | `object.keys(): list`       | List of keys   |
| `values()`  | `object.values(): list`     | List of values |

### Regular Expression Functions

| Function    | Signature                         | Description     |
| ----------- | --------------------------------- | --------------- |
| `matches()` | `regexp.matches(string): boolean` | Test if matches |

## View Types

### Table View

```yaml
views:
  - type: table
    name: "My Table"
    order:
      - file.name
      - status
      - due_date
    summaries:
      price: Sum
      count: Average
```

### Cards View

```yaml
views:
  - type: cards
    name: "Gallery"
    order:
      - file.name
      - cover_image
      - description
```

### List View

```yaml
views:
  - type: list
    name: "Simple List"
    order:
      - file.name
      - status
```

### Map View

Requires latitude/longitude properties and the Maps community plugin.

```yaml
views:
  - type: map
    name: "Locations"
    # Map-specific settings for lat/lng properties
```

## Default Summary Formulas

| Name        | Input Type | Description               |
| ----------- | ---------- | ------------------------- |
| `Average`   | Number     | Mathematical mean         |
| `Min`       | Number     | Smallest number           |
| `Max`       | Number     | Largest number            |
| `Sum`       | Number     | Sum of all numbers        |
| `Range`     | Number     | Max - Min                 |
| `Median`    | Number     | Mathematical median       |
| `Stddev`    | Number     | Standard deviation        |
| `Earliest`  | Date       | Earliest date             |
| `Latest`    | Date       | Latest date               |
| `Range`     | Date       | Latest - Earliest         |
| `Checked`   | Boolean    | Count of true values      |
| `Unchecked` | Boolean    | Count of false values     |
| `Empty`     | Any        | Count of empty values     |
| `Filled`    | Any        | Count of non-empty values |
| `Unique`    | Any        | Count of unique values    |

## Complete Examples

### Task Tracker Base

```yaml
filters:
  and:
    - file.hasTag("task")
    - 'file.ext == "md"'

formulas:
  days_until_due: 'if(due, (date(due) - today()).days, "")'
  is_overdue: 'if(due, date(due) < today() && status != "done", false)'
  priority_label: 'if(priority == 1, "🔴 High", if(priority == 2, "🟡 Medium", "🟢 Low"))'

properties:
  status:
    displayName: Status
  formula.days_until_due:
    displayName: "Days Until Due"
  formula.priority_label:
    displayName: Priority

views:
  - type: table
    name: "Active Tasks"
    filters:
      and:
        - 'status != "done"'
    order:
      - file.name
      - status
      - formula.priority_label
      - due
      - formula.days_until_due
    groupBy:
      property: status
      direction: ASC
    summaries:
      formula.days_until_due: Average

  - type: table
    name: "Completed"
    filters:
      and:
        - 'status == "done"'
    order:
      - file.name
      - completed_date
```

### Reading List Base

```yaml
filters:
  or:
    - file.hasTag("book")
    - file.hasTag("article")

formulas:
  reading_time: 'if(pages, (pages * 2).toString() + " min", "")'
  status_icon: 'if(status == "reading", "📖", if(status == "done", "✅", "📚"))'
  year_read: 'if(finished_date, date(finished_date).year, "")'

properties:
  author:
    displayName: Author
  formula.status_icon:
    displayName: ""
  formula.reading_time:
    displayName: "Est. Time"

views:
  - type: cards
    name: "Library"
    order:
      - cover
      - file.name
      - author
      - formula.status_icon
    filters:
      not:
        - 'status == "dropped"'

  - type: table
    name: "Reading List"
    filters:
      and:
        - 'status == "to-read"'
    order:
      - file.name
      - author
      - pages
      - formula.reading_time
```

### Project Notes Base

```yaml
filters:
  and:
    - file.inFolder("Projects")
    - 'file.ext == "md"'

formulas:
  last_updated: "file.mtime.relative()"
  link_count: "file.links.length"

summaries:
  avgLinks: 'values.filter(value.isType("number")).mean().round(1)'

properties:
  formula.last_updated:
    displayName: "Updated"
  formula.link_count:
    displayName: "Links"

views:
  - type: table
    name: "All Projects"
    order:
      - file.name
      - status
      - formula.last_updated
      - formula.link_count
    summaries:
      formula.link_count: avgLinks
    groupBy:
      property: status
      direction: ASC

  - type: list
    name: "Quick List"
    order:
      - file.name
      - status
```

### Daily Notes Index

```yaml
filters:
  and:
    - file.inFolder("Daily Notes")
    - '/^\d{4}-\d{2}-\d{2}$/.matches(file.basename)'

formulas:
  word_estimate: "(file.size / 5).round(0)"
  day_of_week: 'date(file.basename).format("dddd")'

properties:
  formula.day_of_week:
    displayName: "Day"
  formula.word_estimate:
    displayName: "~Words"

views:
  - type: table
    name: "Recent Notes"
    limit: 30
    order:
      - file.name
      - formula.day_of_week
      - formula.word_estimate
      - file.mtime
```

## Embedding Bases

Embed in Markdown files:

```markdown
![[MyBase.base]]

<!-- Specific view -->

![[MyBase.base#View Name]]
```

## YAML Quoting Rules

- Use single quotes for formulas containing double quotes: `'if(done, "Yes", "No")'`
- Use double quotes for simple strings: `"My View Name"`
- Escape nested quotes properly in complex expressions

## Common Patterns

### Filter by Tag

```yaml
filters:
  and:
    - file.hasTag("project")
```

### Filter by Folder

```yaml
filters:
  and:
    - file.inFolder("Notes")
```

### Filter by Date Range

```yaml
filters:
  and:
    - 'file.mtime > now() - "7d"'
```

### Filter by Property Value

```yaml
filters:
  and:
    - 'status == "active"'
    - "priority >= 3"
```

### Combine Multiple Conditions

```yaml
filters:
  or:
    - and:
        - file.hasTag("important")
        - 'status != "done"'
    - and:
        - "priority == 1"
        - 'due != ""'
```

## References

- [Bases Syntax](https://help.obsidian.md/bases/syntax)
- [Functions](https://help.obsidian.md/bases/functions)
- [Views](https://help.obsidian.md/bases/views)
- [Formulas](https://help.obsidian.md/formulas)

