Publishiing to Google Docs

Hi all,

I have completed my very first agent. yei!. Well, sort of, mostly completed. I created a workflow to write blogs as per my requirements. One issue I am having is publishing to Google Docs as a fully formatted document. H1, H2, and H3 are not being formatted, and instead come out as HEADING 2, HEADING 3 line items at the beginning of the paragraph. I have tried using an agent to format it and tried different prompts, but nothing seems to work.

The document comes out as plain text, no formatting whatsoever. I am also having issues getting it to hyperlink key phrases.

output from the last node before publishing is this

“

Easy Divorce: Your Guide to a Kinder, Smoother Separation

\n\n“I thought divorce meant war.” Those were the words Emily whispered between tears, sitting across from her friend at a café.\n\nAfter months of tension, her conversations with her spouse felt like walking through a minefield.\n\nBut something …”

When exporting to Google Docs, the h1 HTMLs vanish…

Hostinger hosts my n8n
V1.91.3
on Ubuntu 24.04

Hello @Oscar_Chavarria
Welcome to the n8n community and congrats for your first agent!

Unfortunately the current Google Docs node only inserts plain text. It doesn’t translate Markdown, HTML or heading tags, so everything lands as body text and the “H1/H2/H3” labels vanish. The same limitation prevents hyperlinks.

This is a known gap in the node. See also this topic to have more insights.

Here are a few ways to fix it:

  1. HTML file:
    Export your blog post as a proper HTML file (with real “h1”, “a href”, etc.) and upload it to Google Drive using the Upload File node. If you set the right mimeType, Drive will convert it into a Google Doc with the formatting preserved.

  2. HTTPS Request:
    Use an HTTP Request to call Google Docs API directly (specifically batchUpdate). That way, you can insert text and style it however you want.

1 Like

Can’t do it with google docs

If you want to use Notion to store your blog posts. I can help you create a page entry for each blog post with the formatting you need (headers, paragraph, bullets, etc…)

Thank you very much for this. ChatGPT created an HTTP request for me and gave me several different scripts, and they all kept failing. It suspects an error with my Oath authentication, but I can create blog posts, and my permissions look fine. So I will try the HTML file, ChatGPT did not think of that. Thanks for the clarification!

1 Like

Thanks for your comment. I am not using Notion yet, but are you saying that even then, I would need to create an entry page first? I cannot just publish directly a formatted doc?

There are 2 notion nodes in n8n the create and update pages.

You do have to create the page first using n8n, then using the ID of the new page, you have to call the append block notion node, which will make the content. Unfortunately, you do have to make 2 calls because of the way blocks work.

Notion is cool for blogging, because you can make a database where each page name is the title of the blog post and n8n will automatically pull in the page, perform the AI prompt to generate content and then update the pages content.

1 Like

Hi @Oscar_Chavarria

I made it working but using Google App Script with n8n,

Here is the n8n workflow.

We post the content and title to a App Script endpoint.
Here is the App Script code

// Main entry point for POST requests
function doPost(e) {
  try {
    // Parse the incoming JSON data
    const data = JSON.parse(e.postData.contents);
    const title = data.title || 'Untitled Document';
    const markdown = data.content || '';

    // Create a new Google Document
    const doc = DocumentApp.create(title);
    const body = doc.getBody();

    // Parse and insert Markdown content into the document
    parseMarkdownToDoc(markdown, body);

    // Save and close the document
    doc.saveAndClose();

    // Return the document URL and ID as JSON
    return ContentService.createTextOutput(JSON.stringify({
      success: true,
      url: doc.getUrl(),
      id: doc.getId()
    })).setMimeType(ContentService.MimeType.JSON);

  } catch (err) {
    // Handle errors and return the error message as JSON
    return ContentService.createTextOutput(JSON.stringify({
      success: false,
      error: err.message
    })).setMimeType(ContentService.MimeType.JSON);
  }
}

// Function to parse Markdown content and insert into the document body
function parseMarkdownToDoc(markdown, body) {
  const lines = markdown.split('\n');
  let inList = false;

  lines.forEach(line => {
    line = line.trim();

    // Handle headings (e.g., # Heading 1)
    const headingMatch = line.match(/^(#{1,6})\s+(.*)/);
    if (headingMatch) {
      const level = headingMatch[1].length;
      const text = headingMatch[2];
      const heading = body.appendParagraph(text);
      heading.setHeading(DocumentApp.ParagraphHeading['HEADING' + level]);
      inList = false;
      return;
    }

    // Handle unordered list items (e.g., - Item)
    if (/^[-*+]\s+/.test(line)) {
      const text = line.replace(/^[-*+]\s+/, '');
      const listItem = body.appendListItem(text);
      listItem.setGlyphType(DocumentApp.GlyphType.BULLET);
      applyInlineStyles(listItem);
      inList = true;
      return;
    } else if (inList && line === '') {
      // End the list when an empty line is encountered
      inList = false;
      body.appendParagraph('');
      return;
    }

    // Handle regular paragraphs
    if (line !== '') {
      const para = body.appendParagraph(line);
      applyInlineStyles(para);
    } else {
      body.appendParagraph('');
    }
  });
}

// Function to apply bold and italic styles based on Markdown syntax
function applyInlineStyles(paragraph) {
  const textElement = paragraph.editAsText();
  let text = textElement.getText();

  // Apply bold formatting for **text**
  const boldRegex = /\*\*(.+?)\*\*/g;
  let match;
  while ((match = boldRegex.exec(text)) !== null) {
    const start = match.index;
    const end = start + match[1].length - 1;
    textElement.setBold(start, end, true);
  }
  text = text.replace(boldRegex, '$1');
  textElement.setText(text);

  // Apply italic formatting for *text*
  const italicRegex = /\*(.+?)\*/g;
  while ((match = italicRegex.exec(text)) !== null) {
    const start = match.index;
    const end = start + match[1].length - 1;
    textElement.setItalic(start, end, true);
  }
  text = text.replace(italicRegex, '$1');
  textElement.setText(text);
}

Here is the result I got :

Maybe this can solve your problem!

1 Like

Thank you. I did manage to get it done and published. Minor hiccups in the final output, but it is 99% there. good enough for me.
Thanks again

1 Like