Directory Structure
acf-blocks-plugin/
acf-blocks.php Main plugin file
includes/
functions.php Block registration, styles, editor assets
compat.php ACF 6.7+ compatibility layer
image-localizer.php External image download on save
license-manager.php License activation and update checks
assets/
js/
block-transforms.js Editor-side block transforms
blocks/
accordion-block/ One directory per block
block.json Block metadata (ACF v3)
block-data.json ACF field group definition
accordion-block.php PHP render template
accordion.css Block stylesheet
extra.php Optional: additional hooks
callout/
...
(27 more block directories)
llm/
(29 markdown files for AI/LLM reference)
.github/
workflows/
release.yml CI: tag push creates zip releaseBoot Sequence
The plugin loads in this order:
The ACF Blocks architecture is deliberately flat: one main file, an includes directory for registration and compatibility, and a blocks directory where every block is a self-contained folder. Understanding this layout is most of what you need to extend or debug the plugin.
acf-blocks.phpruns immediately. It defines constants and instantiates the license manager (which hooks intoadmin_menu,admin_init, and the update system).On
plugins_loaded,acf_blocks_init()checks whether ACF is available. If yes, it loadsfunctions.php,compat.php, andimage-localizer.php. If ACF is missing, it registers an admin notice and stops.On
init(priority 5),acf_blocks_register_styles()pre-registers all block stylesheets withwp_register_style(). This allows WordPress to conditionally enqueue them only when a block appears on the page.On
acf/init(priority 5),acf_blocks_load_blocks()scans theblocks/directory. For each subdirectory containing ablock.json, it callsregister_block_type(), loads ACF field groups from any*.jsonfiles (excludingblock.json), and includesextra.phpif present.On
block_categories_all, a custom “ACF Blocks” category is inserted at the top of the block inserter.On
enqueue_block_editor_assets, the block transforms script is enqueued.Editor styles are loaded through three overlapping mechanisms (see below).
Block Category
All blocks are registered under the acf-blocks category, which appears first in the block inserter. The category is added via the block_categories_all filter.
Per-Block Structure
Every block lives in its own directory under blocks/ and contains at minimum:
block.json– Standard WordPress block metadata with anacfkey specifying the render template and block version.block-data.json– An ACF field group definition (JSON). This is loaded automatically. No manual import through the ACF admin is needed.{block-name}.php– The PHP render template that receives$block,$content,$is_preview, and$post_id.{block-name}.css– Block styles, referenced via"style": "file:./filename.css"inblock.json.
Some blocks also include:
extra.php– Additional hooks, AJAX handlers, or scripts. Loaded automatically if present.{block-name}.js– Frontend JavaScript (e.g. star rating submission, section block editor component).
No Build Step
The plugin has no build system, no package.json, no webpack or bundler. All JavaScript is vanilla ES5-compatible (IIFE pattern). Assets are committed as source files and deployed as-is.
Editor Style Loading
Getting block styles into the Gutenberg editor iframe is notoriously unreliable across WordPress versions and themes. The plugin uses three overlapping mechanisms to ensure styles always appear:
add_editor_style()viaafter_setup_theme(priority 999) – Registers all block CSS files as editor styles.wp_enqueue_style()viaenqueue_block_assets(priority 999999, admin only) – Directly enqueues all block stylesheets in the editor.Inline CSS injection via
block_editor_settings_all(priority 999999) – Reads all CSS files and injects them as inline styles into the editor settings object.
On the frontend, styles are loaded conditionally. WordPress only enqueues a block’s stylesheet when that block appears on the page, because the styles are pre-registered (not enqueued) during init.
CSS Class Naming
All blocks use the acf- prefix for CSS classes: .acf-accordion, .acf-hero-block, .acf-product-review, etc. This prevents conflicts with theme and plugin styles.
Quick answers to common questions:
Where does each block live in the plugin?
Under blocks/{block-name}/ with its block.json, field group JSON, and render template together. Nothing is scattered: deleting a block folder removes that block cleanly, and copying one is the starting point for a custom block.
Can I override a block’s template from my theme?
Yes, the render pipeline checks your theme for an override before falling back to the plugin’s template, so customizations survive plugin updates. The render templates lesson covers the exact lookup order.