I would like to have the Table of Contents only include Heading 1 and not any of the others.

Is there a way to set the Table of Contents to exclude the rest of the headings?


11 Answers 11


Unfortunately Google Docs does not support hiding specific heading levels.

The only "solution" would be to change the headers you want to hide to the "Normal" style, then manually set the font formatting for each heading.

The only way to avoid all headings from being added to the TOC is to manually apply the style you want rather than using the set styles. Change those headers to "Normal" style by select each one individually and pressing Ctrl + \ (Control plus the backslash key). This will remove the formatting. Then manually add the point size, font, and bolding that you want.

To easily copy formatting from one place to another, you can use the Paint Tool. So once you manually change one heading, you can use the Paint Tool to change others that are the same level heading.

- Source: Google Docs Forums: How to show only Heading 1 in table of contents


What I did to solve the problem which is probably doc specific, but I was using H1 through H3, what would have been H4 I didn't want to show up in the TOC. I converted all the H4's to Subtitles, then styled Subtitles the way I wanted it.

Subtitles don't show up in the TOC.

How to convert all headings into another heading:


The previous answers suggests setting the style of headers to "Normal" and manually setting their style to look like a header. If you do so, you will lose the ability to link to that header from within the document.

It looks like you can actually manually delete items from the table of contents. I think that's a simpler approach.

  • 17
    Yet then you'll have to do this each time you update the table of content.
    – Royi
    Sep 27, 2017 at 16:53
  • 12
    Also, when inserting the table of contents, the page numbers are calculated. So if your table of contents with the extra headings is moving things around, the page numbers will be wrong after deleting the extra lines.
    – Laura
    Oct 11, 2017 at 19:24

Table of contents in Google Docs really is very half baked.

The only way to select header levels or to change the formatting from defaults is to REDO any such action(s) MANUALLY after every time the table has been updated. But, to the point made by "Laura" earlier in this thread, that also does not work as the page numbering is calculated right after the table of contents have been inserted and is thereby impacted by the height of the table in its default format. So here is (sadly) what you need to do to have a controlled look of your Google Docs table of content as far as I have been able to take it:

  1. Finish your text.
  2. Insert an empty page for as a place holder for the table of content at the desired place in the document.
  3. Append a blank page after the very last character of the document and insert the table of content there
  4. Do whatever modifications necessary to the table of content (manually)
  5. Manually cut the updated table of contents from the last page and paste it to the previously created place holder page.

This works but it makes you cry... :(


From what I can, tell this action is impossible on Google Docs, as other answers have mentioned. However, it would appear that there is another way if you have access to Microsoft Word:

Export the document into a .docx from Google Drive, which can be done when selecting what form you wish to download the document in. Then open this in Word and alter the table of contents using the following guide: https://support.office.com/en-us/article/format-or-customize-a-table-of-contents-9d85eb9c-0b55-4795-8abb-a49885b3a58d#layout

Guide Outline:

  • Select the drop down list from References>Table of Contents from the top bar.
  • Select Custom Table of Contents... from the list.
  • To change the number of levels displayed in your table of contents, click Show levels, and then click the number of levels you want.

I haven't tested this so I'll edit the answer with more feedback.


Yes, there's a workaround for this, and you can keep your heading structure.

  1. At the top of your doc, write out the text that you want to show in your table of contents. For example Chapter 1, Chapter 2
  2. Select the first phrase, and make it a hyperlink.
  3. You'll see options for the link, including selecting an existing heading.

2023 update: Google has announced this feature in the Google Workspace Updates blog post dated March 20, 2023: Improvements to content organization in Google Docs

The blog introduces Include and indent headings based on levels and has a visual walkthrough, but here are the steps:

  1. Click your TOC and a tab will appear to the left with a refresh icon and three dots. Click the three dots, then more options.

  2. Expand Heading Levels in the right sidebar and select only the heading levels you want.

Note: I was able to set heading levels in the table of contents successfully on March 30, 2023, but the feature may not yet be rolled out to your instance.

Additional new features introduced are:

  • Toggle between three default styles
  • Toggle page numbers
  • Toggle tab leader styling (adds lines between a heading and the page number)

Google Docs help: Add, change, or delete a table of contents

  • 1
    this is perfect and should now become an accepted answer to the question!
    – FurloSK
    Apr 25, 2023 at 9:01

Here's what I have come up with to make a TOC that uses only one specific heading style. It works pretty well and I included the code (onOpen) that makes this a custom function that appears at the top with the document menu bar. So once you have this code tweaked for your document, you can actually run the function from the document and not go back to the script.

Note you have to use Style sheets (and any headers you want) and you must first create/refresh a Google TOC first.

WARNING: These items in ANY HEADING STYLE will cause Script Failures: Parentheses, blank line, space(s) at end of line, Horz Rule in Heading style, etc. I could probably catch these with a try{} function but haven't messed with that enough to go forth with it.

Here is the typical message I put at top just as a reminder of how to build the specific TOC.

“Specific HEADING TOC” will fill-in above this line – Cut & Paste to Desired Location

Heres the code

function onOpen() {
  // will need to set up a trigger (alarm clock) to make this run on Opening the doc or refreshing it
  var docId = DocumentApp.getActiveDocument().getId().toString()
  var doc = DocumentApp.openById(docId);
  // create a custom menu item at the end of the menu row of the doc (avoids having to "run" through this script)
  var ui = DocumentApp.getUi();
  ui.createMenu('CustomFunct')  // note, no semi-colons at the end of each ".add____( )" below
      .addItem('Heading2 Only TOC -- "As Is" Order', 'menuItem1')
      .addItem('Heading2 Only TOC -- "Reverse" Order', 'menuItem2')
      .addItem('Heading3 Only TOC -- "As Is" Order', 'menuItem3')
      .addItem('Heading3 Only TOC -- "Reverse" Order', 'menuItem4')

      /* // Saving this in case you would expand the menu item with other functions and/or submenus
          .addItem('Temp Sum Menu Item', 'menuItem5'))

function menuItem1() {DocumentApp.getUi(); parseText("As Is","HEADING2");}
function menuItem2() {DocumentApp.getUi(); parseText("Reverse","HEADING2");}
function menuItem3() {DocumentApp.getUi(); parseText("As Is","HEADING3");}
function menuItem4() {DocumentApp.getUi(); parseText("Reverse","HEADING3");}

function menuItem5() {
     .alert('You need to create a script function for this submenu item!'); // just saving this as a reminder

function parseText(indexOrder,headStyle) {
  var docId = DocumentApp.getActiveDocument().getId().toString();  // Use this line when access the CustomFunct menu item
  var doc = DocumentApp.openById(docId);

  // Define the search parameters
  var searchElement = doc.getBody();
  var searchParas = doc.getBody().getParagraphs();

  // Look thru ALL Paragraphs to LOCATE AND SAVE all of the links and anchor points based on Heading Style
  var urls = [];
  var urlsText = [];
  for (var i = 0; i < searchParas.length; i++) {
    // Get values for the NEW TOC using only "headStyle" Styles (see UI menu items above)
    // Note: items in any HEADING#'s that cause this to fail:  parentheses, is a blank line, space at end of line, Horz Rule in any Heading style, etc.
    if (searchParas[i].getHeading() == headStyle) {
      var searchedText = searchElement.findText(searchParas[i].getText());
      var dropParens = searchParas[i].getText();
      var getAnchor = searchedText.getElement().asText().getLinkUrl(searchedText.getStartOffset())
      if (dropParens.indexOf("/##/") == -1){
        urlsText.push(dropParens); // builds the array of link text
        urls.push(getAnchor); // builds the array of link anchors

  // Inserts the NEW TOC at the top of the Document -- then you can copy/paste to a location of your choice
  for (var i = 0;i<urlsText.length;i++) {
    if(indexOrder == "Reverse"){var indexToUse = i;} // Produces a TOC in the reverse order as they are in the document
    if(indexOrder == "As Is")indexToUse = urls.length-1-i;  // Produces a TOC in the order they are in the document
    if (i==0){doc.getBody().editAsText().insertText(0,"\r");doc.getBody().editAsText().insertText(0,"\r")}


  /* Adapted from --> https://stackoverflow.com/questions/32602495/how-to-add-a-hyperlink-in-a-google-docs-using-a-google-script
    var text = "link text\n";
    var url = "www.google.com";
    DocumentApp.getActiveDocument().getBody().editAsText().insertText(0, text).setLinkUrl(0, text.length, url); 
  doc.getBody().editAsText().insertText(0,"TOC using only " + headStyle + " (in '" + indexOrder + "' Order)\r")

5 years later, and there still isn't an option to limit the heading levels, so I am putting my two cents in.

This solution still requires the manual deletion of unwanted lines at the end of the writing process, but at least it maintains proper page numbers in the TOC.

Since Google Docs let you decide where to put the TOC, you can use this to avoid the page numbers being off at the end. The solution that worked for me was this:

  1. Finalise your document
  2. Add a placeholder (e.g. blank newlines) at the place in the document where you eventually want your TOC to be. The placeholder needs to take that many pages as the final version of TOC will consume – this will probably take a bit of trial-by-error guessing.
  3. Generate the TOC at the end of the document.
  4. Edit it the way you want (that is, remove the unwanted headings etc).
  5. Finally, replace the placeholder with the generated TOC. This way the page numbers will remain correct.

Link to original source: Customize the Table of Contents in a Google Docs Document


I have really tried to achieve a better solution for this and got a bit further. I think it is as far as you can get without Google addressing the shortcomings properly. My solution is not great but not entirely appalling. The customized TOC is designed in a separate Google Doc (like a template) and is fulfilled with data and added to the actual document by a script.

Shared a google doc that explains the solution and that also can be used for testing the solution. https://docs.google.com/document/d/1mlJ3rI04isifgncHHP0VWhO8NEmt395Re8lapuTcG3w

The code here (also in the doc):

function onOpen() {
    .createMenu('My menu')
    .addItem('Custom Table of Contents', 'customTableOfContents')

function customTableOfContents(){
  var ui = DocumentApp.getUi();  

  //fetch content for the toc
  var textDoc = DocumentApp.getActiveDocument();
  var tocData = getTocData(textDoc);
  if (!tocData.content) {
  //fetch the "style sheet" for the toc and render the content accordingly
  var styleDoc = DocumentApp.openById('1chKuIuJDpQJEcHqn2AW1-W6exDyiknKLrmBXX-3KEDs');
  var renderResult = renderToc(textDoc,styleDoc,tocData.content);
  if (!renderResult.success) {

  //clean up empty rows at document end
  var textDocBody = textDoc.getBody();
  var lastElement = textDocBody.getChild(textDocBody.getNumChildren()-2);
  while (lastElement.getText() == "") {
    lastElement = textDocBody.getChild(textDocBody.getNumChildren()-2);

  if (tocData.dataCollectionStep == 1) {
  //position cursor at the end of the document, prompt user to insert a native table of content
  //and to run the function again.
    var position = textDoc.newPosition(textDocBody.getChild(textDocBody.getNumChildren()-1), 0);
  //we are done!
  if (renderResult.customTocPosition > 0) {
    var position = textDoc.newPosition(textDocBody.getChild(renderResult.customTocPosition), 0);

function getTocData(doc){
  //build an array of data for the toc
  var docBody = doc.getBody();
  var paragraphs = docBody.getParagraphs();
  var statusMessage;
  var nativeToc;
  var dataCollectionStep = 0;
  try {
    //if a native table of contents exists we assume we are running the function for the second time
    dataCollectionStep = 2;
    nativeToc = docBody.findElement(DocumentApp.ElementType.TABLE_OF_CONTENTS).getElement().asTableOfContents();
  } catch(e) {
    //if no native table of content exists we assume we are running the function for the first time
    dataCollectionStep = 1;
    statusMessage = 'Table of content has been generated but is missing page references and interactive links.' + String.fromCharCode(10) + 
      'To add the missing parts, please select "Table of content" from the "Insert" menu.' + String.fromCharCode(10) + 
      'Then run the "Custom table of content" again.';
  var customTocData = [];
  for(var i=0;i<paragraphs.length;i++) { 
    if (paragraphs[i].getText().trim() == "") continue;
    if (paragraphs[i].getHeading() == DocumentApp.ParagraphHeading.NORMAL) continue;
    if (paragraphs[i].getHeading() == DocumentApp.ParagraphHeading.SUBTITLE) continue;
    if (paragraphs[i].getHeading() == DocumentApp.ParagraphHeading.TITLE) continue;

    if (dataCollectionStep == 2) {
      //extracting url-data and page numbers from the native table of contents 
      var nativeTocRecord = nativeToc.getChild(customTocData.length).asParagraph();
      var textAndPage = nativeTocRecord.getText().split(String.fromCharCode(9));        
      if (paragraphs[i].getText().trim() != textAndPage[0]) {
        statusMessage = "Error at row " + i + String.fromCharCode(10) + 'Please manually refresh the table of contents and try again!';
        return {
          content: null,
          dataCollectionStep : dataCollectionStep,
          alert: statusMessage
      heading : paragraphs[i].getHeading(),
      description : paragraphs[i].getText(),
      hyperlink : (dataCollectionStep == 2) ? nativeTocRecord.getChild(0).asText().getLinkUrl(2) : "",
      page : (dataCollectionStep == 2) ? textAndPage[1] : "N/A"
  //removing the native table of contents
  if (dataCollectionStep == 2) docBody.removeChild(nativeToc);
  return {
    content: customTocData,
    dataCollectionStep : dataCollectionStep,
    alert: statusMessage

function renderToc(textDoc,styleDoc,tocData){
  var textDocBody = textDoc.getBody();
  var styleHeaderText = styleDoc.getBody().getChild(0).getText().trim().toUpperCase();
  var styleTable = styleDoc.getBody().getChild(1).asTable();
  var customToc;
  var tocIdx = -1;
  var featuredHeadings = [];
  for (var i=0;i<styleTable.getNumRows();i++){
    var str = styleTable.getRow(i).getCell(0).getText().trim().toUpperCase();
      case "FIRST LEVEL" : featuredHeadings.push(DocumentApp.ParagraphHeading.HEADING1);break;
      case "SECOND LEVEL" : featuredHeadings.push(DocumentApp.ParagraphHeading.HEADING2);break;
      case "THIRD LEVEL" : featuredHeadings.push(DocumentApp.ParagraphHeading.HEADING3);break;
      case "FOURTH LEVEL" : featuredHeadings.push(DocumentApp.ParagraphHeading.HEADING4);break;
      case "FIFTH LEVEL" : featuredHeadings.push(DocumentApp.ParagraphHeading.HEADING5);break;
      case "SIXTH LEVEL" : featuredHeadings.push(DocumentApp.ParagraphHeading.HEADING6);break;
  //locate where to insert the custom Table of Contents, delete the current of if there is one and add an empty table element.
  //then copy the attributes from the table element in the style doc
  for(var i=0;i<textDocBody.getParagraphs().length;i++){
    //the below assumes that the text doc has a header paragraph with the same text (i.e "Table of content") as the header for the table in the style doc 
    if (textDocBody.getParagraphs()[i].getText().trim().toUpperCase() == styleHeaderText) {
      tocIdx = i+1;
      if (textDocBody.getChild(tocIdx).getType() == DocumentApp.ElementType.TABLE) textDocBody.removeChild(textDocBody.getChild(tocIdx))
      customToc = textDocBody.insertTable(tocIdx).asTable();
  if (!customToc){
    var errorMessage = "Can not figure out where to insert the Table of Contents. Please check that there is a header for it in the text document.";
    return {
      success : false,
      customTocPosition : tocIdx,
      alert : errorMessage

  for (var i=0;i<tocData.length;i++){
    var templateRowIdx = featuredHeadings.indexOf(tocData[i].heading);
    if (templateRowIdx < 0) continue;

    var styleRow = styleTable.getRow(templateRowIdx);    
    var destinationRow = customToc.appendTableRow(styleRow.copy());

    var destinationCell1 = destinationRow.getCell(0);
    //tablerow.copy() does not include the cell width of the copied child cells. Below is a hack to work around that...
    //setLinkUrl forces blue color and underline to text. Below is a hack to work around that...
      var underline = destinationCell1.isUnderline();
      var foregroundColor = destinationCell1.getForegroundColor();

    var destinationCell2 = destinationRow.getCell(1);
    //tablerow.copy() does not include the cell width of the copied child cells. Below is a hack to work around that...
  return {
    success : true,
    customTocPosition : tocIdx,
    comment : ''

Another option for the non-developers. I bookmarked the item and used normal text. This will allow me to link within the document without the items appearing in the Table of Contents. You can add manual formatting styles if need be to keep consistent.

Not the answer you're looking for? Browse other questions tagged or ask your own question.