{"id":676,"date":"2022-08-16T04:05:00","date_gmt":"2022-08-15T21:05:00","guid":{"rendered":"https:\/\/lab.wptips.dev\/?p=676"},"modified":"2022-11-19T12:36:45","modified_gmt":"2022-11-19T05:36:45","slug":"custom-block-without-plugin","status":"publish","type":"post","link":"https:\/\/pixelstudio.id\/blog\/custom-block-without-plugin\/","title":{"rendered":"How to Create A Custom Block Without Plugin (Updated 2022)"},"content":{"rendered":"\n<p>Creating a custom block isn&#8217;t an easy feat especially if you are not comfortable with pure JavaScript. Hopefully, this guide can help you with that.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Before Making Custom Block<\/h2>\n\n\n\n<p>First, ask this question before wasting time creating a redundant block:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\"><p>&#8220;Is there an existing block that fits your need?&#8221;<\/p><\/blockquote>\n\n\n\n<p>For example, I once need to create this design in Gutenberg:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1280\" height=\"573\" src=\"https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2021\/10\/image-1-1280x573.png\" alt=\"\" class=\"wp-image-2040\" srcset=\"https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2021\/10\/image-1-1280x573.png 1280w, https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2021\/10\/image-1-480x215.png 480w, https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2021\/10\/image-1.png 1450w\" sizes=\"auto, (max-width: 1280px) 100vw, 1280px\" \/><\/figure>\n\n\n\n<p>The general layout can be achieved with <strong>Media-Text<\/strong> block. But there&#8217;s no option to create a colored box behind the image. We also don&#8217;t have an option to add five stars below the <strong>Heading<\/strong>.<\/p>\n\n\n\n<p>So we create 2 custom styles, one for Media-Text, and one for Heading:<\/p>\n\n\n\n<pre title=\"functions.php\" class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">register_block_style('core\/media-text', [\n  'name' =&gt; 'half-bg',\n  'label' =&gt; 'Half Background'\n]);\n\nregister_block_style('core\/heading', [\n  'name' =&gt; 'has-stars',\n  'label' =&gt; 'Has Stars'\n]);<\/code><\/pre>\n\n\n\n<p>Now we can use pseudoselector <code>::before<\/code> and <code>::after<\/code> to create that design like this:<\/p>\n\n\n\n<pre title=\"style.css\" class=\"wp-block-code\"><code lang=\"css\" class=\"language-css\">.wp-block-media-text.is-style-half-bg {\n  position: relative;\n}\n.wp-block-media-text.is-style-half-bg::before {\n  content: \"\";\n  position: absolute;\n  top: 0;\n  left: -3rem\n  bottom: 0;\n  width: 50%;\n  background-color: #eee1cc;\n}\n\nh1.is-style-has-stars::after,\nh2.is-style-has-stars::after,\nh3.is-style-has-stars::after {\n  content: \"\u2605\u2605\u2605\u2605\u2605\";\n  order: 2;\n  display: block;\n  width: 100%;\n  font-size: 14px;\n  letter-spacing: 2px;\n  color: #ba9556;\n}<\/code><\/pre>\n\n\n\n<p>If none of the existing blocks fits your need, continue the tutorial below.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. Register Assets &amp; Block Type<\/h2>\n\n\n\n<p>This tutorial uses this directory structure, adapt it according to your theme:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">my-theme\/\n\u251c\u2500 css\/\n\u2502  \u251c\u2500 my-block.css\n\u2502  \u251c\u2500 ...\n\u251c\u2500 js\/\n\u2502  \u251c\u2500 my-block.js\n\u2502  \u251c\u2500 ...\n\u251c\u2500 functions.php\n\u251c\u2500 ...<\/code><\/pre>\n\n\n\n<ul><li><code>my-block.js<\/code> is for the admin editor.<\/li><li><code>my-block.css<\/code> is for both the front end and the admin editor.<\/li><\/ul>\n\n\n\n<p>Now let&#8217;s enqueue them:<\/p>\n\n\n\n<pre title=\"functions.php\" class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">add_action('wp_enqueue_scripts', 'my_enqueue_assets');\nadd_action('enqueue_block_editor_assets', 'my_enqueue_block_assets');\n\nfunction my_enqueue_assets() {\n  $css_dir = get_stylesheet_directory_uri() . '\/css';\n  wp_enqueue_style('my-block', $css_dir . '\/my-block.css', []);\n}\n\nfunction my_enqueue_block_assets() {\n  $css_dir = get_stylesheet_directory_uri() . '\/css';\n  $js_dir = get_stylesheet_directory_uri() . '\/js';\n\n  \/\/ If in plugin, use this instead:\n  \/\/ $css_dir = plugin_dir_url(__FILE__) . 'css';\n  \/\/ $js_dir = plugin_dir_url(__FILE__) . 'js';\r\n\n  wp_enqueue_script('my-block', $js_dir . '\/my-block.js', [ 'wp-blocks', 'wp-dom' ] , null, true);\n  wp_enqueue_style('my-block', $css_dir . '\/my-block.css', [ 'wp-edit-blocks' ]);\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">2. What We Are Building<\/h2>\n\n\n\n<p>We will build a simple text field. It&#8217;s pretty much useless since we have Paragraph block, but this is just to introduce you to the syntax:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"719\" height=\"242\" src=\"https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2019\/11\/custom-block-result.jpg\" alt=\"\" class=\"wp-image-857\" srcset=\"https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2019\/11\/custom-block-result.jpg 719w, https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2019\/11\/custom-block-result-480x162.jpg 480w\" sizes=\"auto, (max-width: 719px) 100vw, 719px\" \/><figcaption>Our &#8220;Simple Text&#8221; custom block in action<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">3. The Script<\/h2>\n\n\n\n<p>Gutenberg uses a library called <strong>React<\/strong>. If you are not familiar with it, the code below will look taunting. But don&#8217;t worry, we will explain it the best we could.<\/p>\n\n\n\n<pre title=\"js\/my-block.js\" class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">(() =&gt; {\n  const el = window.wp.element.createElement;\n  const { registerBlockType } = window.wp.blocks;\n  const { RichText } = window.wp.blockEditor;\n\n  registerBlockType('my\/simple-text', {\n    title: 'Simple Text (Custom)',\n    icon: 'universal-access-alt',\n    category: 'layout',\n    attributes: {\n      content: {\n        type: 'array',\n        source: 'children',\n        selector: 'p',\n      },\n    },\n    edit: myEdit,\n    save: mySave\n  });\n  \n  \/\/ what's rendered in admin\n  function myEdit(props) {\n    const atts = props.attributes;\n\n    return el(RichText, {\n      tagName: 'p',\n      className: props.className,\n      value: atts.content,\n\n      \/\/ Listener when the RichText is changed.\n      onChange: (value) =&gt; {\n        props.setAttributes({ content: value });\n      },\n    });\n  }\n  \n  \/\/ what's saved in database and rendered in frontend\n  function mySave(props) {\n    const atts = props.attributes;\n\n    return el(RichText.Content, {\n      tagName: 'p',\n      value: atts.content,\n    });\n  }\n})();<\/code><\/pre>\n\n\n\n<p>Let&#8217;s break down the code:<\/p>\n\n\n\n<p><strong>SELF INVOKING FUNCTION<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">(() =&gt; {\n  ...\n})();<\/code><\/pre>\n\n\n\n<p>This wrapper is used to enclose our code. So any variables or functions defined here won&#8217;t leak to the outside.<\/p>\n\n\n\n<p><strong>WP BLOCKS LIBRARY<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">const el = window.wp.element.createElement;\nconst { registerBlockType } = window.wp.blocks;\nconst { RichText } = window.wp.blockEditor;<\/code><\/pre>\n\n\n\n<p>Gutenberg already provided us with many reusable functions and elements such <code>wp.blockEditor.RichText<\/code>.<\/p>\n\n\n\n<p>The code above is just re-assigning the variable so it&#8217;s easier to use later.<\/p>\n\n\n\n<p><strong>REGISTER BLOCK<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">registerBlockType('my\/simple-text', {\n  title: 'Simple Text (Custom)',\n  icon: 'universal-access-alt',\n  category: 'layout',\n  attributes: {\n    content: {\n      type: 'array',\n      source: 'children',\n      selector: 'p',\n    },\n  },\n  edit: myEdit,\n  save: mySave\n});<\/code><\/pre>\n\n\n\n<ul><li><code>my\/simple-text<\/code> &#8211; The block&#8217;s slug. This will translate into the wrapper&#8217;s class name like &#8220;wp-block-my-simple-text&#8221;.<\/li><li><code>title<\/code> &#8211; Is what appears when selecting the block.<\/li><li><code>icon<\/code> &#8211; <a rel=\"noreferrer noopener\" href=\"https:\/\/developer.wordpress.org\/resource\/dashicons\/\" target=\"_blank\">Click here<\/a> for a list of available icons.<\/li><li><code>attributes<\/code> &#8211; List of variables for the block. <code>source<\/code> and <code>selector<\/code> defines how to extract the value from saved content. <a rel=\"noreferrer noopener\" aria-label=\"More info\u00bb (opens in a new tab)\" href=\"https:\/\/developer.wordpress.org\/block-editor\/developers\/block-api\/block-attributes\/\" target=\"_blank\">More info\u00bb<\/a><\/li><li><code>edit<\/code> &#8211;  Callback on how to render the content in the editor.<\/li><li><code>save<\/code> &#8211; Callback on how to render the content before saved to the database.<\/li><\/ul>\n\n\n\n<p><strong>EDIT<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">function myEdit(props) {\n  const atts = props.attributes;\n\n  return el(RichText, {\n    tagName: 'p',\n    className: props.className,\n    value: atts.content,\n\n    \/\/ Listener when the RichText is changed.\n    onChange: (value) =&gt; {\n      props.setAttributes({ content: value });\n    },\n  });\n}<\/code><\/pre>\n\n\n\n<p>Edit function returns the elements that are rendered in Gutenberg. Here&#8217;s the breakdown:<\/p>\n\n\n\n<ul><li><code><strong>el(...)<\/strong><\/code> &#8211; create an HTML element.<\/li><li><code><strong>RichText<\/strong><\/code> &#8211; a special element from Gutenberg. It&#8217;s like <code>&lt;input type=\"text\"&gt;<\/code> but has a toolbar to bold, italic, anchor link, etc.<\/li><li><code><strong>tagName<\/strong><\/code> &#8211; wraps the RichText with <code>&lt;p&gt;<\/code>.<\/li><li><code><strong>value<\/strong><\/code> &#8211; uses the <code>content<\/code> variable we defined in <code>attributes<\/code> as the value of this RichText.<\/li><li><code><strong>onChange<\/strong><\/code> &#8211; updates the <code>content<\/code> variable as we type.<\/li><\/ul>\n\n\n\n<p><strong>SAVE<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">function mySave(props) {\n  const atts = props.attributes;\n\n  return el(RichText.Content, {\n    tagName: 'p',\n    value: atts.content,\n  });\n}<\/code><\/pre>\n\n\n\n<p>The HTML returned here is saved into the database. In turn, this is also what is shown on the front end.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. The Style<\/h2>\n\n\n\n<p>Our custom block is just a standard field text, so the CSS is just this:<\/p>\n\n\n\n<pre title=\"css\/my-block.css\" class=\"wp-block-code\"><code lang=\"css\" class=\"language-css\">.wp-block-my-simple-text {\n  border: 1px solid rgba(0,0,0,.1);\n  padding: 1rem;\n}<\/code><\/pre>\n\n\n\n<p><strong>Done!<\/strong> Go check out your block in the editor. You should see your custom block listed like this:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-medium\"><img loading=\"lazy\" decoding=\"async\" width=\"480\" height=\"395\" src=\"https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2019\/11\/custom-block-selection-480x395.jpg\" alt=\"\" class=\"wp-image-858\" srcset=\"https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2019\/11\/custom-block-selection-480x395.jpg 480w, https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2019\/11\/custom-block-selection.jpg 650w\" sizes=\"auto, (max-width: 480px) 100vw, 480px\" \/><figcaption>Selecting the custom block<\/figcaption><\/figure><\/div>\n\n\n\n<hr class=\"wp-block-separator is-style-dots\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Creating a custom block is difficult and can get ugly. So try to use a <strong>custom block style<\/strong> as shown above whenever possible.<\/p>\n\n\n\n<p>If you are interested to learn more, we made an extensive tutorial on our Github. It also included the code with React JSX syntax:<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link\" href=\"https:\/\/github.com\/hrsetyono\/gutenberg-tutorial\" target=\"_blank\" rel=\"noreferrer noopener\">Learn More from Our Github<\/a><\/div>\n<\/div>\n\n\n\n<blockquote class=\"wp-block-quote\"><p>Feel free to leave a question below regarding custom block \ud83d\ude42<\/p><\/blockquote>\n\n\n\n<p style=\"font-size:14px\"><strong>Note<\/strong>: This article is originally written in March 2019. It has been updated to reflect the standard way in the latest WP version.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Creating a custom Gutenberg block isn&#8217;t an easy feat especially if you are not comfortable with JavaScript. We will you guide you slowly through the process<\/p>\n","protected":false},"author":1,"featured_media":868,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[22,32,31],"class_list":["post-676","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-gutenberg","tag-gutenberg","tag-javascript","tag-php"],"blocksy_meta":{"page_structure_type":"default","page_enable_vertical_spacing":"yes","has_hero_section":"default","hero_section":"type-1","hero_alignment1":"left","hero_alignment2":"center","hero_height":"230px","page_title_bg_type":"color","40ee9eab10d90f3d39fe5951e28de8a2":"","custom_hero_background":{"attachment_id":null},"parallax":{"desktop":false,"tablet":false,"mobile":false},"single_meta_elements":{"author":true,"date":true,"categories":true,"comments":true,"updated":false,"tags":false},"has_meta_label":"yes","single_meta_date_format":"M j, Y","page_excerpt_visibility":{"desktop":true,"tablet":true,"mobile":false},"pageTitleFont":{"family":"Default","variation":"n7","size":{"desktop":"32px","tablet":"30px","mobile":"25px"},"line-height":"1.3","letter-spacing":"0em","text-transform":"none","text-decoration":"none"},"pageTitleFontColor":{"default":{"color":"var(--paletteColor4)"}},"pageMetaFont":{"family":"Default","variation":"n6","size":{"desktop":"12px","tablet":"12px","mobile":"12px"},"line-height":"1.3","letter-spacing":"0em","text-transform":"uppercase","text-decoration":"none"},"pageMetaFontColor":{"default":{"color":"CT_CSS_SKIP_RULEDEFAULT"},"hover":{"color":"CT_CSS_SKIP_RULEDEFAULT"}},"pageExcerptFont":{"family":"Default","variation":"n5","size":"17px","line-height":"1.65","letter-spacing":"0em","text-transform":"none","text-decoration":"none"},"pageExcerptColor":{"default":{"color":"CT_CSS_SKIP_RULEDEFAULT"}},"pageTitleOverlay":{"default":{"color":"rgba(41, 51, 60, 0.2)"}},"pageTitleBackground":{"default":{"color":"#EDEFF2"}},"01fb1f06445f4173f359a2d30dd7aac5":"","7570cc01b2b6c08fa16555d2b46bfeb5":"","7af8aa8487441bb478a41ac55057ea85":"","date_format_source":"custom","56914810dc8f72d0b39772dcd852007b":"","176ea3993cc2462c20a07a98877042dd":"","4aea83d7d06ae1ef031e84a2e3f986b6":"","f7d907b36db0ad1c40de1b9bd7ec1889":"","68824acc951251675ade63b9639d7489":"","8dd2655b103c21c046bb38e5a7015c65":"","0acecf503927043dd9b6c3f354863d84":"","f04a3cd148fee0a37ff06a47330774d5":"","4e888d05f5b0284ef487cce32c53aa04":"","b215aef11ba90869fc74eb7b94658bd6":""},"_links":{"self":[{"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/posts\/676","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/comments?post=676"}],"version-history":[{"count":23,"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/posts\/676\/revisions"}],"predecessor-version":[{"id":2228,"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/posts\/676\/revisions\/2228"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/media\/868"}],"wp:attachment":[{"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/media?parent=676"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/categories?post=676"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/tags?post=676"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}