Why Use WordPress Table of Contents Without Plugin
Using a custom post type (CPT) instead of default posts gives you:
- Better content isolation
- Custom URL structure
(/case-study/,/insights/, etc.) - Dedicated schema control
- Performance benefits (no unnecessary filters or plugins)
Example CPT
register_post_type('case-study', [
'label' => 'Case Studies',
'public' => true,
'rewrite' => ['slug' => 'case-study'],
'supports' => ['title', 'editor', 'thumbnail'],
]);
Content Structure (Very Important for SEO)
Your blog must follow this hierarchy:
H1 โ Blog Title (Only ONE) H2 โ Main sections (used for TOC) H3 โ Sub-sections Paragraphs Lists Images
Example
<h1>How We Increased Conversion Rate by 120%</h1>
<h2>Project Overview</h2>
<p>Brief description of the project scope.</p>
<h2>Challenges We Faced</h2>
<h3>Performance Issues</h3>
<h3>UX Problems</h3>
<h2>Our Solution</h2>
Rendered Output Example:
How We Increased Conversion Rate by 120%
Project Overview
We analyzed the existing funnel, user behavior, and technical bottlenecks affecting conversions.
Challenges We Faced
Performance Issues
Slow page load times and render-blocking assets were impacting user retention.
UX Problems
Poor CTA visibility and confusing navigation reduced engagement.
Our Solution
We optimized Core Web Vitals, restructured the layout, and improved CTA
placement.
Google uses H2s to understand page structure.
๐ก Your custom TOC reads H2 โ perfect alignment.
Writing Content Using ACF (Best Practice)
Use ACF Flexible Content blocks like:
- Content Block
- Image + Content
- Quote Block
- Summary Block
- Author Block
Each block outputs real HTML headings, not shortcodes.
โ
Clean
โ
Crawlable
โ
TOC-friendly
Pure Custom Table of Contents (No Plugin)
You already implemented this correctly ๐
What makes it SEO-safe:
- Reads real
<h2>headings - Generates real anchor links
- Uses
ItemListschema - No JavaScript dependency for content rendering
Why Google Likes This
- Anchors appear in SERP sitelinks
- Improves dwell time
- Improves page experience signals
TOC Schema (Rich Result Friendly)
Youโre using the correct schema:
{
"@type": "ItemList",
"name": "Table of Contents"
}
โ
Supported
โ
Safe
โ No fake schema types
โ No misuse of FAQ
This helps Google understand page navigation structure.
document.addEventListener('DOMContentLoaded', function () {
const contentWrap = document.querySelector(
'.blog-details-content > .col-right > .blog-details-content-part'
);
const toc = document.querySelector('#case-toc');
const tocList = toc?.querySelector('ul');
if (!contentWrap || !toc || !tocList) return;
const headings = contentWrap.querySelectorAll('h2');
if (!headings.length) {
toc.style.display = 'none';
return;
}
const HEADER_OFFSET = 100; // adjust for sticky header
const schemaItems = [];
/* -------------------------
* Build TOC + Schema
* ------------------------- */
headings.forEach((heading, index) => {
if (!heading.id) {
heading.id = `section-${index + 1}`;
}
/* ---------- TOC UI ---------- */
const li = document.createElement('li');
const a = document.createElement('a');
a.href = `#${heading.id}`;
a.textContent = heading.textContent;
a.addEventListener('click', function (e) {
e.preventDefault();
const targetPos =
heading.getBoundingClientRect().top +
window.pageYOffset -
HEADER_OFFSET;
window.scrollTo({
top: targetPos,
behavior: 'smooth'
});
});
li.appendChild(a);
tocList.appendChild(li);
/* ---------- Schema ---------- */
schemaItems.push({
"@type": "ListItem",
"position": index + 1,
"name": heading.textContent,
"url": window.location.href.split('#')[0] + '#' + heading.id
});
});
const tocLinks = tocList.querySelectorAll('a');
/* -------------------------
* Active link on scroll
* ------------------------- */
function setActiveHeading() {
let currentIndex = 0; // always keep one active
headings.forEach((heading, index) => {
const rect = heading.getBoundingClientRect();
if (rect.top <= HEADER_OFFSET + 10) { currentIndex = index; } }); tocLinks.forEach(link => link.classList.remove('active'));
if (tocLinks[currentIndex]) {
tocLinks[currentIndex].classList.add('active');
}
}
// Initial state
tocLinks[0]?.classList.add('active');
setActiveHeading();
window.addEventListener('scroll', setActiveHeading);
/* -------------------------
* Inject JSON-LD Schema
* ------------------------- */
const schema = {
"@context": "https://schema.org",
"@type": "ItemList",
"name": "Table of Contents",
"itemListElement": schemaItems
};
const script = document.createElement('script');
script.type = 'application/ld+json';
script.textContent = JSON.stringify(schema);
document.head.appendChild(script);
});
Build Faster, Rank Better โ Without Plugins
Want a lightweight WordPress setup with custom CPTs, plugin-free Table of Contents, and clean schema that Google actually understands?
๐ Letโs build a high-performance WordPress site together
Rajan Gupta
FullStack Web DeveloperRajan Gupta is a passionate web developer and digital creator who loves sharing insights on WordPress, modern web design, and performance optimization. When not coding, they enjoy exploring the latest tech trends and helping others build stunning, high-performing websites.