diff --git a/docs/private-apis.md b/docs/private-apis.md
index 4e9929bee84afe..2b684769ed8f0b 100644
--- a/docs/private-apis.md
+++ b/docs/private-apis.md
@@ -213,7 +213,6 @@ Private exports:
- `CreatePatternModalContents`
- `DuplicatePatternModal`
- `isOverridableBlock`
-- `hasOverridableBlocks`
- `useDuplicatePatternProps`
- `RenamePatternModal`
- `PatternsMenuItems`
@@ -227,7 +226,6 @@ Private exports:
- `PATTERN_USER_CATEGORY`
- `EXCLUDED_PATTERN_SOURCES`
- `PATTERN_SYNC_TYPES`
-- `PARTIAL_SYNCING_SUPPORTED_BLOCKS`
### `core/patterns` store
diff --git a/docs/reference-guides/block-api/block-bindings.md b/docs/reference-guides/block-api/block-bindings.md
index dc4e397f1a9d42..6497026c03808a 100644
--- a/docs/reference-guides/block-api/block-bindings.md
+++ b/docs/reference-guides/block-api/block-bindings.md
@@ -23,7 +23,7 @@ An example could be connecting an Image block `url` attribute to a function that
## Compatible blocks and their attributes
-Right now, not all block attributes are compatible with block bindings. There is some ongoing effort to increase this compatibility, but for now, this is the list:
+Right now, not all block attributes are compatible with block bindings. There is some ongoing effort to increase this compatibility, but for now, this is the default list:
| Supported Blocks | Supported Attributes |
| ---------------- | -------------------- |
@@ -32,6 +32,117 @@ Right now, not all block attributes are compatible with block bindings. There is
| Image | id, url, title, alt |
| Button | text, url, linkTarget, rel |
+### Extending supported attributes
+
+_**Note:** Since WordPress 6.9._
+
+Developers can extend the list of supported attributes using the `block_bindings_supported_attributes` filter. This filter allows adding support for additional block attributes.
+
+There are two filters available:
+
+- `block_bindings_supported_attributes`: A general filter that receives the supported attributes array and the block type name.
+- `block_bindings_supported_attributes_{$block_type}`: A dynamic filter specific to a block type (e.g., `block_bindings_supported_attributes_core/image`).
+
+Example of adding support for the `caption` attribute on the Image block:
+
+```php
+add_filter(
+ 'block_bindings_supported_attributes_core/image',
+ function ( $supported_attributes ) {
+ $supported_attributes[] = 'caption';
+ return $supported_attributes;
+ }
+);
+```
+
+Example of adding support for a custom block:
+
+```php
+add_filter(
+ 'block_bindings_supported_attributes_my-plugin/my-block',
+ function ( $supported_attributes ) {
+ $supported_attributes[] = 'title';
+ $supported_attributes[] = 'description';
+ return $supported_attributes;
+ }
+);
+```
+
+This filter also affects which blocks and attributes are available for Pattern Overrides, as both features share the same underlying supported attributes configuration.
+
+### Accessing Pattern Override values in dynamic blocks
+
+When creating a dynamic block that supports Pattern Overrides, you can access the override values within your `render_callback` function. The Pattern block (`core/block`) provides override values to nested blocks via the `pattern/overrides` context.
+
+**Step 1: Register your block with the required context and supported attributes**
+
+```php
+add_action( 'init', function() {
+ // Register supported attributes for pattern overrides
+ add_filter(
+ 'block_bindings_supported_attributes_my-plugin/my-block',
+ function ( $supported_attributes ) {
+ $supported_attributes[] = 'title';
+ $supported_attributes[] = 'description';
+ return $supported_attributes;
+ }
+ );
+
+ register_block_type( 'my-plugin/my-block', array(
+ 'attributes' => array(
+ 'title' => array( 'type' => 'string', 'default' => '' ),
+ 'description' => array( 'type' => 'string', 'default' => '' ),
+ 'metadata' => array( 'type' => 'object' ),
+ ),
+ // Declare that you need the pattern/overrides context
+ 'uses_context' => array( 'pattern/overrides' ),
+ 'render_callback' => 'my_block_render_callback',
+ ) );
+} );
+```
+
+**Step 2: Access override values in your render callback**
+
+The override values are stored in `$block->context['pattern/overrides']` as an associative array. The keys are block metadata names (assigned when enabling overrides), and the values are arrays of attribute overrides.
+
+```php
+function my_block_render_callback( $attributes, $content, $block ) {
+ // Get the block's metadata name (set when enabling overrides)
+ $block_name = $attributes['metadata']['name'] ?? null;
+
+ // Get the pattern overrides from context
+ $overrides = array();
+ if ( $block_name && isset( $block->context['pattern/overrides'][ $block_name ] ) ) {
+ $overrides = $block->context['pattern/overrides'][ $block_name ];
+ }
+
+ // Get attribute values, preferring overrides when available
+ // Note: An empty string in overrides means "reset to default"
+ $title = $attributes['title'];
+ if ( isset( $overrides['title'] ) && $overrides['title'] !== '' ) {
+ $title = $overrides['title'];
+ }
+
+ $description = $attributes['description'];
+ if ( isset( $overrides['description'] ) && $overrides['description'] !== '' ) {
+ $description = $overrides['description'];
+ }
+
+ return sprintf(
+ '
',
+ esc_html( $title ),
+ esc_html( $description )
+ );
+}
+```
+
+**Key points to keep in mind:**
+
+- **`uses_context`**: Your block must declare `pattern/overrides` in its `uses_context` to receive override data from parent Pattern blocks.
+- **Block metadata name**: Each overridable block instance has a unique name stored in `$attributes['metadata']['name']`. This name is assigned when the user enables overrides on the block in the editor.
+- **Empty string convention**: An empty string (`""`) in the overrides represents a reset to the default value. Your code should handle this appropriately.
+- **Fallback behavior**: Always provide fallback values from `$attributes` in case the block is not inside a pattern or overrides are not set.
+
## Registering a custom source
Registering a source requires defining at least `name`, a `label` and a `callback` function that gets a value from the source and passes it back to a block attribute.
diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js
index ab7f8adf24c134..13b7029f5f04a7 100644
--- a/packages/block-library/src/block/edit.js
+++ b/packages/block-library/src/block/edit.js
@@ -40,7 +40,7 @@ import { getBlockBindingsSource } from '@wordpress/blocks';
import { unlock } from '../lock-unlock';
const { useLayoutClasses } = unlock( blockEditorPrivateApis );
-const { hasOverridableBlocks } = unlock( patternsPrivateApis );
+const { isOverridableBlock } = unlock( patternsPrivateApis );
const fullAlignments = [ 'full', 'wide', 'left', 'right' ];
@@ -168,24 +168,39 @@ function ReusableBlockEdit( {
const { __unstableMarkLastChangeAsPersistent } =
useDispatch( blockEditorStore );
- const { onNavigateToEntityRecord, hasPatternOverridesSource } = useSelect(
- ( select ) => {
- const { getSettings } = select( blockEditorStore );
- // For editing link to the site editor if the theme and user permissions support it.
- return {
- onNavigateToEntityRecord:
- getSettings().onNavigateToEntityRecord,
- hasPatternOverridesSource: !! getBlockBindingsSource(
- 'core/pattern-overrides'
- ),
- };
- },
- []
- );
+ const {
+ onNavigateToEntityRecord,
+ hasPatternOverridesSource,
+ supportedBlockTypes,
+ } = useSelect( ( select ) => {
+ const { getSettings } = select( blockEditorStore );
+ // For editing link to the site editor if the theme and user permissions support it.
+ return {
+ onNavigateToEntityRecord: getSettings().onNavigateToEntityRecord,
+ hasPatternOverridesSource: !! getBlockBindingsSource(
+ 'core/pattern-overrides'
+ ),
+ supportedBlockTypes: Object.keys(
+ getSettings().__experimentalBlockBindingsSupportedAttributes ||
+ {}
+ ),
+ };
+ }, [] );
+
+ const hasOverridableBlocks = ( _blocks ) =>
+ _blocks.some( ( block ) => {
+ if (
+ supportedBlockTypes.includes( block.name ) &&
+ isOverridableBlock( block )
+ ) {
+ return true;
+ }
+ return hasOverridableBlocks( block.innerBlocks );
+ } );
const canOverrideBlocks = useMemo(
() => hasPatternOverridesSource && hasOverridableBlocks( blocks ),
- [ hasPatternOverridesSource, blocks ]
+ [ hasPatternOverridesSource, hasOverridableBlocks, blocks ]
);
const { alignment, layout } = useInferredLayout( blocks, parentLayout );
diff --git a/packages/editor/src/hooks/pattern-overrides.js b/packages/editor/src/hooks/pattern-overrides.js
index 828e98bb940f69..074ab909d6eaa0 100644
--- a/packages/editor/src/hooks/pattern-overrides.js
+++ b/packages/editor/src/hooks/pattern-overrides.js
@@ -4,7 +4,10 @@
import { addFilter } from '@wordpress/hooks';
import { privateApis as patternsPrivateApis } from '@wordpress/patterns';
import { createHigherOrderComponent } from '@wordpress/compose';
-import { useBlockEditingMode } from '@wordpress/block-editor';
+import {
+ store as blockEditorStore,
+ useBlockEditingMode,
+} from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
import { getBlockBindingsSource } from '@wordpress/blocks';
@@ -20,14 +23,12 @@ const {
PatternOverridesControls,
ResetOverridesControl,
PATTERN_TYPES,
- PARTIAL_SYNCING_SUPPORTED_BLOCKS,
PATTERN_SYNC_TYPES,
} = unlock( patternsPrivateApis );
/**
* Override the default edit UI to include a new block inspector control for
* assigning a partial syncing controls to supported blocks in the pattern editor.
- * Currently, only the `core/paragraph` block is supported.
*
* @param {Component} BlockEdit Original component.
*
@@ -35,8 +36,16 @@ const {
*/
const withPatternOverrideControls = createHigherOrderComponent(
( BlockEdit ) => ( props ) => {
- const isSupportedBlock =
- !! PARTIAL_SYNCING_SUPPORTED_BLOCKS[ props.name ];
+ const isSupportedBlock = useSelect(
+ ( select ) => {
+ const { __experimentalBlockBindingsSupportedAttributes } =
+ select( blockEditorStore ).getSettings();
+ return !! __experimentalBlockBindingsSupportedAttributes?.[
+ props.name
+ ];
+ },
+ [ props.name ]
+ );
return (
<>
diff --git a/packages/patterns/src/api/index.js b/packages/patterns/src/api/index.js
index 4321dc4262145a..ed142e22179a95 100644
--- a/packages/patterns/src/api/index.js
+++ b/packages/patterns/src/api/index.js
@@ -1,8 +1,3 @@
-/**
- * Internal dependencies
- */
-import { PARTIAL_SYNCING_SUPPORTED_BLOCKS } from '../constants';
-
/**
* Determines whether a block is overridable.
*
@@ -12,9 +7,6 @@ import { PARTIAL_SYNCING_SUPPORTED_BLOCKS } from '../constants';
*/
export function isOverridableBlock( block ) {
return (
- Object.keys( PARTIAL_SYNCING_SUPPORTED_BLOCKS ).includes(
- block.name
- ) &&
!! block.attributes.metadata?.name &&
!! block.attributes.metadata?.bindings &&
Object.values( block.attributes.metadata.bindings ).some(
@@ -22,19 +14,3 @@ export function isOverridableBlock( block ) {
)
);
}
-
-/**
- * Determines whether the blocks list has overridable blocks.
- *
- * @param {WPBlock[]} blocks The blocks list.
- *
- * @return {boolean} `true` if the list has overridable blocks, `false` otherwise.
- */
-export function hasOverridableBlocks( blocks ) {
- return blocks.some( ( block ) => {
- if ( isOverridableBlock( block ) ) {
- return true;
- }
- return hasOverridableBlocks( block.innerBlocks );
- } );
-}
diff --git a/packages/patterns/src/components/overrides-panel.js b/packages/patterns/src/components/overrides-panel.js
index b567b3c372daa2..7210ad35755dbe 100644
--- a/packages/patterns/src/components/overrides-panel.js
+++ b/packages/patterns/src/components/overrides-panel.js
@@ -19,8 +19,15 @@ import { unlock } from '../lock-unlock';
const { BlockQuickNavigation } = unlock( blockEditorPrivateApis );
export default function OverridesPanel() {
- const allClientIds = useSelect(
- ( select ) => select( blockEditorStore ).getClientIdsWithDescendants(),
+ const { allClientIds, supportedBlockTypes } = useSelect(
+ ( select ) => ( {
+ allClientIds:
+ select( blockEditorStore ).getClientIdsWithDescendants(),
+ supportedBlockTypes: Object.keys(
+ select( blockEditorStore ).getSettings()
+ ?.__experimentalBlockBindingsSupportedAttributes || {}
+ ),
+ } ),
[]
);
const { getBlock } = useSelect( blockEditorStore );
@@ -28,9 +35,12 @@ export default function OverridesPanel() {
() =>
allClientIds.filter( ( clientId ) => {
const block = getBlock( clientId );
- return isOverridableBlock( block );
+ return (
+ supportedBlockTypes.includes( block.name ) &&
+ isOverridableBlock( block )
+ );
} ),
- [ allClientIds, getBlock ]
+ [ allClientIds, getBlock, supportedBlockTypes ]
);
if ( ! clientIdsWithOverrides?.length ) {
diff --git a/packages/patterns/src/constants.js b/packages/patterns/src/constants.js
index 9f35b719949c42..0bcfeb99d4d020 100644
--- a/packages/patterns/src/constants.js
+++ b/packages/patterns/src/constants.js
@@ -15,12 +15,4 @@ export const PATTERN_SYNC_TYPES = {
unsynced: 'unsynced',
};
-// TODO: This should not be hardcoded. Maybe there should be a config and/or an UI.
-export const PARTIAL_SYNCING_SUPPORTED_BLOCKS = {
- 'core/paragraph': [ 'content' ],
- 'core/heading': [ 'content' ],
- 'core/button': [ 'text', 'url', 'linkTarget', 'rel' ],
- 'core/image': [ 'id', 'url', 'title', 'alt', 'caption' ],
-};
-
export const PATTERN_OVERRIDES_BINDING_SOURCE = 'core/pattern-overrides';
diff --git a/packages/patterns/src/private-apis.js b/packages/patterns/src/private-apis.js
index 0553378cb56043..972527ad8d3fb3 100644
--- a/packages/patterns/src/private-apis.js
+++ b/packages/patterns/src/private-apis.js
@@ -11,7 +11,7 @@ import {
default as DuplicatePatternModal,
useDuplicatePatternProps,
} from './components/duplicate-pattern-modal';
-import { isOverridableBlock, hasOverridableBlocks } from './api';
+import { isOverridableBlock } from './api';
import RenamePatternModal from './components/rename-pattern-modal';
import PatternsMenuItems from './components';
import RenamePatternCategoryModal from './components/rename-pattern-category-modal';
@@ -24,7 +24,6 @@ import {
PATTERN_USER_CATEGORY,
EXCLUDED_PATTERN_SOURCES,
PATTERN_SYNC_TYPES,
- PARTIAL_SYNCING_SUPPORTED_BLOCKS,
} from './constants';
export const privateApis = {};
@@ -34,7 +33,6 @@ lock( privateApis, {
CreatePatternModalContents,
DuplicatePatternModal,
isOverridableBlock,
- hasOverridableBlocks,
useDuplicatePatternProps,
RenamePatternModal,
PatternsMenuItems,
@@ -47,5 +45,4 @@ lock( privateApis, {
PATTERN_USER_CATEGORY,
EXCLUDED_PATTERN_SOURCES,
PATTERN_SYNC_TYPES,
- PARTIAL_SYNCING_SUPPORTED_BLOCKS,
} );