Moving from Dataview and inline metadata to Bases, YAML and Datacore
By Rob Coles
The Problem
I’ve been using Obsidian since 2021. When I started, Dataview was the best way to query my notes. I liked the use of inline fields, e.g. “Next::”, as it kept all of the information within the context of the note. I built a bunch of workflow around Dataview and these inline fields, and it has served me well. But…
I’ve noticed a lag in keystrokes when typing into pages with heavy Dataview queries, which includes my daily notes. Also Dataview is no longer undergoing active development. The developer is focused on developing a successor, “Datacore”, which offers much better performance, but a completely different way of defining queries.
The Obsidian team has recently unveiled “Bases” - a core plugin which does a lot of what Dataview can do, with ongoing support. At the time of writing, Bases is still in beta, and only available to Obsidian Catalyst members, but it should become generally available soon.
Bases is a great feature, but the main problem for me in using it is that, in keeping with their general standards-based philosophy, it uses YAML frontmatter in its queries, not the inline fields I have been using. The inline fields are standard to Dataview, but nothing else. In order to move ahead with Bases I needed to convert my inline fields to YAML frontmatter. I also wanted to find a way of at least displaying some of the fields inline. Only having YAML fields visible at the top of the note, or in a separate panel wasn’t ideal for my workflows.
Before:
After:
From Inline to YAML
In order to be able to use any of my “inline” fields in Bases, they needed to be converted to YAML frontmatter. This is more standard for Markdown, and is cached as part of the Obsidian note metadata, so accessing the information should be fast. Also Dataview treats inline fields and YAML frontmatter the same, so I could switch from inline to YAML and the Dataview queries would still work the same. letting me work on replacing the queries separately.
My biggest obstacle in doing the conversion was losing visibility of the fields within the note. I wanted a way to at least see the values of the YAML in the body of the note. Another plugin, “Meta Bind,” came to the rescue here as it allowed me to add the frontmatter fields to the body of the note and synchronised the display and the frontmatter allowing me to change either.
I looked at a few different methods to go from inline fields to YAML. There are options including plug-ins and coding scripts myself. Because I wanted to replace the inline fields with meta bind text, and move the keys and values to YAML at the same time, and also wanted to see what AI could do, I decided to try getting AI to do the conversion for me.
Having first backed up the whole Obsidian folder, I asked Claude to write a script to do the conversion for me. The actual prompt I used was:
I use Obsidian and markdown files, and currently have data inline in my
notes in the form "key:: value". I want to move these key/value pairs to
YAML frontmatter in the same markdown file. There may already be
frontmatter or not.
I want to replace the key:: value string itself with a meta bind string
that will display the frontmatter content. Something like
"`INPUT(number:key)`\n"
Please generate a python script that will recurse through all markdown
files in the current directory and all subdirectories looking for a
specific inline key , extract the key and value to YAML and replace the
original key:: value with a specified metabind string. The key and
metabind string should be able to be prompted at runtime.
I wanted to be able to do the conversion one inline value at a time, at least initially so I could make sure I wasn’t doing anything dim. Claude wrote the python code for me, together with instructions on how to set up the environment and run the script. I wanted to be able to specify the Meta Bind string individually, as this is different for numbers, text and toggles.
I didn’t have too many different keys and the code that Claude had generated worked well, so I just ran the script for each key separately. This was the first step.
I also had been tracking habits with inline checkboxes. I wanted to convert these to YAML checkboxes, and again Claude generated a python script to convert them for me.
The script did what I asked, but I had forgotten that I had occasionally editorialised after the checkbox, e.g. Adding “Ben and Jerry’s” after the snack checkbox. I found this while trying to add properties in the Bases prompt. Instead of the property “NoSnack” it had created “NoSnackBenandJerrys”. There weren’t too many of these errors, so I dealt with them manually after asking Claude to write a script to identify all unique frontmatter key values.
From Dataview to Bases
My Dataview queries were mostly still running fine with the switch from inline fields to YAML frontmatter. I still had the stuttery keyboard input , and the dependence on Dataview so it was time to see what Bases could replace.
Most of my Dataview queries were simple - just a table with a straightforward selection. I used the Dataview converter to do a quick conversion and tweaked the output a bit. For the majority of the queries, this was all that was needed. There was a bit of tinkering to get date comparisons to work as I wanted, especially for the daily note. For the daily note “on this day” query I ended up with:
properties:
file.name:
displayName: Name
note.summary:
displayName: summary
file.day:
displayName: file.day
views:
- type: table
name: Today in History
filters:
and:
- file.inFolder("Journal")
- file.name.slice(0, 5) != this.file.name.slice(0, 5)
- file.name.slice(8, 10) == this.file.name.slice(8, 10)
- file.name.slice(5, 7) == this.file.name.slice(5, 7)
- Summary
order:
- file.name
- summary
- file.day
sort:
- file.name
This was a bit of a brute force approach but is working. I have also used the below to compare the daily file to a range of YAML frontmatter dates held in my “weekly” file:
filters:
and:
- file.name >= date(this.WeekFromDate).format("YYYY-MM-DD")
- file.name <= date(this.WeekToDate).format("YYYY-MM-DD")
Same principle for my “files created today” query:
filters:
and:
- date(file.ctime).format("YYYY-MM-DD") == this.file.name
I also took the opportunity to externalise some of queries, so these are now external “.base” files that I imbed rather than a query in a code block that is repeated in each daily note.
This took care of most of my existing Dataview queries. I was left with a couple of issues.
- I had an awkward Dataview query that I borrowed from the Obsidian forums that pivoted my habit tracking checkboxes
- I used the Dataview progress bar to visually track progress in my daily, weekly, quarterly and annual notes.
- A couple of Dataview queries used grouping. Bases doesn’t currently support grouping
The Dataview query I was using for habit tracking was:
TABLE without id
"[[" + key + "]]" as Date,
rows.StepCount[0] as "Step Count",
choice(rows.StepCount[0] > 7000, "🟢", "❌") AS "Steps",
choice(rows.AlcoholUnits[0] = 0, "🟢", "❌") AS "No Alcohol",
choice(rows.T.completed[0], "🟢", "❌") AS Guitar,
choice(rows.T.completed[1], "🟢", "❌") AS "No Snack",
choice(rows.T.completed[2], "🟢", "❌") AS Water,
choice(rows.T.completed[3], "🟢", "❌") AS Supplements
FROM "Journal"
where file.name <= this.file.name
sort file.name desc
FLATTEN file.tasks as T
WHERE meta(T.section).subpath = "Habits"
group by file.name
sort key DESC
LIMIT 7
Which the online converter understandably choked on. However, now that the habits have all been converted to YAML frontmatter, most of the complexity has gone and I was able to do this in Bases:
formulas:
StepF: if(StepCount > 6999, "🟢", "❌")
AlcoholF: if(AlcoholUnits != 0, "❌", "🟢")
MusicF: if(Music, "🟢", "❌")
NoSnackF: if(NoSnack, "🟢", "❌")
SupplementsF: if(Supplements, "🟢", "❌")
WordleF: if(Wordle, "🟢", "❌")
JournalF: if(Journal, "🟢", "❌")
WaterF: if(Water, "🟢", "❌")
properties:
file.name:
displayName: Name
formula.WaterF:
displayName: Water
formula.StepF:
displayName: Steps
formula.AlcoholF:
displayName: No Alcohol
formula.MusicF:
displayName: Music
formula.NoSnackF:
displayName: No Snack
formula.SupplementsF:
displayName: Supplements
formula.WordleF:
displayName: Wordle
formula.JournalF:
displayName: Journal
views:
- type: table
name: Habits summary
filters:
and:
- date(file.name) >= date(this.file.name) - "6d"
- date(file.name) <= date(this.file.name)
- file.folder.contains("Journal")
- '!file.name.containsAny("W", "Q")'
Bases is unlikely to support a status bar. Grouping functionality is mentioned in the Bases roadmap but not available yet so I am looking at other options.
From Dataview to Datacore
I have a couple of Dataview queries that show the built-in status bar. I use dynamic status bars a lot in my periodic notes and in a couple of other queries including a " life progress" panel that I include in my daily notes showing how the day, month and year are going and a bit of a “memento mori”. I had borrowed this from a Prakash Joshi Pax video.
The replacement for Dataview by the plugin author is intended to be Datacore. This is also currently in beta so needs to be installed using BRAT. It is based around React so the syntax is very different to Dataview. I couldn’t find any other way to dynamically generate a status bar based on note contents, so I installed it and started playing around.
Asking AI to generate Datacore code didn’t get me very far - not much by way of resources for it to draw from at the moment but because Datacore is based around React, I asked it to generate the status bar in React instead, and this ran well with a couple of tweaks. I asked it to generate without external dependencies.
The only changes I had to make to the code Claude generated for me was:
- Remove the “import React…” statement
- Datacore wants a View() returned, so I changed the return to be “return function View() "
This looks like the below, including code to retrieve the total, and completed checkbox count from the current note:
function StatusBar({ completed, total, label = "Progress" }) {
const percentage = total === 0 ? 100 : Math.round((completed / total) * 100);
return (
<div style={{ width: '100%', padding: '8px' }}>
<div style={{ marginBottom: '8px' }}>
<span style={{ float: 'left' }}>
{label}: {completed}/{total} completed, {percentage}%
</span>
<div style={{ clear: 'both' }}></div>
</div>
<div style={{
width: '100%',
backgroundColor: 'black',
borderRadius: '9999px',
height: '16px',
border: '1px solid var(--color-accent)',
overflow: 'hidden'
}}>
<div
style={{
backgroundColor: 'var(--color-accent)',
height: '100%',
borderRadius: '9999px',
width: `${percentage}%`,
transition: 'width 0.3s ease-out'
}}
/>
</div>
</div>
);
}
// Main View component using the StatusBar
return function View() {
const current = dc.useCurrentFile().$file;
const completed = dc.useQuery("@task and $completed = true and $file = \"" + current + "\"").length;
const total = dc.useQuery("@task and $file = \"" + current + "\"").length;
return (
<StatusBar
completed={completed}
total={total}
label="Progress"
/>
);
}
And it looks like:
Using the same idea on the Life progress panel looks like:
Work Still to Do
Everything is usable now, and I have created a Quickadd hotkey to make inserting frontmatter and the Meta bind inline string a bit easier. This runs a small js script that Claude generated. It prompts for the field to add, and then adds it and creates the correct frontmatter entry.
Grouping in Bases
For now I am using straight bases queries without the grouping. With Dataview I was breaking on each change of first letter. This may wait until Bases supports grouping.
Meta Bind Visuals
Meta bind is great, and there is flexibility in how stuff is displayed by adding CSS classes, but I need to spend more time understanding it. Specific things I want to address are:
Formatting the Habit Toggles
The only way so far I have found to get effective columns is to force the text to monospace using backticks. This gives me the alignment I want at the expense of the font. There must be a better way. Currently it looks like:
I experimented with having the toggles on one line, but this is not a great experience on mobile.
Input Field Width
Some of my text input can be quite long, and I want to be able to see as much of the text as possible. Ideally I’d like to have the text input take up the rest of the line it is on, allowing for the title of the field, but I haven’t worked out how to do that yet. I have hard-coded the input field width to 800px currently, which is OK on desktop, but not great on mobile.
Learnings
- AI can save a ton of time in writing or translating scripts for you. Backup first and test carefully!
- Dataview supporting YAML frontmatter as well as inline fields makes switching from inline to YAML relatively painless.
- Meta bind helps keep the inline experience.
- Templater can also access note content. Claude re-wrote a templater template that used Dataview for note access to one that only used templater itself.
Typing is no longer stuttery in my daily note and my queries are fast. More of my querying is using core supported plugins and the Obsidian standard metadata method of using YAML frontmatter. I am using Meta bind and Datacore to make visualising easier, but the system would still run without them.
Resources:
- Meta bind
- Claude - I used the free version from the web, but have since taken a paid sub and am experimenting with Claude Code that can access my Obsidian vault.
- Dataview to Bases converter
- Bases documentation
- BRAT installer
- Datacore