n8n logon8n Automation Hub
Debugging notes

16 of 18 YouTube Descriptions Showed a Literal \n\n Instead of a Line Break

Debugging notes · n8n workflow · September 19, 2026 · Updated October 13, 2026

The workflow log was clean, every upload succeeded, and the channel looked fine at a glance. It was only when I read the published videos back through the YouTube Data API for an unrelated audit that the descriptions showed a literal backslash-n-backslash-n between the description text and the hashtags.

Workflow at a glance
  1. A publisher workflow uploads a video with a description taken from a Sheets row plus a line of hashtags built from the row's tags
  2. The field held one expression, then \n\n typed as plain text, then a second expression: {{ description }}\n\n{{ hashtags }}
  3. YouTube received the two characters backslash and n, twice, and rendered them literally on 16 of the 18 videos live at the time
  4. n8n only processes an escape sequence inside a JavaScript string literal within the braces; text outside the braces is copied through unchanged
  5. Fix: one expression that concatenates everything, with the \n\n inside quotes, then a backfill of the videos already published

What the description looked like on the watch page

A description that should have been a paragraph, a blank line and a hashtag line rendered as one paragraph with \n\n sitting in the middle of it in plain text. It was cosmetic, but it appeared on every video that had gone through that upload node.

The field that caused it

The upload node's description field was built like this (references simplified):

={{ $json.description }}\n\n{{ $json.tags.split(', ').slice(0, 4).map(t => '#' + t.replace(/\s+/g, '')).join(' ') }}

It reads naturally: description, blank line, hashtags. The \n\n is sitting between two {{ }} blocks, which is the problem.

A two-line experiment

On a scratch workflow I put both forms into a Set node and looked at the output:

={{ 'desc' }}\n\n{{ 'tags' }}          -> desc\n\ntags     (backslash, n, backslash, n)
={{ 'desc' + '\n\n' + 'tags' }}        -> desc, a blank line, tags

In the first form the \n\n is template text. n8n substitutes the braces and leaves everything else exactly as typed. In the second it is a string literal inside JavaScript, where \n means a newline.

The fix, and the backfill

The live node now builds the whole description as a single expression:

={{ $('Limit to 1 Video').item.json.description + '\n\n' + $('Limit to 1 Video').item.json.tags.split(', ').slice(0, 4).map(t => '#' + t.replace(/\s+/g, '')).join(' ') }}

Fixing the node only helps future uploads. The 16 affected videos were rewritten through the YouTube API's videos.update, replacing the literal sequences in the description with real newlines and leaving the other fields as they were. Then all 18 were read back to confirm none still contained a literal backslash-n.

The habit

Anywhere an n8n field mixes expressions with text that contains \n, put the whole value in one expression. And after any change to how text is published, read one published item back from the platform instead of trusting the execution log. The log records that the upload happened, not what the platform stored.

Where else the same mistake hides

The YouTube description was the field where I noticed it, but the rule is about n8n parameter fields in general, so the same pattern breaks anything that sends text to a platform:

The common factor is always the same: text with an escape sequence typed outside the {{ }} braces of a field in expression mode.

Three ways to write a multi-line field correctly

1. One expression with string concatenation. This is what the live workflow uses now:

={{ $json.description + '\n\n' + $json.hashtags }}

2. A template literal inside the braces. Backticks let you write it more readably, and the escape sequence is still inside JavaScript:

={{ `${$json.description}\n\n${$json.hashtags}` }}

3. Real line breaks in the field. Pressing Enter in the expression editor puts an actual newline character into the template text, and n8n copies that through unchanged, which is what you want. This works, but it is easy to lose when someone later edits the field in a single-line input, so I prefer option 1 for anything that gets published.

For longer texts with several optional parts, a Code node that builds the string and returns it as one field is clearer than any of the three, and it is easier to test.

How to find every affected field on an instance

You don't have to open workflows one by one. The n8n public API returns each workflow's JSON, and a search over the node parameters finds the pattern. A rough filter that worked for me: look for parameter values that start with =, contain }}, and have the two characters backslash and n somewhere outside a pair of braces. In practice I searched for the string }}\n in the exported JSON, then checked each hit by hand. Every hit was a real bug.

For content that was already published, the check has to happen on the platform side. For YouTube that meant reading the videos back with the Data API and searching each description for the literal sequence before and after the backfill.

Frequently asked questions

Why does n8n not convert \n into a line break in my field?

In an expression field, only the parts inside {{ }} are evaluated as JavaScript. A \n typed outside the braces is plain template text, so n8n passes the backslash and the letter n through unchanged.

Does this also happen in Code nodes?

No. In a Code node every string is JavaScript, so '\n' inside quotes is a real newline. The problem only exists in parameter fields that mix template text with expressions.

How do I fix videos or posts that were already published?

Fix the node first, then rewrite the stored text through the platform's API (for YouTube, videos.update), and read every item back afterwards to confirm the literal sequence is gone.

Is a template literal with backticks safe in n8n expressions?

Yes. Template literals are normal JavaScript inside the braces, and escape sequences in them behave as in any JavaScript string.

Text that looks fine in n8n but wrong on the platform?

I check what actually lands on the platform, not just what the execution log says was sent.