Skip to content

Instantly share code, notes, and snippets.

@stavarengo
Last active June 25, 2025 06:09
Show Gist options
  • Save stavarengo/4d62c425b43f877df39351ad3158b64e to your computer and use it in GitHub Desktop.
Save stavarengo/4d62c425b43f877df39351ad3158b64e to your computer and use it in GitHub Desktop.
πŸ“‘ Obsidian TOC Generator (DataviewJS snippet)
try {
/**
* @typedef {Object} HeadingCache - Extends [CacheItem](https://docs.obsidian.md/Reference/TypeScript+API/CacheItem)
* @see https://docs.obsidian.md/Reference/TypeScript+API/HeadingCache
* @property {string} heading - The heading text.
* @property {number} level - Heading level (1–6).
* @property {number} position - Offset of this item in the note (inherited from CacheItem).
*/
/**
* @typedef {Object} TitleType
* @property {number} level - The heading level.
* @property {string} text - The original heading text.
* @property {string} textToDisplay - The heading text to display.
*/
/** @typedef {TitleType[]} TitleTuple */
/**
* Extract headings and their levels from the current note.
*
* @returns {TitleTuple} Array of title objects.
*/
const extractHeadings = () => {
/** @type {TitleTuple} */
const titles = [];
/** @type {HeadingCache[]} */
const headings =
dv.app.metadataCache.getFileCache(dv.current().file).headings || [];
for (const heading of headings) {
let textToDisplay = heading.heading;
// If the text is a markdown link, extract the link text.
const match = textToDisplay.match(/\[(.*?)]/);
if (match) textToDisplay = match[1];
titles.push({
level: heading.level,
text: heading.heading,
textToDisplay,
});
}
return titles;
};
/**
* Render the Table of Contents from the given titles.
*
* @param {TitleTuple} titles - The titles to render.
* @param {string} ident - The indentation string.
* @returns {string}
*/
const generateTocMarkdown = (titles, ident) => {
/** @type {string[]} */
const toc = [];
// Find the minimum heading level to adjust indentation.
const baseLevel = Math.min(...titles.map((t) => t.level));
for (const title of titles) {
const listLevel = title.level - baseLevel + 1;
const listMarker = `${ident.repeat(listLevel - 1)}-`;
const link = `[[#${title.text}|${title.textToDisplay}]]`;
toc.push(`${listMarker} ${link}`);
}
return toc.join('\n');
};
const identUseTab = dv.app.vault?.config?.useTab ?? true;
const ident = identUseTab
? '\t'
: ' '.repeat(dv.app.vault?.config?.tabSize ?? 4);
const titles = extractHeadings();
dv.paragraph('## Table of Contents');
dv.paragraph(generateTocMarkdown(titles, ident));
} catch (e) {
dv.paragraph('## Table of Contents [ERROR]');
dv.paragraph(`\`\`\`\n${e.message || e.toString()}\n\`\`\``);
// Alternatively, using JSON.stringify() for a more detailed output
dv.paragraph("```\n"+JSON.stringify(e, Object.getOwnPropertyNames(e), 2)+"\n```");
}
@stavarengo
Copy link
Author

stavarengo commented Jun 25, 2025

πŸ“‘ Obsidian TOC Generator (DataviewJS)

Creates an in-note Table of Contents from all headings in the current file.

Features

  • Respects vault indentation settings (tabs or spaces)
  • Handles nested heading levels automatically
  • Cleans up markdown-link headings ([Text](url) β†’ Text)
  • Obsidian-style links for quick jump-to-section ([[#Heading|Label]])
  • Graceful error message if parsing fails

Dependencies

  • Obsidian (v1 or newer)
  • Dataview plugin (JavaScript queries enabled)

That’s itβ€”no additional libraries. Paste once, reuse everywhere.

Usage

  1. Save the script (e.g. toc.js) anywhere visible to Obsidian – many users keep custom views in a views/ or dataviewjs/ folder at vault-root.
  2. Call it from any note with dv.view():
    ```dataviewjs
    dv.view("views/toc")   <!-- path = folder/file, no β€œ.js” needed -->
    ```
    
    # Title Level 1
    Contents of level 1
    
    ## Title level 2
    Contents of level 2
    
    ### Title level 3
    Contents of level 3
    
    ## Title level 2 again
    Contents of level 2 again

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment