If you build client sites with WordPress, Advanced Custom Fields (ACF) is an indispensable tool for structuring your content. In this post, I’ll show you how to leverage the power of ACF to allow your clients to update and edit their own content without knowing any code, while helping you streamline your development workflow in the process. If that sounds like a win-win, it is!
This post expands on the material presented in the talk I gave at WordCamp Chicago 2018.
I’m assuming you have some familiarity of the Advanced Custom Fields plugin and for the purposes of this article, I will be referencing the Pro version which includes the Repeater Field, Flexible Content Field, Gallery Field and Options Field which we will put to good use. If you don’t have the Pro version, get it now…it will be the best money you’ve ever spent on WordPress development.
ACF Field Basics
As a developer, using ACF involves two separate but interdependent tasks:
-
Creating ACF fields to structure your content
-
Adding the ACF calls in your theme templates to display the content
Let’s go over both of these tasks in detail.
Task 1: Creating ACF Fields
When adding ACF fields to any project, it’s always good to sketch out a rough idea of what fields you will need and how to organize them. This gets easier the more you use ACF but I’d like to provide some pro tips that you can take advantage of right away.
You don’t have to map out all of your fields at once yet doing it Field Group by Field Group when you need them is a good way to start.
Ok on to our tips!
Tip #1: Ask some basic questions to help figure out what ACF fields you will need:
-
Do I need extra fields on all Pages or Posts, or only on specific ones? Which ones?
-
Will I be adding to the built-in Content box or replacing it completely? (More about this later).
-
Do I have any Custom Post Types and if so, what fields will those need?
-
Will I need extra fields for Products for WooCommerce? For example, if you are selling clothes, you may want to add a custom field for detailed measurements or other information that the default WooCommerce fields don’t cover.
With the answers to the above, you can start to get an idea of what fields you will need yet each site and situation is different. The fields will follow from the nature of the content for your particular site and as you use ACF more, field organization will get more intuitive.
In general, it’s a good idea to keep things general and flexible in the early stages as your content may change or you may think of a better way of doing things. If you change the name of your fields, usually that means any content you’ve entered will need to be re-entered so it’s better to have your Field Groups and their respective fields set before starting to enter content.
Tip #2: Use a Global Field Group for fields you need on every edit screen for a post type.
Start creating a Field Group by going here from the WP admin:
Custom Fields > Field Groups > Add New
If you want, say, a header block with an banner image, tagline, and call-to-action on every Page, set up a group named ‘Global Pages’. Use the ‘Group’ field type to collect your ‘Page Header’ fields together, using the Location rule to assign the Global Pages Field Group to any Page:
Then, within your Page Header Group, you could have the following fields:
Now the Page Header group will show up on every Page edit screen and is ready for you or your clients to add content. Sweetness.
Any other Global Page fields you need on your Pages you can now add to this Field Group.
Aside: What is the difference between an ACF Field Group and the Group field?
I’m glad you asked as these can get confusing. The way I like to think of these is:
-
A Field Group is a top-level collection of ACF Fields using a set of Location Rules.
-
The Group Field is a lower-level collection of ACF Fields within a Field Group. Think of it like a subfolder to keep your ACF fields logically grouped within a Field Group.
Note that you don’t need to use the Group field type — it only really helps if you have multiple groups of fields within a Field Group you need to keep organized.
For example, let’s say later you wanted to add a ‘Page Footer’ section to every page with say a callout image and link, so you add a ‘Page Footer’ group within your Global Page Field Group. Your fields are nice and tidy so by keeping related fields in a Group field, you’ll keep things organized for both you and your clients.
More about keeping organized in the next tip.
Tip #3: Keep your field names as general as possible
One of the best things about ACF is that you do not need unique names for every field — even for fields on the same edit screen! ACF handles giving unique identifiers to every field so you don’t have to.
Thus, try to use general field names like ‘Heading’, ‘Image’, or ‘Description’. The rationale for keeping these fields general is simple: content can change in the future and you want to keep your fields flexible to account for that.
When I take over projects using ACF, I often see devs labeling their fields with crazy specific names like ‘Title Hero Top Left Blue’. What happens when you want to move that to the right? Or to another page? Or make it purple? Maybe you can keep track of these changes but then when you hand your site to your clients they can’t…and your code is now a mess. Nobody likes a mess! Think modularly and keep your fields and Field Groups agnostic so they could work anywhere.
Notice how in our ‘Page Header’ section above, we labeled the fields ‘Banner’ (for the banner image), ‘Tagline’, and ‘Call To Action’. There’s nothing overly specific here so if need to move these fields to another Field Group or call them somewhere else in our theme, we can do that easily without having to change our code aside from maybe moving it around a bit or a little copy/paste.
Your field names should simply describe what the field is — no more, no less. Use the Group or Repeater field types or Field Group to provide the context. Thinking modularly like this keeps your fields and Field Groups free to be anywhere.
Trust me — clients change their minds and their content all the time so make it easy on yourself when this will inevitably happen. You’ll thank yourself later!
Tip #4: Using the Repeater field
The Repeater field can be intimidating at first but once you get familiar with it, you’ll find it is one of the most powerful fields in your arsenal that you will use often.
When to use the Repeater field?
Most simply put: use the Repeater field when you have a group of fields that apply to more than two or an unknown number of items.
The ‘items’ in this case could be anything: services, logos with links, content sections, or a list of uploaded documents.
Let’s start with the first example: services. Imagine you are working on a client’s site that needs to show the services offered they offer so in each Service item you’ll have a heading, icon, description, and button.
Let’s also assume you have a page called ‘Services’ that you will be attaching these fields to.
Here’s how the Repeater field will be set up:
We’ve got our ‘Services’ Repeater field and assigned it with the Location rules for a Page called ‘Services’.
Now, let’s add our individual fields in the Repeater. In the ‘Sub-Fields’ section, we’ll add our Heading, Icon, Description, and Button fields:
We’ve set the Minimum Rows to ‘1’ as we know we will have at least one Service, and changed the Button Label to ‘Add Service’ so you or your client can add a Service in the future.
While you may be the one entering in all the content for your clients, the goal is still to have it be editable and updatable by non-coders. Keep things simple and organized and your content and code will be easier to navigate and maintain.
Let’s go over the individual fields and in each Service and how to set them up:
Heading: this will just be a simple Text field as it will be short and we won’t need any extra formatting.
Icon: this is just a regular Image field. Super Pro Tip: If you are using .svg
files, then use the Image Array
return type, otherwise if you are using a .png
or .jpg
image use the Image ID
return type as this works with the WordPress built-in srcset
function to serve responsive images.
As a general rule, if you not using .svg
, then upload an image at @3x the size you need to support responsive and retina images.
Your clients probably won’t know how to format images so always include some instructions. I like to put the instructions with the top-level Repeater field itself, rather than with the Image field so then it isn’t…wait for it…repeated with every item as that clutters up your edit screens real fast.
For more information on the Image field type or any of the field types described here, the ACF docs are a fantastic resource with examples and code snippets for every field type.
Description: here you have a choice between the Text Area field or the WYSIWYG field. In general, if the Descriptions will need formatting like bold or italic, use the WYSIWYG field, and if not, use the Text Area field. Note that you can add HTML to a Text Area field but depending on who will be updating the site, they may want the help of Quicktags which are only found on the WYSIWYG field.
Settings for the WYSIWYG field: for Tabs I use ‘Text Only’ and have ‘Show Media Upload Buttons?’ set to ‘No’:
This way, the field conforms to its context yet doesn’t give too many options like Media Uploads where they aren’t necessary.
Button: usually for buttons I just use the Link field and add my button class(es) as you can specify the link url and the link text. There is no default ACF ‘Button’ field and some may argue that you should use a <button>
element here. So, if you want to use separate Text and Link fields and wrap those in a <button>
element in your theme, that works too.
Great! Now you have your set fields that can apply to any number of Services your client wants to display. If they need to add one in the future, the Repeater Field makes that a breeze (you can also delete with a click of the minus button). Your clients can also drag-and-drop the Services to reorder them giving them ultimate control over their content.
Repeater fields and unknown content
We’ve seen how the Repeater field can work if you have known discreet fields, but what if you don’t know exactly what the content will look like?
To illustrate this, let’s say your client has a page for which you’ve set up a Repeater field of ‘Content Sections’, each will include a ‘Heading’ Text field and a ‘Content’ WYSIWYG field.
Perhaps one Content section has a bullet list, while another has a <h3>
sub-heading and a diagram (image). The takeaway here is that you won’t always be able to create discreet fields around all of the content all of the time so in that case, use the WYSIWYG field which — like the main WordPress Content box — is adaptable to any kind of content. While you can try to avoid having to enter HTML in custom fields sometimes it is unavoidable — and that’s ok!
The trick is to find the right balance between discreet custom fields which wrap snugly around predetermined content and more general, flexible fields that can adapt to anything. There’s no right way and the more you use ACF, the more you’ll figure out what feels right for your particular situation and content.
Tip #5: Arrange fields in the order they will display on the site.
This may seem completely obvious but it bears repeating:
ACF fields on edit screens should directly follow the order they will appear on the front end of the site.
While HTML in the default WP content box is parsed from top to bottom, ACF fields from anywhere can be called anywhere in your theme — even from other pages. Present them in their proper order so that your clients will intuitively know what goes where.
Tip #6: Using Custom Post Types and ACF
To illustrate custom post types and ACF, let’s use a real-world situation. At studio.bio, we have a lot of non-profit organizations as clients and invariably these organizations will want a Staff page on their site.
While we could use the ACF Repeater Field to create all the fields for an unknown number of staff members on a Staff page, in this case creating a staff
custom post type makes the most sense. Why? Because we may want to show some staff members on the Home page or, say, a Leadership page and thus we don’t want this content tied to a particular page.
However you normally create your CPTs is fine (like using the Custom Post Type UI plugin), but I like to use the GenerateWP site to create the CPT code.
Here’s an example of a staff
CPT, output from the GenerateWP Custom Post Type generator, added to a plugin:
<?php
/*
Plugin Name: Staff CPT
Plugin URI: https://data.studio.bio/
Description: Another studio.bio plugin
Version: 0.1.0
Author: Joshua Michaels for studio.bio
Author URI: https://data.studio.bio
License: WTFPL
*/
// Register Custom Post Type
function sb_staff_cpt() {
$labels = array(
'name' => _x( 'Staff', 'Post Type General Name', 'platetheme' ),
'singular_name' => _x( 'Staff', 'Post Type Singular Name', 'platetheme' ),
'menu_name' => __( 'Staff', 'platetheme' ),
'name_admin_bar' => __( 'Staff', 'platetheme' ),
'archives' => __( 'Staff Archives', 'platetheme' ),
'attributes' => __( 'Staff Attributes', 'platetheme' ),
'parent_item_colon' => __( 'Parent Staff:', 'platetheme' ),
'all_items' => __( 'All Staff', 'platetheme' ),
'add_new_item' => __( 'Add New Staff', 'platetheme' ),
'add_new' => __( 'Add New Staff', 'platetheme' ),
'new_item' => __( 'New Staff', 'platetheme' ),
'edit_item' => __( 'Edit Staff', 'platetheme' ),
'update_item' => __( 'Update Staff', 'platetheme' ),
'view_item' => __( 'View Staff', 'platetheme' ),
'view_items' => __( 'View Staff', 'platetheme' ),
'search_items' => __( 'Search Staff', 'platetheme' ),
'not_found' => __( 'Not found', 'platetheme' ),
'not_found_in_trash' => __( 'Not found in Trash', 'platetheme' ),
'featured_image' => __( 'Featured Image', 'platetheme' ),
'set_featured_image' => __( 'Set featured image', 'platetheme' ),
'remove_featured_image' => __( 'Remove featured image', 'platetheme' ),
'use_featured_image' => __( 'Use as featured image', 'platetheme' ),
'insert_into_item' => __( 'Insert into item', 'platetheme' ),
'uploaded_to_this_item' => __( 'Uploaded to this item', 'platetheme' ),
'items_list' => __( 'Items list', 'platetheme' ),
'items_list_navigation' => __( 'Items list navigation', 'platetheme' ),
'filter_items_list' => __( 'Filter items list', 'platetheme' ),
);
$rewrite = array(
'slug' => 'staff',
'with_front' => true,
'pages' => true,
'feeds' => true,
);
$args = array(
'label' => __( 'Staff', 'platetheme' ),
'description' => __( 'Staff Custom Post Type', 'platetheme' ),
'labels' => $labels,
'supports' => array( 'title', 'editor', 'thumbnail', 'revisions', 'page-attributes' ),
'taxonomies' => array( 'category', 'post_tag' ),
'hierarchical' => false,
'public' => true,
'show_ui' => true,
'show_in_menu' => true,
'menu_position' => 5,
'menu_icon' => 'dashicons-groups',
'show_in_admin_bar' => true,
'show_in_nav_menus' => true,
'can_export' => true,
'has_archive' => 'staff',
'exclude_from_search' => false,
'publicly_queryable' => true,
'rewrite' => $rewrite,
'capability_type' => 'page',
'show_in_rest' => true,
);
register_post_type( 'sb_staff', $args );
}
add_action( 'init', 'sb_staff_cpt', 0 );
?>
I highly recommend putting any CPTs along with any custom taxonomies into a plugin so that they are not dependent on your theme. If your client gets a new theme, all of their Staff content will still be there.
Right, so let’s save this to /wp-content/plugins/sb-staff/sb-staff.php
and then activate it like any other plugin in the WP admin.
From now on, the ‘Staff’ CPT is available to use in the ACF Location Rules by Post Type.
From there we can add a new ‘Staff’ Field Group and its respective fields:
Note that we don’t have a ‘Name’ field — we’ll use the default ‘Title’ field for the staff member’s full name. We did however include a ‘Last Name’ field which I’ve found very useful for sorting when displaying the Staff posts using a custom WP_Query
.
The Staff Grouping field has the various types of Staff so we can in essence use these like categories to group our staff members:
Note that instead of using a custom field you could also create a custom ‘Grouping’ taxonomy for your Staff CPT but an ACF field will work just fine here. And, you can even order your custom post queries by your ACF fields!
And that brings us to Task #2.
Task #2: adding ACF calls to your theme templates
Following from our Staff CPT example above, here’s a sample custom WP_Query
grabbing all of our staff
posts with the leadership
grouping by using a meta_query
:
<?php
// WP_Query arguments
$args = array (
'post_type' => array( 'sb_staff' ),
'post_status' => array( 'publish' ),
'meta_query' => array(
array(
'key' => 'grouping',
'value' => 'leadership'
),
)
);
// The Query
$query = new WP_Query( $args );
// The Loop
if ( $query->have_posts() ) { ?>
<ul class="staff-members">
<?php while ( $query->have_posts() ) {
$query->the_post();
?>
<li class="staff-member">
<?php $image = get_field( 'image' ); ?>
<?php if ( $image ) { ?>
<div class="staff-photo">
<?php echo wp_get_attachment_image( $image, 'full' ); ?>
</div>
<?php } ?>
<div class="staff-content">
<h3 class="staff-name"><?php the_title(); // this is our 'Name' field ?></h3>
<p class="staff-title"><?php the_field('title'); ?></p>
<p class="staff-email"><?php the_field('email'); ?></p>
<div class="staff-bio">
<?php the_field('bio') ?>
</div>
</div>
</li>
</ul>
<?php }
} else {
// no posts found
}
// Restore original Post Data
wp_reset_postdata();
?>
In the above example, we’ve create an unordered list to wrap around our staff members, with each member in a list item. From there, we’re grabbing the member’s name which was in the default WP title
element from our staff
custom post type.
Then, it’s just regular ACF theme calls using the_field()
for our other staff member fields. If you’ve used WP_Query
before, this should all be pretty standard.
As with creating a custom post type, I use GenerateWP to create my custom WP_Query
.
And with that, we should have all of our staff members who are in the Leadership
grouping showing on the page.
Here’s some pro tips for displaying your ACF content with your theme.
Tip #1: If your ACF field content is the most important part of your page or post, add the ACF theme calls in the main content area of your template.
To show how this works, here’s some code from the default Page template in our Plate WordPress starter theme:
<?php get_header(); ?>
<div id="content">
<div id="inner-content" class="wrap">
<main id="main" class="main" role="main" itemscope itemprop="mainContentOfPage" itemtype="https://schema.org/Blog">
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
<article id="post-< ?php the_ID(); ?>" < ?php post_class(); ?> role="article" itemscope itemtype="https://schema.org/BlogPosting">
<header class="article-header">
<h1 class="page-title" itemprop="headline">< ?php the_title(); ?></h1>
</header> < ?php // end article header ?>
<section class="entry-content" itemprop="articleBody">
<?php the_content(); ?>
<div class="acf-fields-outer">
<div class="acf-fields-inner">
<?php // ACF field calls go here ?>
</div>
</div>
</section> <?php // end article section ?>
<footer class="article-footer">
</footer>
<?php comments_template(); ?>
</article>
<?php endwhile; ?>
<?php plate_page_navi( $wp_query ); ?>
<?php else : ?>
<?php get_template_part( 'templates/404'); ?>
<?php endif; ?>
</main>
</div>
</div>
<?php get_sidebar(); ?>
<?php get_footer(); ?>
Notice how our ACF calls are within the .entry-content
section, within the <article>
that is within the <main>
.
Your theme may be set up slightly differently but look for wherever your theme has the_content()
and that will be where your main ACF content should go.
Putting your important ACF field content within the main content area of your theme is good for SEO and accessibility.
The next tip will explain how to replace the default WP content box with ACF fields, giving you more granular control over your content.
Tip #2: Use ACF’s ‘Hide on screen’ feature to take total command of your content.
On the Field Group edit screen (found at Custom Fields > Field Groups > [your Field Group]
), if you scroll down to the Field Group settings, you’ll find the ‘Hide on screen’ section.
Here you can hide any of the default WordPress meta boxes on the edit screen you’ve assigned via the Location Rules for your Field Group:
The above is pretty typical ‘Hide on screen’ setup for Pages. In this instance, we are replacing the default WP Content Editor with our own custom fields.
If you use only ACF fields on your edit screen, you don’t need to include the WP loop in your template! This might seem crazy at first as I’m sure everything you ever learned about WordPress theme code included the loop
somewhere. Yet, if you use only ACF fields and assign a custom theme template to your Page/Post you don’t need the loop at all!
To go Loopless™ with WordPress, here’s what you need to do:
- Create an ACF Field Group:
Custom Fields > Field Groups > Add New
- Assign your new Field Group to a specific Page
- Create a custom WordPress Page template. One quick way to create a new custom template is to copy and paste your default
page.php
code into a new file, and then add a Page template header at the very top:
<?php
/*
Template Name: My Page Template
*/
?>
<?php // rest of Page template code below ?>
The Template Name: [Page Template Name]
part is required so that WordPress knows to add your Page template to the list of available templates on the Page edit screen.
- On the edit screen of the Page your created, you should see your new Page template on the right hand side in the
Page Attributes > Template
drop-down. - Add your ACF field calls to your new Page template, deleting any loop code there. Here’s an example from our Plate theme:
<?php
/*
Template Name: My Page Template
*/
?>
<?php get_header(); ?>
<div id="content">
<div id="inner-content" class="wrap">
<main id="main" class="main" role="main" itemscope itemprop="mainContentOfPage" itemtype="https://schema.org/Blog">
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?> role="article" itemscope itemtype="https://schema.org/BlogPosting">
<header class="article-header">
<h1 class="page-title" itemprop="headline"><?php the_title(); ?></h1>
</header> <?php // end article header ?>
<section class="entry-content" itemprop="articleBody">
<div class="acf-fields-outer">
<div class="acf-fields-inner">
<?php // ACF field calls go here. Look Ma, no loop! ?>
<div class="acf-field-1">
<?php the_field('my_field_1'); ?>
</div>
<div class="acf-field-2">
<?php the_field('my_field_2'); ?>
</div>
</div>
</div>
</section> <?php // end article section ?>
<footer class="article-footer">
</footer>
<?php comments_template(); ?>
</article>
</main>
</div>
</div>
<?php get_sidebar(); ?>
<?php get_footer(); ?>
No default WordPress loop in sight!
As long as our ACF Field Group is assigned to this Page and the Page is using our custom Page template, you’ve created a direct connection between your ACF Field Group and your theme template. Pretty sweet.
You could also use this strategy more modularly for similar Pages, assigning an ACF Field Group to a specific Page Template in the Field Group Location Rules, and then using that Page template for multiple pages. That can really speed up your development.
Tip #3: Leverage the WordPress body_class()
function to add page-, template-, post-, and taxonomy-specific classes to your theme.
In our Plate starter theme, we have a robust body_class()
function that adds all the CSS classes we need to our Pages and Posts:
<?php
// Body Class functions
// Adds more slugs to body class so we can style individual pages + posts.
add_filter( 'body_class', 'plate_body_class' );
function plate_body_class( $classes ) {
// Adds new classes for blogroll page (list of blog posts)
// good for containing full-width images from Gutenberg
// Added: 2018-12-07
global $wp_query;
if ( isset( $wp_query ) && (bool) $wp_query->is_posts_page ) {
$classes[] = 'blogroll page-blog';
}
global $post;
if ( isset( $post ) ) {
/* Un comment below if you want the post_type-post_name body class */
/* $classes[] = $post->post_type . '-' . $post->post_name; */
$pagetemplate = get_post_meta( $post->ID, '_wp_page_template', true);
$classes[] = sanitize_html_class( str_replace( '.', '-', $pagetemplate ), '' );
$classes[] = $post->post_name;
}
if (is_page()) {
global $post;
if ( $post->post_parent ) {
// Parent post name/slug
$parent = get_post( $post->post_parent );
$classes[] = $parent->post_name;
// Parent template name
$parent_template = get_post_meta( $parent->ID, '_wp_page_template', true );
if ( !empty($parent_template) )
$classes[] = 'template-'.sanitize_html_class( str_replace( '.', '-', $parent_template ), '' );
}
// If we *do* have an ancestors list, process it
// http://codex.wordpress.org/Function_Reference/get_post_ancestors
if ($parents = get_post_ancestors( $post->ID )) {
foreach ( (array)$parents as $parent ) {
// As the array contains IDs only, we need to get each page
if ( $page = get_page($parent) ) {
// Add the current ancestor to the body class array
$classes[] = "{$page->post_type}-{$page->post_name}";
}
}
}
// Add the current page to our body class array
$classes[] = "{$post->post_type}-{$post->post_name}";
}
if ( is_page_template('single-full.php') ) {
$classes[] = 'single-full';
}
return $classes;
}
?>
Using this function, you’ll get an array of CSS classes that you can use to add custom CSS styling.
Looking at our Staff page we created earlier in Chrome Dev Tools:
<body class="page-template page-template-page-staff page-template-page-staff-php page page-id-56 logged-in page-staff-php staff page-staff">
</body>
Now we can target this page by slug, page template, id, and more in our CSS/SCSS like so:
.page-staff {
.acf-field-1 {
padding: 1.5em;
background: powderblue;
}
}
With our body_class()
function and custom Page templates, you can work both modularly using just a couple theme templates across your site or get specific and use a different template for each Page.
Combining this with logically organized ACF fields and you’ll have an amazingly powerful WordPress development arsenal that can adapt to any kind of content.
Summary
-
Using Advanced Custom Fields as a WordPress developer involves two main interrelated tasks:
- Creating ACF fields to structure your content
- Add ACF field calls to your theme templates
-
Sketch out your ACF Field Groups and fields in advance; work Field Group by Field Group to start.
-
For fields that can apply to multiple Pages/Posts, use ‘Global’ Field Groups.
-
Keep field names general and agnostic: you don’t need to namespace your fields, even on the same edit screen!
-
Use the ACF Repeater field when you have two or more or an unknown number of items.
-
Use the ACF Group field as a subfolder to organize fields within a Field Group.
-
Arrange ACF fields on your edit screens in the order they will display on the front end.
-
Use discreet fields to structure your content; If you’re unsure of what the content may be or it could change, use the ACF WYSIWYG field.
-
Use a custom post type if you will need to display content in multiple locations on your site; if you know it will only be on a single page, you can use the ACF Repeater field.
-
If your ACF field content is important, add it to the main content section of your theme template.
-
Use the ACF Field Group’s ‘Hide on screen’ feature to take control of your content.
-
Use our
body_class()
function to add page-, post-, and template-specific classes that you can use to quickly style your theme.
Whew – we made it!
With the ideas presented in this post, you should be able to make ACF work for you and your clients.
If you have any questions about this post or need help, ask us on Twitter: @studio_bio