When you create an article, such as a blog post for freeCodeCamp, Hashnode, Medium, or DEV.to, you can help guide the reader by creating one. Table of Contents (ToC). In this article, I’ll explain how to build using JavaScript and browser dev tools. The article will explain how to use Google Chrome Dev Tools. But the same can be applied to any modern browser.
The process in this article needs to be done once on each platform. Once you have the code, you can apply it every time you create a ToC. Note that if the platform changes, you may need to adjust the script.
Table of Contents
Browser Dev Tools
DevTools is a browser extension that allows you to inspect and manipulate the DOMDocument object model), which is a representation of HTML that the browser keeps in memory in the form of a tree. It also provides access to the JavaScript console, where you can write short code snippets to test an object. It has many more features, but we will only use these two.
To open Dev Tools (in Google Chrome), you can press F12 or right-click on the page with your mouse and click Inspect.

Above is a screenshot of DevTools with a preview of this article. On the right, you can see a selected one. h1 HTML tag (title) and CSS are applied to this tag. The tree structure you see is the DOM.
💡
When creating a ToC for Free Code Camp, You should open the preview in a new tab.
JavaScript console
We’ll need to access the JavaScript console. To open the console in Google Chrome, you can use F12, right-click on the page and select Inspect from the context menu, or use the shortcut CTRL+SHIFT+C (Windows, Linux) or CMD+OPTION+C (Mac).
In Chrome DevTools, you can select the Console tab at the top of DevTools. But this will hide the DOM tree. Better to open the bottom drawer. You need to click on the 3 dots in the upper right corner and select “Show Console Drawer”.

The dev tools will look like this:

💡
You can ignore any errors or warnings in the console. You can click this icon 🚫 to the left of the drawer, and it will clear the console.
A console is a so-called. read-evil-print-loop. A classic interface, where you type some commands, JavaScript code here, and when you press enter, the code is executed in the context of the page DevTools is on.

Above, you can see the page alert executed from the console.
Understanding the structure of the DOM
The first step in creating a ToC is to inspect the DOM and look for headers. They usually are. H1…H6 H1 tags are often the page title. In an ideal world, it would last forever.
In my case, the header looks like this:
The article only contains H2 tags, but later in the article, I will also explain how to create a nested ToC.
💡
Your headers must have an “id” attribute. It may look different, for example, on a different element, but it must be in the DOM. Later in the article, I’ll cover some different structures and how to deal with them.
Now with DevTools, we can write code that will search for each header:
document.querySelectorAll('h2(id), h3(id), main h4(id)');
In the case of my article on FreecodeCamp, it returned this output:
NodeList(5) (h2#heading-dev-tools, h2#heading-javascript-console, h2#heading-understanding-the-dom-structure, h2#trending-guides.col-header, h2#mobile-app.col-header)
First, this is a NodeList that we need to convert to an Array. Second, in addition to our headers that we have so far, we also have two headers that are part of the website and not the main content. So we need to find the only element that is the parent of the headers we need.
You can right-click on the white page containing the article and select Inspect the element.. In our case, it found an element . So we can rewrite our selector like this:
document.querySelectorAll('main h2(id), main h3(id), main h4(id)');
And now it returns our headers and nothing more.
💡
gave (id) There is actually no need for an attribute selector here. At least not on the freecode camp.
How to Create a ToC in Markdown
Many blogging platforms support Markdown, so this will be the first thing we’ll build.
First, we’ll convert the node list to an array. We can use Diffusion operator:
(...document.querySelectorAll('main h2(id), main h3(id), main h4(id)'));
Then we can map to the array and create markdown links that point to the given header.
const headers = (...document.querySelectorAll('main h2(id), main h3(id), main h4(id)'));
headers.map(function(node) {
// H2 header should have 0 indent
const level = parseInt(node.nodeName.replace('H', '')) - 2;
const hash = node.getAttribute('id');
const indent=" ".repeat(level * 2);
return `\({indent}* (\){node.innerText})(#${hash})`;
});
The output looks like this:
(4) ('* (Dev Tools)(#heading-dev-tools)', '* (JavaScript Console)(#heading-javascript-console)', '* (Understanding the DOM Structure)(#heading-understanding-the-dom-structure)', '* (What to do if I don’t have headers?)(#heading-what-to-do-if-i-dont-have-headers)')
To get the text, we can join the array with a newline character and use console.log to display the output. If we don’t use console.logit will show a string next to it. \n letters
const headers = (...document.querySelectorAll('main h2(id), main h3(id), main h4(id)'));
console.log(headers.map(function(node) {
// H2 header should have 0 indent
const level = parseInt(node.nodeName.replace('H', '')) - 2;
const hash = node.getAttribute('id');
const indent=" ".repeat(level * 2);
return `\({indent}* (\){node.innerText})(#${hash})`;
}).join('\n'));
The output of this article will look like this:
* (Dev Tools)(#heading-dev-tools)
* (JavaScript Console)(#heading-javascript-console)
* (Understanding the DOM Structure)(#heading-understanding-the-dom-structure)
* (Creating TOC in Markdown)(#heading-creating-toc-in-markdown)
* (This is fake header)(#heading-this-is-fake-header)
I created a fake subheader. Platforms, even when not supporting Markdown when writing articles, often support Markdown when copying and pasting. The ToC at the top of the article was created by copying and pasting the generated Markdown with the last JavaScript snippet.
How to create HTML ToC
If your platform doesn’t support Markdown (like Medium), you can generate HTML, preview that HTML, and copy the output to the clipboard. Pasting it into the editor for the platform you’re using should retain the formatting.
💡
On the medium, the material is contained within a.
To convert Markdown to HTML, you can use any online tool, but you’ll see it in bits and pieces. It will be faster after building the code.
const headers = (...document.querySelectorAll('main h2(id), main h3(id), main h4(id)'))
function indent(state) {
return ' '.repeat((state.level - 1) * 2);
}
function closeUlTags(state, targetLevel) {
while (state.level > targetLevel) {
state.level--;
state.lines.push(`${indent(state)}`);
}
}
function openUlTags(state, targetLevel) {
while (state.level < targetLevel) {
state.lines.push(`${indent(state)}`);
state.level++;
}
}
const result = headers.reduce((state, node) => {
const level = parseInt(node.nodeName.replace('H', ''));
closeUlTags(state, level);
openUlTags(state, level);
const hash = node.getAttribute('id');
state.lines.push(`\({indent(state)}- ${node.innerText}
`);
return state;
}, { lines: (), level: 1 });
closeUlTags(result, 1);
console.log(result.lines.join('\n'));
This is the output of the code in this article:
I’ve added some headers at the end, so you can see that this will work for any level of nested headers. Note that we also have ToC as the first element in the list.
💡
Note that the above HTML code includes a link to the table of contents. This happens if you run the script again after adding the TOC. You can remove it by hand. If you want to improve the code, you can add filters.
Copy the HTML code for the editor.
The most so called WYSIWYG The editors are using HTML, and you should be able to copy and paste the output of the HTML code with formatting into that editor. The easiest is to save it to a file, open that file, and select the text:

What if I don’t have headers?
You need to find anything that can be targeted with CSS. If they are p Tags with a specific class (like headers), you can use p.header instead of h2.
How to create a table of contents for DEV.to
If your DOM structure is different, you can use different DOM methods to extract the element you need. For example, on DEV.to, the headers look like this:
Overview
So the selector should be fair. main h2. But when you execute this code:
(...document.querySelectorAll('main h2, main h3, main h4'));
You’ll notice that there are more headers than document content. Fortunately, we can use a new selector in CSS. :has(). A final selector for a header might look like this: main h2:has(a(name)).
Here is the complete code:
const selector="main h2:has(a(name)), main h3:has(a(name)), main h4:has(a(name))";
const headers = (...document.querySelectorAll(selector));
console.log(headers.map(function(node) {
// H2 header should have 0 indent
const level = parseInt(node.nodeName.replace('H', '')) - 2;
// this is how you get the hash
// you can also access href attribute and remove # from the output string
const hash = node.querySelector('a').getAttribute('name');
const indent=" ".repeat(level);
return `\({indent}* (\){node.innerText})(#${hash})`;
}).join('\n'));
The result
Creating a table of contents can help your readers digest your article. Since most people don’t read the entire article, they only scan for what they need. You can also find many articles about its impact on SEO. So if the article is long, it is always worth adding it.
And as you can see, creating a ToC with a little knowledge of web development is not that difficult.
If you like this article you may want to follow me on social media 🙁Twitter/X, GitHuband/or LinkedIn). You can also check mine. Personal website And mine New blog.