| name | obsidian-core-workflow-a |
| description | Execute Obsidian primary workflow: note manipulation and vault operations.
Use when implementing file operations, frontmatter handling,
or programmatic note creation and modification.
Trigger with phrases like "obsidian vault operations",
"obsidian file manipulation", "obsidian note management".
|
| allowed-tools | Read, Write, Edit, Bash(npm:*), Grep |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Obsidian Core Workflow A: Vault Operations
Overview
Primary workflow for Obsidian plugin development: manipulating notes, handling frontmatter, and working with the vault file system.
Prerequisites
- Completed
obsidian-install-auth setup
- Understanding of Obsidian's file structure
- Valid development vault configured
Instructions
Step 1: Working with Files (TFile)
import { TFile, TFolder, TAbstractFile, Vault } from 'obsidian';
export class FileOperations {
constructor(private vault: Vault) {}
getAllNotes(): TFile[] {
return this.vault.getMarkdownFiles();
}
getNote(path: string): TFile | null {
const file = this.vault.getAbstractFileByPath(path);
return file instanceof TFile ? file : null;
}
async readNote(file: TFile): Promise<string> {
return this.vault.read(file);
}
readNoteCached(file: TFile): string | null {
return this.vault.cachedRead(file);
}
async writeNote(file: TFile, content: string): Promise<void> {
await this.vault.modify(file, content);
}
async createNote(path: string, content: string): Promise<TFile> {
const folderPath = path.substring(0, path.lastIndexOf('/'));
if (folderPath) {
await this.ensureFolder(folderPath);
}
return this.vault.create(path, content);
}
async deleteNote(file: TFile, trash: boolean = true): Promise<void> {
if (trash) {
await this.vault.trash(file, false);
} else {
await this.vault.delete(file);
}
}
async renameNote(file: TFile, newPath: string): Promise<void> {
await this.vault.rename(file, newPath);
}
private async ensureFolder(path: string): Promise<void> {
const folder = this.vault.getAbstractFileByPath(path);
if (!folder) {
await this.vault.createFolder(path);
}
}
}
Step 2: Frontmatter Operations
import { App, TFile, parseYaml, stringifyYaml } from 'obsidian';
export class FrontmatterService {
constructor(private app: App) {}
getFrontmatter(file: TFile): Record<string, any> | null {
const cache = this.app.metadataCache.getFileCache(file);
return cache?.frontmatter || null;
}
parseFrontmatter(content: string): { frontmatter: Record<string, any> | null; body: string } {
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
if (!match) {
return { frontmatter: null, body: content };
}
try {
const frontmatter = parseYaml(match[]);
{ frontmatter, : match[] };
} {
{ : , : content };
}
}
(
: ,
: <, >
): <> {
content = ...(file);
{ frontmatter, body } = .(content);
newFrontmatter = { ...frontmatter, ...updates };
yamlContent = (newFrontmatter);
newContent = ;
...(file, newContent);
}
(: , : ): <> {
content = ...(file);
{ frontmatter, body } = .(content);
(frontmatter && key frontmatter) {
frontmatter[key];
yamlContent = (frontmatter);
newContent = ;
...(file, newContent);
}
}
(: , ?: ): [] {
...().( {
fm = .(file);
(!fm || !(property fm)) ;
(value !== ) fm[property] === value;
;
});
}
}
Step 3: Link and Tag Operations
import { App, TFile, CachedMetadata } from 'obsidian';
export class LinkService {
constructor(private app: App) {}
getOutgoingLinks(file: TFile): string[] {
const cache = this.app.metadataCache.getFileCache(file);
const links: string[] = [];
cache?.links?.forEach(link => {
links.push(link.link);
});
cache?.embeds?.forEach(embed => {
links.push(embed.link);
});
return [...new Set(links)];
}
getBacklinks(file: TFile): TFile[] {
const backlinks: [] = [];
resolvedLinks = ...;
( [sourcePath, links] .(resolvedLinks)) {
(file. links) {
sourceFile = ...(sourcePath);
(sourceFile ) {
backlinks.(sourceFile);
}
}
}
backlinks;
}
(: ): [] {
cache = ...(file);
: [] = [];
cache?.?.( {
tags.(tag.);
});
fmTags = cache?.?.;
(.(fmTags)) {
fmTags.( {
tags.(tag.() ? tag : );
});
}
[... (tags)];
}
(: ): [] {
normalizedTag = tag.() ? tag : ;
...().( {
tags = .(file);
tags.(normalizedTag);
});
}
(
: ,
: ,
:
): <> {
content = ...(file);
content = content.(
(, ),
);
...(file, content);
}
(: ): {
str.(, );
}
}
Step 4: Search and Query
import { App, TFile, prepareFuzzySearch, prepareSimpleSearch } from 'obsidian';
export class SearchService {
constructor(private app: App) {}
searchFileNames(query: string): TFile[] {
const search = prepareFuzzySearch(query);
const results: { file: TFile; score: number }[] = [];
for (const file of this.app.vault.getMarkdownFiles()) {
const match = search(file.basename);
if (match) {
results.push({ file, score: match.score });
}
}
return results
.sort((a, b) => b.score - a.score)
.map(r => r.file);
}
(: ): <{ : ; : }[]> {
search = (query);
: { : ; : }[] = [];
( file ...()) {
content = ...(file);
match = (content);
(match) {
results.({ file, : match.. });
}
}
results.( b. - a.);
}
(: {
?: ;
?: [];
?: <, >;
?: ;
}): <[]> {
files = ...();
(criteria.) {
files = files.( f..(criteria.!));
}
(criteria.?.) {
linkService = (.);
files = files.( {
fileTags = linkService.(f);
criteria.!.(
fileTags.(tag.() ? tag : )
);
});
}
(criteria.) {
fmService = (.);
files = files.( {
fm = fmService.(f);
(!fm) ;
.(criteria.!).(
fm[key] === value
);
});
}
(criteria.) {
search = (criteria.);
: [] = [];
( file files) {
content = ...(file);
((content)) {
contentMatches.(file);
}
}
files = contentMatches;
}
files;
}
}
Output
- File operations for create, read, update, delete
- Frontmatter parsing and modification
- Link and tag traversal
- Search and query capabilities
Error Handling
| Error | Cause | Solution |
|---|
| File not found | Path incorrect | Verify with getAbstractFileByPath |
| Permission denied | File locked | Check if file is open in editor |
| YAML parse error | Invalid frontmatter | Validate YAML syntax |
| Circular links | Recursive backlinks | Track visited files |
Examples
Complete Note Template
async function createNoteFromTemplate(
app: App,
templatePath: string,
newPath: string,
variables: Record<string, string>
): Promise<TFile> {
const template = app.vault.getAbstractFileByPath(templatePath);
if (!(template instanceof TFile)) {
throw new Error('Template not found');
}
let content = await app.vault.read(template);
for (const [key, value] of Object.entries(variables)) {
content = content.replace(new RegExp(`{{${key}}}`, 'g'), value);
}
return app.vault.create(newPath, content);
}
Resources
Next Steps
For UI components, see obsidian-core-workflow-b.