ACF + WordPress: Why I Still Use It for Custom Websites

Quick Summary
- ACF's structured fields keep a signed-off custom design intact, because editors fill in content while templates decide how it looks.
- Flexible Content layouts map to template parts, shared content lives once in options pages, and field groups are committed to Git as JSON.
- Skip ACF for block themes, teams that need layout freedom, or bulk records like bookings, and route template calls through helpers with fallbacks.
The theme that runs my site has 24 page layouts, and not one of them has a field for color, font size or alignment. That isn’t an oversight. It’s the point, and it’s why this WordPress theme is built on Advanced Custom Fields (ACF).
ACF isn’t the fashionable answer anymore. The block editor keeps improving, full site editing is where WordPress core is heading, and page builders still promise that anyone can build anything. For a custom WordPress design that a founder has signed off on, I still reach for ACF first. Here’s the case for it, the patterns I actually use, and the projects where I wouldn’t.
Structure beats freedom when the design is the product
Most clients don’t want to design pages. They want to change a headline, swap a project image or add a testimonial, and know the page will still look like the site they paid for.
A free-form editor answers a different question: what if the editor wants to change everything? That freedom has a cost. Every spacing control and color picker is a way for a page to drift from the design, one well-meant edit at a time, until the site looks like five people built it.
Structured fields flip the arrangement. The design decides the layout; the fields hold the content. The editor sees a box labeled “Title”, and the template decides what a title looks like. I tell clients that what they approve is exactly what goes live. Structured content is how that promise survives the first year of edits, not just launch day.
Even the switches follow that rule. Across all 24 layouts there are exactly two on/off fields: one decides whether the home page shows its loader, the other whether a testimonials wall skips reviews already featured higher up the page. Both are content decisions with a design already behind them, not style knobs.
ACF Flexible Content is a WordPress page builder that can’t break the design
The usual objection to structured fields is rigidity: every page gets the same template. Flexible Content removes that objection. A page becomes a stack of sections the editor can add, remove and reorder, but every section comes from the approved design.
On my site, each section of the design became one layout (heroes, featured work, services, pricing, FAQ, testimonials and so on), and each layout maps to a template part with the same name. The page template is a loop:
<main id="main">
<?php
if ( function_exists( 'have_rows' ) && have_rows( 'sections' ) ) :
while ( have_rows( 'sections' ) ) :
the_row();
// Layout "featured_works" → template-parts/sections/featured-works.php
get_template_part( 'template-parts/sections/' . str_replace( '_', '-', get_row_layout() ) );
endwhile;
endif;
?>
</main>Inside each template part, the row’s fields are read, escaped and arranged. Nothing else. Adding a section means one new layout in ACF and one new file in the theme, and nothing an editor does can produce a section the design didn’t include.
The editing experience matters as much as the code. Sections reorder by drag and drop. Collapsed rows show the layout name, and a small filter on acf/fields/flexible_content/layout_title adds each section’s own title next to it, so a long page stays scannable in the admin instead of reading “Featured Works, Featured Works, CTA”.
Shared content lives once, in an options page
Some content isn’t page content at all. My services list, the process steps, pricing, the FAQ and the testimonials heading each appear on more than one page. Copy them into every page’s fields and sooner or later the site has two different price lists.
So they live once, under Theme Settings → Shared Sections. On a page, those layouts have no fields of their own, only a short note telling the editor where the content is edited. Change the FAQ once and every page that includes the FAQ section changes with it.
add_action( 'acf/init', function () {
if ( ! function_exists( 'acf_add_options_page' ) ) {
return;
}
acf_add_options_page( [
'page_title' => __( 'Theme Settings', 'mytheme' ),
'menu_slug' => 'theme-settings',
'capability' => 'edit_theme_options',
'redirect' => true, // the menu opens the first sub page
] );
acf_add_options_sub_page( [
'page_title' => __( 'Shared Sections', 'mytheme' ),
'menu_slug' => 'theme-settings-shared',
'parent_slug' => 'theme-settings',
] );
} );The field types inside those groups carry decisions too. The testimonials carousel uses a relationship field, so the reviews in it are hand-picked rather than pulled in automatically, the same idea as hand-picked related posts with an ACF Relationship field. A long review made a card look odd, and choosing which reviews appear is a better answer than truncating someone’s words.
Reading a value is get_field( 'faq_items', 'option' ). Images are the usual stumbling block, because the field’s return format decides whether you get an ID, a URL or an array; I covered that case in how to get an image URL from an ACF options page, and the Image ID return format in outputting an ACF image field.
Field groups belong in Git
ACF’s weakest point used to be that the content model lived only in the database. You built fields on staging, rebuilt them by hand on production, and hoped you ticked the same boxes.
Local JSON fixes that. With an acf-json folder in the theme, ACF writes each field group to a JSON file whenever you save it, and loads field groups from those files. Commit the folder and the fields travel with the templates that depend on them. On another environment, ACF offers to sync when the files are newer than the database. I set the save and load paths explicitly in code anyway, so nobody has to know the convention; the snippet is in Add ACF Local JSON.
This matters more than it sounds. A template that reads a portrait field and the field group that defines it are one change. They should be reviewed together and deployed together.
Keep the dependency on a short leash
The honest cost is that the theme depends on a plugin. Flexible Content and options pages are ACF PRO features, and a bare get_field() call turns into a fatal error the moment someone deactivates ACF (the basic guard is checking whether ACF is available).
Two small habits contain that. Templates don’t call ACF directly; they go through thin helpers that return a fallback when ACF is missing or a field is empty. And the theme shows an admin notice when ACF PRO isn’t active, so whoever logs in sees why the pages look empty instead of guessing. A plugin update gone wrong isn’t a content model problem, but the client will experience it as one.
Where I wouldn’t use ACF on a WordPress site
ACF answers one question very well: how do you give non-designers safe control over a site with one fixed design? Change the question and the answer changes.
- Block themes and full site editing. If the client wants to compose templates in the Site Editor and manage styles globally, ACF-driven templates work against that model. ACF can register blocks, which is a reasonable middle ground, but then you’re building for the block editor and should design for it properly.
- Editorial teams that need layout freedom. A marketing team that launches a new landing page shape every week will hit the edge of any fixed set of layouts. Every new layout is a developer task. For that team, the constraint is a bottleneck, not a safeguard.
- Data that isn’t really content. Thousands of records you need to filter, sort and report on, like bookings or inventory, belong in proper tables or a real application, not in post meta.
None of these are complaints about ACF. They’re reminders that structure is a choice, and it’s the wrong one when the people editing the site are supposed to be designing it too.
For everyone else, the pattern I keep coming back to is short:
- Flexible Content sections for layout, one template part per section.
- Options pages for anything that appears on more than one page.
- Field groups as JSON, committed next to the templates that read them.
- Thin helpers between templates and ACF.
It isn’t new, and that’s the point. When a client opens the editor two years from now, the site should still look like the one they approved. If you’re moving an existing site to this setup, the traps are in building a WordPress theme without Elementor, and how I structure a WordPress project shows where each of these pieces lives.

