Skip to content

Instantly share code, notes, and snippets.

@vapvarun
Created April 14, 2026 04:05
Show Gist options
  • Select an option

  • Save vapvarun/06d73c78cd6b91fcd1490e9aa512a108 to your computer and use it in GitHub Desktop.

Select an option

Save vapvarun/06d73c78cd6b91fcd1490e9aa512a108 to your computer and use it in GitHub Desktop.
How to Build Custom Blocks for Block Themes Using the WordPress Block API (brndle.com)
my-theme/blocks/team-member/
block.json # metadata, WordPress reads this to register the block
edit.js # React component rendered in the editor
save.js # HTML saved to post_content (for static blocks)
render.php # PHP template (for dynamic blocks), alternative to save.js
style.scss # shared styles (editor + frontend)
editor.scss # editor-only styles (optional)
view.js # frontend JavaScript (optional)
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "mytheme/team-member",
"title": "Team Member",
"category": "widgets",
"icon": "businessperson",
"keywords": ["team", "staff", "person"],
"textdomain": "mytheme",
"attributes": {
"name": { "type": "string", "default": "" },
"role": { "type": "string", "default": "" },
"imageId": { "type": "number" },
"imageUrl": { "type": "string" }
},
"supports": {
"align": ["wide", "full"],
"spacing": { "padding": true, "margin": true },
"color": { "text": true, "background": true },
"typography": { "fontSize": true, "fontFamily": true }
},
"editorScript": "file:./edit.js",
"style": "file:./style.scss",
"editorStyle": "file:./editor.scss"
}
add_action( 'init', function () {
$dirs = glob( get_template_directory() . '/blocks/*', GLOB_ONLYDIR );
foreach ( $dirs as $dir ) {
register_block_type( $dir );
}
} );
// save.js for a simple team-member block
import { useBlockProps, RichText } from '@wordpress/block-editor';
export default function save( { attributes } ) {
const blockProps = useBlockProps.save();
return (
<div { ...blockProps }>
{ attributes.imageUrl && <img src={ attributes.imageUrl } alt="" /> }
<RichText.Content tagName="h3" value={ attributes.name } />
<RichText.Content tagName="p" value={ attributes.role } />
</div>
);
}
<?php
// render.php
$name = esc_html( $attributes['name'] ?? '' );
$role = esc_html( $attributes['role'] ?? '' );
?>
<div <?php echo get_block_wrapper_attributes(); ?>>
<h3><?php echo $name; ?></h3>
<p><?php echo $role; ?></p>
</div>
.wp-block-mytheme-team-member {
padding: var(--wp--preset--spacing--50);
background: var(--wp--preset--color--base);
color: var(--wp--preset--color--contrast);
h3 {
font-size: var(--wp--preset--font-size--large);
}
}
my-theme/patterns/team-grid.php
<?php
/**
* Title: Team Grid
* Slug: mytheme/team-grid
* Categories: team
*/
?>
<!-- wp:columns -->
<div class="wp-block-columns">
<!-- wp:column -->
<div class="wp-block-column">
<!-- wp:mytheme/team-member {"name":"Jane Smith","role":"CTO"} /-->
</div>
<!-- /wp:column -->
<!-- wp:column -->
<div class="wp-block-column">
<!-- wp:mytheme/team-member {"name":"Rahul Iyer","role":"CEO"} /-->
</div>
<!-- /wp:column -->
</div>
<!-- /wp:columns -->
export default {
// current save function...
};
export const deprecated = [
{
attributes: { /* old attribute shape */ },
save( { attributes } ) {
// old save function
},
},
];
{
"scripts": {
"start": "wp-scripts start",
"build": "wp-scripts build"
},
"devDependencies": { "@wordpress/scripts": "^27.0.0" }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment