commit 2673787272dd091469f5fc6f80c7a7228f5f6dbb Author: Steph Ango <10565871+kepano@users.noreply.github.com> Date: Fri Jan 2 13:54:00 2026 -0800 Bases diff --git a/obsidian-bases/SKILL.md b/obsidian-bases/SKILL.md new file mode 100644 index 0000000..9bc6935 --- /dev/null +++ b/obsidian-bases/SKILL.md @@ -0,0 +1,614 @@ +# Obsidian Bases Skill + +This skill enables Claude Code 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 | +| `!` | 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")' + + # Complex expressions + days_old: '((now() - file.ctime) / 86400000).round(0)' +``` + +## 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 | + +### 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 for millisecond difference +"now() - file.ctime" + +# 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 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()) / 86400000).round(0), "")' + 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]] + + +![[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) +