Custom Post Types and ACF: How to Build Unique Solutions for Every Client
Building bespoke content-driven websites means choosing the right combination of architecture and tools. When you pair custom post types with ACF you get a flexible, maintainable system for delivering tailored content experiences for clients. This article walks through practical patterns, code examples, and best practices so you can implement robust solutions using CPTs, ACF, and WordPress native features.
Why combine custom post types and ACF?
Using a custom post type (CPT) organizes content around its real-world purpose—team members, portfolios, events, products, etc.—while Advanced Custom Fields (ACF) provides a clean UI for adding structured data to those post types via custom fields. Together they let you:
- Decouple content from presentation so editors focus on content and developers control output.
- Enforce consistent data through field types like date pickers, relationships, repeaters, and flexible content.
- Improve performance and SEO by keeping structured data predictable and accessible to templates and APIs.
Planning your content model
Start with content modeling. Map each content type to a CPT, and list the fields editors need. Consider:
- Which data belongs in the post title vs custom fields
- Which fields should be required or conditional
- How content will be queried and displayed
- Whether you need taxonomies or relationships between CPTs
Example models: a Portfolio CPT with gallery (repeater), client (relationship), and project date; an Event CPT with start/end dates, venue (post object), and ticket link.
Registering a CPT — practical example
Register a CPT in a plugin or your theme’s setup file. Keep registrations programmatic for portability and version control. Below is a minimal example:
<?php
add_action( 'init', 'register_portfolio_cpt' );
function register_portfolio_cpt() {
$labels = array(
'name' => 'Portfolios',
'singular_name' => 'Portfolio',
'menu_name' => 'Portfolios',
);
$args = array(
'labels' => $labels,
'public' => true,
'has_archive' => true,
'show_in_rest' => true,
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
'rewrite' => array( 'slug' => 'portfolio' ),
);
register_post_type( 'portfolio', $args );
}
?>
Key registration tips
- Set show_in_rest to true to support the Block Editor and REST API use cases.
- Use descriptive slugs and plural/singular labels for clarity in the admin.
- Only enable supports you need; fewer fields reduce editor confusion.
Creating ACF field groups
ACF lets you attach custom fields to any post type via the GUI or via PHP. For reproducible workflows use the JSON sync feature (acf-json) or register field groups in PHP using acf_add_local_field_group.
ACF JSON sync
Enable ACF Local JSON (plugins/acf-json folder) so field definitions are saved to disk. This makes field groups versionable and portable across environments.
Register fields in PHP (example)
<?php
add_action( 'acf/init', 'portfolio_acf_fields' );
function portfolio_acf_fields() {
if( function_exists('acf_add_local_field_group') ) {
acf_add_local_field_group(array(
'key' => 'group_portfolio',
'title' => 'Portfolio Fields',
'fields' => array(
array(
'key' => 'field_client',
'label' => 'Client',
'name' => 'client',
'type' => 'post_object',
'post_type' => array('client'),
),
array(
'key' => 'field_gallery',
'label' => 'Gallery',
'name' => 'gallery',
'type' => 'gallery',
),
),
'location' => array(
array(
array(
'param' => 'post_type',
'operator' => '==',
'value' => 'portfolio',
),
),
),
));
}
}
?>
Templating: pulling ACF data into the theme
Use get_field() and the_field() to access custom fields in templates. Prefer get_field() with explicit escaping for security and flexibility.
<?php
// single-portfolio.php
$client = get_field('client');
$gallery = get_field('gallery');
if ( $client ) {
echo '<h3>Client: ' . esc_html( get_the_title($client) ) . '</h3>';
}
if ( $gallery ) {
echo '<div class="portfolio-gallery">';
foreach ( $gallery as $image ) {
echo '<img src="' . esc_url( $image['sizes']['medium'] ) . '" alt="' . esc_attr( $image['alt'] ) . '" />';
}
echo '</div>';
}
?>
Performance and security
- Cache repeatable queries and large relational fields (transients, object cache).
- Escape all output (esc_html, esc_url, wp_kses_post) and sanitize inputs where appropriate.
- Limit field groups loaded in the admin by using targeted locations to keep screens fast.
Advanced patterns
ACF Flexible Content & Repeater for modular pages
Flexible Content enables page builders without bloated markup. Use reusable layouts for predictable output and fewer layout settings in the editor.
ACF + REST API
To expose ACF fields to the REST API you can use plugins or add the fields to the REST response yourself. Example to add a specific ACF field to the REST post response:
<?php
add_action( 'rest_api_init', function() {
register_rest_field( 'portfolio',
'acf_fields',
array(
'get_callback' => function( $object ) {
return get_fields( $object['id'] );
},
'update_callback' => null,
'schema' => null,
)
);
});
?>
This approach makes data available for headless setups, JS-driven front-ends, or integration with third-party services.
ACF Blocks for Gutenberg
Use acf_register_block_type to create custom Gutenberg blocks that render server-side with access to the same ACF fields you use for CPTs. This provides a consistent editing experience and keeps content portable.
Common pitfalls and how to avoid them
- Overusing post meta: Don’t store relational data that would be better expressed with taxonomies or post relationships.
- Too many fields: Keep the editor focused. Group fields and make non-essential ones conditional or hidden.
- Hard-coded templates: Abstract render logic into template parts and partials so multiple CPTs can reuse display code.
- Not versioning field groups: Use ACF JSON or register fields in PHP to keep changes trackable.
SEO considerations
Structured custom fields can improve SEO when used for metadata and schema markup. Best practices:
- Populate SEO title and meta description fields on CPTs and hook them into your SEO plugin or theme head output.
- Expose structured data (JSON-LD) using sanitized ACF values for schema.org objects like Event, Product, or Article.
- Keep important content in the main post content when possible—search engines weigh content and headings more heavily than hidden meta fields.
Workflow and maintainability
To ensure long-term maintainability:
- Store ACF definitions in acf-json and include them in your repository.
- Register CPTs and critical field groups in plugin code for portability between themes.
- Document the content model for editors and clients—describe each CPT, field purpose, and expected usage.
- Automate migrations and deployment of field changes with environment-aware tooling (WP-CLI, CI pipelines).
Real-world example: Events site
Requirements: recurring events, venue relationships, ticket URL, and an event calendar feed.
- Register an “event” CPT with show_in_rest enabled.
- Create ACF fields: start_date (date_time_picker), end_date, venue (post_object), is_recurring (true/false), recurrence_rules (repeater).
- Expose data via REST and build a custom endpoint to return upcoming events, applying caching and date-range queries.
- On the front-end, use template partials to render event lists and single views and include JSON-LD for Event schema using ACF values.
Conclusion
Combining custom post types with ACF gives you a powerful, editor-friendly platform to build unique solutions for clients. By planning the content model, registering CPTs in code, versioning ACF definitions, and following templating and performance best practices, you create maintainable, SEO-aware sites. Use the patterns in this article—CPT registration, ACF field management, templating, REST integration, and modular design—to deliver high-quality, repeatable results for every project.