Alright, let’s talk about text. Because let’s be honest, most of what you’re building with Hugo is just glorified text wrangling. You’re taking content written by humans (who are, famously, inconsistent) and trying to make it look presentable for other humans. It’s a messy job, but Hugo’s string functions are your first line of defense. These are the digital equivalent of a trusty multi-tool: not always glamorous, but absolutely essential for not looking like an amateur.

The Workhorses: lower, upper, title, and trim

These functions do exactly what they say on the tin, but the devil is in the details.

lower and upper are your case-conversion buddies. Use them for normalizing user input or for programmatic comparisons. title is a bit more opinionated; it capitalizes the first letter of each word. This is great for headings but can get weird with words like “the” or “and”.

{{ "hello WORLD from HUGO" | lower }} → "hello world from hugo"
{{ "hello WORLD from HUGO" | upper }} → "HELLO WORLD FROM HUGO"
{{ "the great gatsby" | title }} → "The Great Gatsby"

Now, trim. This one seems simple but trips people up constantly. It doesn’t remove whitespace inside your string, only from the beginning and end. And here’s the kicker: by default, it removes all Unicode whitespace. That’s not just spaces and tabs, but also non-breaking spaces ( ), which you might have littering your content from a WYSIWYG editor. It’s ruthlessly effective.

{{ "   too much space   " | trim }} → "too much space"
{{ "\u00a0hello world\u00a0" | trim }} → "hello world" // \u00a0 is a non-breaking space

The Swiss Army Knife: replace

This function is straightforward but incredibly powerful. You give it an old string, a new string, and it makes the swap. The one “gotcha” is that it replaces all instances it finds, not just the first one. This is almost always what you want, but it’s good to know.

{{ "Hello Hugo" | replace "H" "J" }} → "Jello Jugo" // See? All of them.

Its real power comes from chaining it with other functions or using it with Hugo’s .Page variables. Need to sanitize a filename for a CSS class? No problem.

{{ replace (lower .File.LogicalName) " " "-" }} → "my-great-post.md" becomes "my-great-post"

The Deceptively Complex One: truncate

Oh, truncate. You look so simple. {{ .Summary | truncate 50 }}? Easy. But you, my friend, are a trap. The first thing you need to know is that truncate counts runes (roughly, characters), not bytes. This is actually good—it means your multi-byte Unicode characters (like emoji 👍) count as one, not a random number of bytes.

The real trap is what it does at the end. By default, it adds an ellipsis (...) and those three dots count against your total length. So if you write truncate 50, you’re actually only going to get 47 characters of your content plus the ellipsis. This has caused more than one developer to stare blankly at their screen wondering where their text went. Plan accordingly.

{{ "This is a very long sentence that needs to be shortened." | truncate 20 }}
→ "This is a very..." // See? 17 characters + "..." = 20.

You can also change the ellipsis to anything you want, or even nothing, with the optional second argument.

{{ "This is a very long sentence." | truncate 20 "…" }} → "This is a very long…"
{{ "This is a very long sentence." | truncate 20 "" }} → "This is a very long s"

The Power User’s Choice: printf

This is where you graduate from simple text manipulation to building strings with intention. printf is Hugo’s direct portal to Go’s fmt package. It doesn’t just format strings; it formats values into strings. You use verbs like %s for strings, %d for integers, and so on.

Why use this over simple concatenation with {{ $varA }}{{ $varB }}? Control and clarity. It’s invaluable for creating precise URLs, file paths, or any output where formatting is critical.

{{ $name := "Hugo" }}
{{ $year := 2023 }}
{{ printf "Welcome to %s, now in its %dth year!" $name $year }}
→ "Welcome to Hugo, now in its 2023th year!"

// Building a path is a classic use case:
{{ $base := "https://example.com/images" }}
{{ $file := "cat.jpg" }}
{{ printf "%s/%s" $base $file }} → "https://example.com/images/cat.jpg"

The learning curve is memorizing the format verbs, but it’s a skill that pays dividends far beyond Hugo. It’s the difference between banging rocks together and starting a fire. Use it.