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 Background external-image download queue
license-manager.php License activation and update checks
block-migrator.php Block Migrator & Repair (admin card + WP-CLI)
block-recovery.php Self-healing InnerBlocks recovery + WP-CLI repair
performance-manager.php Block Manager, usage inventory, diagnostics
generated-block-manifest.php Generated block/field-group manifest
assets/
js/
block-transforms.js Editor-side block transforms
css/
editor-blocks.css Consolidated editor style bundle
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, and the star rating endpoint file. Operational modules load contextually:image-localizer.php,block-migrator.php, andperformance-manager.phponly in admin, cron, or WP-CLI requests, andblock-recovery.phpin WP-CLI and REST contexts. Public frontend requests carry only registration and rendering code. If ACF is missing, the plugin 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()reads block definitions from the generated manifest (includes/generated-block-manifest.php), an OPcache-friendly PHP array that replaces request-time directory scans and JSON decoding. For each block it callsregister_block_type(), loads the ACF field groups, and includesextra.phpif present. A filesystem scan ofblocks/remains as a fallback when the manifest is missing, and blocks disabled through the Block Manager are skipped.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 load as one consolidated bundle (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
Since 2.9.0, all block CSS is consolidated into one cacheable editor bundle (assets/css/editor-blocks.css), enqueued on enqueue_block_assets (priority 999999, admin only). The per-block style handles that block.json registers are dequeued in the editor so nothing loads twice. When you disable blocks through the Block Manager, the plugin builds a site-specific bundle that excludes their CSS.
One stylesheet request instead of dozens makes the editor iframe both faster and more predictable across WordPress versions and themes.
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.