Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/inkdown/inkdown/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The MetadataCache provides fast access to parsed file metadata including frontmatter, links, tags, headings, and embeds.
const cache = this.app.metadataCache.getCache('/path/to/file.md');
if (cache) {
  console.log('Frontmatter:', cache.frontmatter);
  console.log('Links:', cache.links);
  console.log('Tags:', cache.tags);
  console.log('Headings:', cache.headings);
}

MetadataCache Interface

interface MetadataCache extends Events {
  getCache(path: string): CachedMetadata | null;
  getFileCache(path: string): CachedMetadata | null;
  onChanged(callback: (file: TFile, data: string, cache: CachedMetadata) => void): EventRef;
}

CachedMetadata

interface CachedMetadata {
  frontmatter?: FrontMatterCache;
  links?: LinkCache[];
  embeds?: EmbedCache[];
  tags?: TagCache[];
  headings?: HeadingCache[];
}

Getting Metadata

getCache()

getCache
(path: string) => CachedMetadata | null
Get cached metadata for a file by path.
const cache = this.app.metadataCache.getCache('/notes/daily.md');
if (cache) {
  console.log('Found metadata for daily.md');
} else {
  console.log('No metadata cached');
}

getFileCache()

getFileCache
(path: string) => CachedMetadata | null
Alias for getCache().
const cache = this.app.metadataCache.getFileCache('/notes/project.md');

Frontmatter

FrontMatterCache

interface FrontMatterCache extends Record<string, unknown> {}
Frontmatter fields are accessible as properties:
const cache = this.app.metadataCache.getCache(file.path);
if (cache?.frontmatter) {
  const title = cache.frontmatter.title;
  const tags = cache.frontmatter.tags;
  const date = cache.frontmatter.date;
  const customField = cache.frontmatter.myCustomField;
  
  console.log('Title:', title);
  console.log('Tags:', tags);
}

Example: Filter by Frontmatter

async getPublishedNotes() {
  const files = await this.app.workspace.getMarkdownFiles();
  
  return files.filter(file => {
    const cache = this.app.metadataCache.getCache(file.path);
    return cache?.frontmatter?.status === 'published';
  });
}

async getDraftNotes() {
  const files = await this.app.workspace.getMarkdownFiles();
  
  return files.filter(file => {
    const cache = this.app.metadataCache.getCache(file.path);
    return cache?.frontmatter?.draft === true;
  });
}

LinkCache

interface LinkCache {
  link: string;           // The link target
  original: string;       // Original link text
  displayText?: string;   // Display text (if different)
  position: CachePosition;
}
async findBrokenLinks() {
  const files = await this.app.workspace.getMarkdownFiles();
  const brokenLinks: Array<{file: TFile; link: string}> = [];
  
  for (const file of files) {
    const cache = this.app.metadataCache.getCache(file.path);
    if (!cache?.links) continue;
    
    for (const link of cache.links) {
      const targetFile = this.app.workspace.getAbstractFileByPath(link.link);
      if (!targetFile) {
        brokenLinks.push({
          file,
          link: link.link,
        });
      }
    }
  }
  
  return brokenLinks;
}
getBacklinks(targetFile: TFile): Array<{file: TFile; link: LinkCache}> {
  const allFiles = await this.app.workspace.getMarkdownFiles();
  const backlinks: Array<{file: TFile; link: LinkCache}> = [];
  
  for (const file of allFiles) {
    const cache = this.app.metadataCache.getCache(file.path);
    if (!cache?.links) continue;
    
    for (const link of cache.links) {
      if (link.link === targetFile.path || link.link === targetFile.basename) {
        backlinks.push({ file, link });
      }
    }
  }
  
  return backlinks;
}

Tags

TagCache

interface TagCache {
  tag: string;            // Tag name (including #)
  position: CachePosition;
}

Example: Find Files by Tag

async getFilesByTag(tag: string): Promise<TFile[]> {
  const files = await this.app.workspace.getMarkdownFiles();
  const normalizedTag = tag.startsWith('#') ? tag : '#' + tag;
  
  return files.filter(file => {
    const cache = this.app.metadataCache.getCache(file.path);
    return cache?.tags?.some(t => t.tag === normalizedTag);
  });
}

// Usage
const importantFiles = await this.getFilesByTag('important');
const todoFiles = await this.getFilesByTag('#todo');

Example: Get All Tags

async getAllTags(): Promise<Set<string>> {
  const files = await this.app.workspace.getMarkdownFiles();
  const allTags = new Set<string>();
  
  for (const file of files) {
    const cache = this.app.metadataCache.getCache(file.path);
    if (cache?.tags) {
      for (const tag of cache.tags) {
        allTags.add(tag.tag);
      }
    }
  }
  
  return allTags;
}

Headings

HeadingCache

interface HeadingCache {
  heading: string;        // Heading text
  level: number;          // Heading level (1-6)
  position: CachePosition;
}

Example: Generate Table of Contents

generateTOC(file: TFile): string {
  const cache = this.app.metadataCache.getCache(file.path);
  if (!cache?.headings) return '';
  
  const lines: string[] = [];
  
  for (const heading of cache.headings) {
    const indent = '  '.repeat(heading.level - 1);
    const link = heading.heading.toLowerCase().replace(/\s+/g, '-');
    lines.push(`${indent}- [${heading.heading}](#${link})`);
  }
  
  return lines.join('\n');
}

Example: Find Files by Heading

async findFilesByHeading(searchText: string): Promise<Array<{file: TFile; heading: HeadingCache}>> {
  const files = await this.app.workspace.getMarkdownFiles();
  const results: Array<{file: TFile; heading: HeadingCache}> = [];
  
  for (const file of files) {
    const cache = this.app.metadataCache.getCache(file.path);
    if (!cache?.headings) continue;
    
    for (const heading of cache.headings) {
      if (heading.heading.toLowerCase().includes(searchText.toLowerCase())) {
        results.push({ file, heading });
      }
    }
  }
  
  return results;
}

Embeds

EmbedCache

interface EmbedCache {
  original: string;  // Original embed syntax
}

Example: Find Files with Images

async getFilesWithImages(): Promise<TFile[]> {
  const files = await this.app.workspace.getMarkdownFiles();
  
  return files.filter(file => {
    const cache = this.app.metadataCache.getCache(file.path);
    return cache?.embeds && cache.embeds.length > 0;
  });
}

Position Information

CachePosition

interface CachePosition {
  start: {
    line: number;   // Line number (0-indexed)
    col: number;    // Column number (0-indexed)
    offset: number; // Character offset from start
  };
  end: {
    line: number;
    col: number;
    offset: number;
  };
}

Example: Get Line Range

function getLineRange(position: CachePosition): {start: number; end: number} {
  return {
    start: position.start.line,
    end: position.end.line,
  };
}

Events

onChanged()

onChanged
(callback: (file: TFile, data: string, cache: CachedMetadata) => void) => EventRef
Called when file metadata is updated.
this.registerEvent(
  this.app.metadataCache.onChanged((file, data, cache) => {
    console.log('Metadata changed:', file.path);
    console.log('New content:', data);
    console.log('Parsed cache:', cache);
    
    // React to changes
    this.updateIndex(file, cache);
  })
);

Complete Examples

Tag Browser Plugin

export default class TagBrowserPlugin extends Plugin {
  private tagIndex = new Map<string, Set<string>>();

  async onload() {
    // Build initial index
    await this.buildTagIndex();

    // Update index on metadata changes
    this.registerEvent(
      this.app.metadataCache.onChanged((file, data, cache) => {
        this.updateTagIndex(file, cache);
      })
    );

    // Add command to show tags
    this.addCommand({
      id: 'show-tags',
      name: 'Show All Tags',
      callback: () => {
        const tags = Array.from(this.tagIndex.keys()).sort();
        console.log('All tags:', tags);
      },
    });
  }

  async buildTagIndex() {
    this.tagIndex.clear();
    const files = await this.app.workspace.getMarkdownFiles();

    for (const file of files) {
      const cache = this.app.metadataCache.getCache(file.path);
      if (cache) {
        this.updateTagIndex(file, cache);
      }
    }
  }

  updateTagIndex(file: TFile, cache: CachedMetadata) {
    // Remove old entries
    for (const [tag, files] of this.tagIndex.entries()) {
      files.delete(file.path);
    }

    // Add new entries
    if (cache.tags) {
      for (const tag of cache.tags) {
        if (!this.tagIndex.has(tag.tag)) {
          this.tagIndex.set(tag.tag, new Set());
        }
        this.tagIndex.get(tag.tag)!.add(file.path);
      }
    }
  }

  getFilesForTag(tag: string): string[] {
    return Array.from(this.tagIndex.get(tag) || []);
  }
}
export default class LinkValidatorPlugin extends Plugin {
  async onload() {
    this.addCommand({
      id: 'check-broken-links',
      name: 'Check for Broken Links',
      callback: async () => {
        const broken = await this.findBrokenLinks();
        
        if (broken.length === 0) {
          this.showNotice('No broken links found!');
        } else {
          this.showNotice(`Found ${broken.length} broken links`);
          console.log('Broken links:', broken);
        }
      },
    });
  }

  async findBrokenLinks() {
    const files = await this.app.workspace.getMarkdownFiles();
    const brokenLinks: Array<{
      file: TFile;
      link: string;
      line: number;
    }> = [];

    for (const file of files) {
      const cache = this.app.metadataCache.getCache(file.path);
      if (!cache?.links) continue;

      for (const link of cache.links) {
        // Check if target exists
        const target = this.app.workspace.getAbstractFileByPath(link.link);
        if (!target) {
          brokenLinks.push({
            file,
            link: link.link,
            line: link.position.start.line,
          });
        }
      }
    }

    return brokenLinks;
  }
}

Best Practices

Check for Null

// ✅ Good
const cache = this.app.metadataCache.getCache(file.path);
if (cache?.frontmatter) {
  const title = cache.frontmatter.title;
}

// ❌ Bad
const cache = this.app.metadataCache.getCache(file.path);
const title = cache.frontmatter.title; // May throw!

Use Optional Chaining

// ✅ Good
const tags = cache?.tags?.map(t => t.tag) || [];
const headings = cache?.headings?.filter(h => h.level === 1) || [];

// ❌ Bad
const tags = cache.tags.map(t => t.tag); // May throw!

Cache Results

// ✅ Good - cache results
class MyPlugin extends Plugin {
  private cachedData = new Map<string, ProcessedData>();

  async onload() {
    this.registerEvent(
      this.app.metadataCache.onChanged((file, data, cache) => {
        this.cachedData.set(file.path, this.process(cache));
      })
    );
  }
}

// ❌ Bad - recalculate every time
getData(file: TFile) {
  const cache = this.app.metadataCache.getCache(file.path);
  return this.expensiveProcess(cache); // Recalculated!
}

Build docs developers (and LLMs) love