From WordPress to Hugo: Theming Is Not What You Think

In this post
Anyone who has worked with WordPress long enough develops a strong intuition for what a theme is and what it does. That intuition serves you well within the ecosystem: it guides decisions about file structure, where to put logic, and how to extend functionality. The trouble starts when you move to Hugo and try to apply the same mental model. The vocabulary overlaps — templates, layouts, partials — but what those words mean in practice is radically different.
A WordPress theme is, at its core, a full PHP application that queries a database, makes logic decisions, and renders HTML, all in one place. A Hugo theme is a collection of templates that receives pre-processed data and does nothing more than present it. It sounds like a subtle distinction until you sit down to build your first layout and realize that almost everything you knew needs to be relearned.
This post does not attempt to replace Hugo’s documentation or serve as a theme-building tutorial — if you’re looking for a practical step-by-step guide to building a blog with Hugo, Pages CMS, and Cloudflare, see Why leave WordPress — and what to build instead. The goal here is to map the conceptual differences between the two worlds — the WordPress and the Hugo mental models — so that the transition is less frustrating and more productive.
The WordPress Mental Model#
The Theme as an Application#
In WordPress, the theme is not just a visual layer. In practice, it is the application that runs the site. A theme decides which posts to fetch, how to filter results, which fields to customize, which scripts to load, and how to assemble each page. It has direct access to the database through the WordPress API, can register custom post types, create REST endpoints, manipulate queries, and even alter admin behavior. The boundary between “theme” and “plugin” is thin — and in practice many themes cross that line without hesitation.
This creates a specific mental model: when a WordPress developer thinks “theme,” they think of a complete package. Data structure, presentation logic, and markup live together, often in the same PHP file. The theme is the gravitational center of the project — nearly everything passes through it or depends on it.
The Loop, the Database, and functions.php#
The heart of a WordPress theme is the Loop. Before any template renders, WordPress has already queried the database based on the URL. The theme receives that query and iterates over the results with while ( have_posts() ). It looks simple, but the implication runs deep: the theme operates at runtime, responding to each visitor request with a MySQL query.
The functions.php file reinforces this model. It acts as the theme’s bootstrap — where you register menus, sidebars, image sizes, enqueue scripts and stylesheets with wp_enqueue_script, add support for core features, and inevitably write logic that probably belongs in a plugin. It executes on every page load, which means any code there has access to the full WordPress state at that moment: the logged-in user, the current query, database options, available hooks. It is power and responsibility in one file — and the reason WordPress themes can become as complex as the application they supposedly just “dress up.”
The Hugo Mental Model#
The Theme as a Template Layer#
In Hugo, the theme is exactly what the name suggests: a presentation layer. It does not query a database because there is no database. It does not execute logic at request time because there is no request time — everything happens at build time, before the site is published. The output is a set of static HTML files that can be served by any web server with no runtime dependencies.
A Hugo theme is a collection of template files written in Go’s template language. These templates define how content is presented, but they do not define what content exists or how it is structured. That responsibility belongs to the Markdown files and the site configuration. The theme receives everything pre-processed — taxonomies resolved, pages sorted, parameters available — and its only job is to turn that data into HTML. If the WordPress theme is the conductor leading the orchestra, the Hugo theme is the sheet music: it defines the form, but it does not play the instruments.
Content Lives in Markdown, Logic Lives in the Build#
In WordPress, content lives in the database and is only accessible through the API. In Hugo, content is in text files. Each post is a Markdown file with a front matter block — metadata in YAML, TOML, or JSON at the top — followed by the body text. There is no intermediary: what sits in the content/ directory is what Hugo processes.
This separation has practical consequences that catch WordPress developers off guard. In WordPress, adding a custom field to a post means using the meta fields API or a plugin like ACF, then accessing the value with get_post_meta() inside the theme. In Hugo, you simply add a key to the Markdown file’s front matter and access it with .Params.keyname in the template. No intermediate layer, no plugin, no database — just text and templates. The logic available in Hugo templates is limited to conditionals, loops, and formatting functions. It exists to decide how to present data, never to fetch or transform it in complex ways. Anything more elaborate happens in Hugo’s build pipeline, outside the theme’s reach.
What Catches You Off Guard#
Template Hierarchy: Convention vs Lookup Order#
In WordPress, the template hierarchy is a cascade based on file names. When a visitor hits a category page, WordPress looks for category-slug.php, then category-id.php, then category.php, then archive.php, and finally index.php. You learn this sequence once and apply it for the rest of your career. It is predictable, well-documented, and rarely causes confusion.
In Hugo, the equivalent system is the lookup order — and it is considerably more complex. The template used to render a page depends on a combination of content type, layout, section, output format, and language. A page of type post in section blog with layout single in Portuguese triggers a search through dozens of possible paths before Hugo finds a matching template. The documentation includes a massive table for each page kind detailing the full order. In practice, the developer coming from WordPress creates a file with the “obvious” name and gets frustrated when Hugo silently ignores it — because the name does not match any valid position in the lookup order. The learning curve here is real, and the solution is to consult the documentation until the system becomes second nature.
Child Themes vs Hugo Overrides#
In WordPress, the child theme mechanism is a central piece of the ecosystem. You create a directory with a style.css referencing the parent theme, add its own functions.php, and from there you can override any parent template by placing a file with the same name in the child theme directory. This lets you customize without touching the parent theme’s code — essential protection for surviving updates.
In Hugo, this concept exists in a simpler and more direct form. Any template file placed in the project’s root layouts/ directory takes precedence over the equivalent file inside the theme. There is no need to declare a “child theme” or create a special configuration file. If the theme defines layouts/_default/single.html and you create the same path at your project root, Hugo uses your version. The model is transparent and works well, but it requires discipline: there is no mechanism that warns you which templates you have overridden, and after a theme update the developer needs to manually verify whether the overrides are still compatible.
No Plugins — Now What?#
The plugin ecosystem is one of WordPress’s greatest strengths and one of its greatest sources of complexity. Need a contact form? Plugin. SEO? Plugin. Cache? Plugin. Image gallery? Plugin. Themes are frequently built assuming certain plugins exist, and removing one can break the site in unpredictable ways.
Hugo has no plugin system. This is probably the difference that disorients WordPress developers the most. Functionality that would come from a plugin in WordPress is handled differently in Hugo: custom shortcodes for reusable components inside content, partials for template fragments, Hugo modules for importing functionality from external repositories, and data processing with JSON, YAML, or CSV files in the data/ directory. None of these solutions offer the convenience of WordPress’s “install and activate” — all of them require the developer to understand what they are doing and write or adapt code. On the other hand, there is no fragility of depending on third-party code executing in production on every request.
For maintenance tasks like keeping tags consistent and generating meta descriptions for SEO, external tools fill the gap. Hugin, for example, uses AI to suggest tags and summaries directly in Markdown files, without relying on runtime plugins.
Assets: From enqueue to Hugo Pipes#
In WordPress, the correct way to include CSS and JavaScript is through the enqueue system: wp_enqueue_style() and wp_enqueue_script(). These functions register dependencies, control loading order, allow scripts to be conditional to specific pages, and prevent duplication. It is a robust system, but one that operates at runtime — each page assembles its asset list dynamically.
Hugo Pipes solves the same problem in a radically different way. Assets are processed during the build: SCSS is compiled, JavaScript is bundled and minified, fingerprinting is applied for cache busting, and the result is static files with final paths. All of this is declared in templates with functions like resources.ToCSS, resources.Minify, and resources.Fingerprint, chained in a pipeline. There is no concern about runtime loading order because there is no runtime. The template declares what it needs, Hugo processes it during the build, and the final HTML ships with correct paths to already-optimized files. For anyone who spent years fighting jQuery conflicts and scripts loading out of order, the predictability of Hugo Pipes is a relief.
What Changes in Your Mind#
The transition from WordPress to Hugo is not just technical, but more of a shift in perspective. In WordPress, the theme developer operates with the mindset of someone building an application: they think about state, sessions, queries, and hooks that fire at specific moments in the request lifecycle. In Hugo, that complexity simply does not exist. The entire site is generated at once, in seconds, and the result is a directory of HTML files that need nothing to function beyond a web server capable of serving static files.
This simplicity comes with a cost of entry. The developer loses the flexibility to make decisions at request time: there is no way to show different content to a logged-in user, no way to process a form on the server, nor to perform a dynamic search without resorting to external services. Those coming from WordPress need to accept that these limitations are not flaws; they are consequences of an architectural choice that prioritizes speed, security, and predictability. A Hugo site has no runtime attack surface because there is no runtime. There is no database to be breached, no PHP to be exploited, no plugins with vulnerabilities waiting to be discovered.
The most significant gain, however, is cognitive. When the theme is just a presentation layer and content is text files versioned in Git, the developer can hold the complete model of the site in their head. There are no surprises hidden in the database, no implicit logic in hooks that someone added three years ago, no mysterious state that changes between one request and the next. What you see in the files is what exists. This transparency changes how you work — debugging a problem in Hugo means reading templates and checking front matter, not digging through MySQL tables trying to understand why a post looks one way on the homepage and another on the category page.
Closing Thoughts#
The question is not which tool is better. WordPress remains the right choice for many projects — especially those that need dynamic content, multi-user management, or integration with a vast plugin ecosystem. The point is that moving from WordPress to Hugo without recalibrating your mental model is a recipe for frustration. The concepts do not translate directly, and trying to force one into the other only produces Hugo themes that feel like workarounds from someone still thinking in PHP and MySQL. The real investment in the transition is not learning Go template syntax — that takes days. It is unlearning twenty years of conditioned reflexes about what a theme should be and what it should do.
Sysadmin, 53, Brazilian working from home for the world. Manages Linux servers, LXC containers, and cats that won't get off the keyboard.