{"id":22486163,"url":"https://github.com/front/gutenberg-js","last_synced_at":"2025-08-02T19:31:00.251Z","repository":{"id":57112939,"uuid":"139964865","full_name":"front/gutenberg-js","owner":"front","description":"JavaScript only version of the WordPress Gutenberg editor (DEPRECATED)","archived":true,"fork":false,"pushed_at":"2020-08-11T15:22:31.000Z","size":387,"stargazers_count":390,"open_issues_count":11,"forks_count":42,"subscribers_count":36,"default_branch":"master","last_synced_at":"2024-08-10T22:57:20.973Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/front.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-07-06T09:38:03.000Z","updated_at":"2024-07-30T07:17:55.000Z","dependencies_parsed_at":"2022-08-21T20:50:30.195Z","dependency_job_id":null,"html_url":"https://github.com/front/gutenberg-js","commit_stats":null,"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/front%2Fgutenberg-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/front%2Fgutenberg-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/front%2Fgutenberg-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/front%2Fgutenberg-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/front","download_url":"https://codeload.github.com/front/gutenberg-js/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228500077,"owners_count":17929997,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-06T17:13:59.891Z","updated_at":"2024-12-06T17:15:29.944Z","avatar_url":"https://github.com/front.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"**DEPRECATED**\n\nOn our 2 main projects that used to depend on gutenberg-js, we have moved away and started using the Gutenberg packages directly.\n\nPlease check:\n\n* https://www.drupal.org/project/gutenberg\n* https://github.com/front/g-editor\n_____\n\n![gutenberg-js](gutenberg_js_logo.svg?sanitize=true \"gutenberg-js\")\n\n# gutenberg-js\n\nWe made [Gutenberg](https://github.com/Wordpress/gutenberg) editor a little more **customizable**!\n\nGutenberg editor can **be easily included in your apps** with this [package](https://github.com/front/gutenberg-js).\n\nThis package is based on [Gutenberg v4.8.0](https://github.com/WordPress/gutenberg/releases/tag/v4.8.0) and respective @wordpress packages versions.\n\n## Table of contents\n\n* [Installation](#installation)\n  * [Dependencies](#dependencies)\n* [Development](#development)\n* [Global variables](#global-variables)\n  * [apiFetch](#apifetch)\n    * [Post Types](#post-types)\n      * [Wp block](#wp-block)\n    * [Posts and Pages](#posts-and-pages)\n    * [Categories](#categories)\n    * [Media](#media)\n    * [Taxonomies](#taxonomies)\n    * [Blocks](#blocks)\n    * [Themes](#themes)\n  * [url](#url)\n* [Usage](#usage)\n  * [Gutenberg Stores](#gutenberg-stores)\n  * [Registering Custom Blocks](#registering-custom-blocks)\n* [Customize your Gutenberg](#customize-your-gutenberg)\n  * [Events](#events)\n* [Rendering Dynamic Blocks](#rendering-dynamic-blocks)\n* [Custom blocks](#custom-blocks)\n  * [Creating and Registering](#creating-and-registering)\n  * [Sharing](#sharing)\n\n## Installation\n\n**gutenberg-js** is available through npm.\n\n```sh\n$ npm install @frontkom/gutenberg-js\n```\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n### Dependencies\n\nSome of the Gutenberg features depend on the [TinyMCE](https://www.tinymce.com/) text editor and the editor expects to find TinyMCE *plugins*, *themes* and *skins* on the project root. Since **gutenberg-js** has TinyMCE as a dependency, we suggest to use webpack and [CopyWebpackPlugin](https://github.com/webpack-contrib/copy-webpack-plugin) to handle with that.\n\n```js\n// webpack.config.js\nconst CopyWebpackPlugin = require('copy-webpack-plugin');\n\nmodule.exports = {\n    ...\n    plugins: [\n        new CopyWebpackPlugin([\n            { from: 'node_modules/tinymce/plugins', to: `${ your_root_path }/plugins` },\n            { from: 'node_modules/tinymce/themes', to: `${ your_root_path }/themes` },\n            { from: 'node_modules/tinymce/skins', to: `${ your_root_path }/skins` },\n        ], {}),\n    ],\n    ...\n}\n```\n\nGutenbergJS expects to find React (v16.6.3), ReactDOM (v16.6.3), moment (v2.22.1) and jquery (v1.12.4) libraries in the environment it runs. Maybe you would add the following lines to your pages.\n\n```html\n\u003cscript src=\"https://unpkg.com/react@16.6.3/umd/react.production.min.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://unpkg.com/react-dom@16.6.3/umd/react-dom.production.min.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://unpkg.com/moment@2.22.1/min/moment.min.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://code.jquery.com/jquery-1.12.4.min.js\" integrity=\"sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=\" crossorigin=\"anonymous\"\u003e\u003c/script\u003e\n```\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n## Development\n\nThe main goal of Gutenberg JS is to expose all Gutenberg packages and keep them up-to-date.\n\nIn order to ensure Gutenberg JS never breaks because of our overrides, we had to use fixed versions for the overrided packages in `package.json`.\n\nSo everytime we have to update Gutenberg JS, there are several steps we must follow:\n\n1. Check @wordpress packages versions from Gutenberg release we want to upgrade to and update `package.json` file (`npm outdated` could help).\n2. Check if there are new @wordpress packages and import them in `index.js` file'.\n3. Check if our overrides are updated and work well with new @wordpress packages versions.\n4. Check if there are new blocks containing images and apply `data` attributes override.\n5. Test, test and test. We can use [g-editor](https://github.com/front/g-editor) to test the editor.\n6. [To do: unit tests could help]\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n## Global variables\n\nGutenberg depends on several global variables: `wp`, `userSettings`, `wpEditorL10n`, `wpApiSettings`, etc and probably during your Gutenberg experiencie you will discover other required variables, please share with us if you feel they are important to Gutenberg execution.\n\nHere we're only presenting those variables which - by our experience - we belive are crucial to Gutenberg and already set to them default values. If you don't set them up, you'll see that Gutenberg editor won't run.\n\nSo we recommend you to set up them all in one file called `globals.js` or `settings.js` for example and import them **before** Gutenberg call. Feel free to override Gutenberg global variables if you need.\n\n```js\n// globals.js\n\nwindow.wp = {\n    apiFetch,\n    url: { addQueryArgs },\n    ...,\n};\n\nwindow.userSettings = {\n    uid: 2, // Among other things, this uid is used to identify and store editor user preferences in localStorage\n};\n\n// set your root path\nwindow.wpApiSettings = {\n    root: 'YOUR_ROOT_PATH',\n    ...,\n};\n```\n\nWe are working to include on **gutenberg-js** all settings that shouldn't be part of your apps, but you always can override them if you need.\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n### apiFetch\n\nThose two are very important for communication between the editor and remaining app, so you should set them up according your needs.\n\n***apiFetch*** is the method that will handle data operations on Gutenberg, like getting resources (categories for example), saving page changes or deleting pages, etc. It receives an object with `path`, `method`, `data`, etc, so you can treat it as you want.\n\n```js\nfunction apiFetch(options) {\n    // Do something with those options like calling an API\n    // or actions from your store...\n}\n```\n\nNext, we will show some commons API requests Gutenberg does and the respective response it expects. For more information, you can check the [WordPress REST API Documentation](https://developer.wordpress.org/rest-api/reference/post-revisions/).\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n#### Post Types\n\nThe Gutenberg editor will ask for available **Post Types** through `/wp/v2/types/?context=edit` request. The _type_ properties that can be checked in [WordPress documentation](https://developer.wordpress.org/rest-api/reference/post-types/).\n\n**Post Types:** _post_, _pages_, _attachment_, _wp_block_\n\n**Request for post type settings:** `/wp/v2/types/post?context=edit`\n\n**Request for page type settings:** `/wp/v2/types/page?context=edit`\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n##### Wp Block\n\nThere is no documentation for `/wp/v2/types/wp_block?context=edit` request yet, but the response should be similar to post and page responses:\n\n```js\n{\n    \"capabilities\": { ... }\n    \"description\": \"\",\n    \"hierarchical\": false,\n    \"labels\": { ... }\n    \"name\": \"Blocks\",\n    \"slug\": \"wp_block\",\n    \"taxonomies\": [],\n    \"rest_base\": \"blocks\",\n    \"supports\": { ... }\n    \"viewable\": false,\n    \"_links\": { ... }\n}\n```\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n#### Posts and Pages\n\nCheck the WordPress API documentation for [Posts](https://developer.wordpress.org/rest-api/reference/posts/) and [Pages](https://developer.wordpress.org/rest-api/reference/pages/) requests.\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n#### Categories\n\nCheck the WordPress API documentation for [Categories](https://developer.wordpress.org/rest-api/reference/categories/).\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n#### Taxonomies\n\nTaxonomies and Categories are requested to fill Categories panel in Document sidebar. Check the WordPress API documentation for [Taxonomies](https://developer.wordpress.org/rest-api/reference/taxonomies/).\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n#### Media\n\nHere is the WordPress API documentation for [Media](https://developer.wordpress.org/rest-api/reference/media/). The **gutenberg-js** introduces the `data` property which is an object with all data attributes you want to add to image/media DOM element.\n\n```js\n{\n    ...,\n    id: 1527069591355,\n    link: MEDIA_LINK_HERE,\n    source_url: MEDIA_URL_HERE,\n    // Additionaly, you can add some data attributes for images for example\n    data: { entity_type: 'file', entity_uuid: 'e94e9d8d-4cf4-43c1-b95e-1527069591355' }\n    ...,\n}\n```\n\nThe editor also requests for `wp/v2/media` OPTIONS:\n\n```js\n{\n    headers: {\n        get: value =\u003e {\n            if (value === 'allow') {\n                return [ 'POST' ];\n            }\n        },\n    },\n}\n```\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n#### Blocks\n\nThere is no documentation for `/wp/v2/wp_blocks` or `/wp/v2/blocks` request yet, but the response should be similar to posts and pages responses with and `id` and `content`:\n\n```js\n{\n    content: \"\u003c!-- wp:paragraph --\u003e↵    \u003cp\u003e3\u003c/p\u003e↵    \u003c!-- /wp:paragraph --\u003e\",\n    id: 131,\n    title: \"my block\",\n}\n```\n\nGutenberg editor allows us to create, edit, list, get one and delete one block operations, so make sure you expect GET, POST, PUT and DELETE requests.\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n#### Themes\n\nGutenberg will ask for the [theme features](https://codex.wordpress.org/Theme_Features) through the index request (`/wp/v2/themes`). The response should be the following object.\n\n```js\n{\n    ...,\n    theme_supports: {\n        formats: [ 'standard', 'aside', 'image', 'video', 'quote', 'link', 'gallery', 'audio' ],\n        'post-thumbnails': true,\n    },\n    ...,\n}\n```\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n### url\n\n***url*** should has a function called `addQueryArgs( url, args )` that handles with `url` and `args` and returns the final url to different actions. The original implementation is the following, feel free to keep it or change it according to your needs.\n\n```js\n/**\n * External dependencies\n */\nimport { parse, format } from 'url';\nimport { parse as parseQueryString, stringify } from 'querystring';\n\n/**\n * Appends arguments to the query string of the url\n *\n * @param  {String} url   URL\n * @param  {Object} args  Query Args\n *\n * @return {String}       Updated URL\n */\nexport function addQueryArgs(url, args) {\n    const queryStringIndex = url.indexOf('?');\n    const query = queryStringIndex !== -1 ? parse(url.substr(queryStringIndex + 1)) : {};\n    const baseUrl = queryStringIndex !== -1 ? url.substr(0, queryStringIndex) : url;\n\n    return baseUrl + '?' + stringify({ ...query, ...args });\n}\n```\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n## Usage\n\nWe've tried to make it easy to import **gutenberg-js** modules to your apps.\n\n```js\n// Importing global variables that Gutenberg requires\nimport './globals';\n\n// Importing domReady and editPost modules\nimport { domReady, editPost } from '@frontkom/gutenberg-js';\n\n// Don't forget to import the style\nimport '@frontkom/gutenberg-js/build/css/block-library/style.css';\nimport '@frontkom/gutenberg-js/build/css/style.css';\n\n// DOM element id where editor will be displayed\nconst target = 'editor';\n\n// Post properties\nconst postType = 'post'; // or 'page'\nconst postId = 123;\n\n// Some editor settings\nconst settings = {\n    alignWide: true,\n    availableTemplates: [],\n    allowedBlockTypes: true,\n    disableCustomColors: false,\n    disableCustomFontSizes: false,\n    disablePostFormats: false,\n    titlePlaceholder: \"Add title\",\n    bodyPlaceholder: \"Write your story\",\n    isRTL: false,\n    autosaveInterval: 10,\n    styles: [],\n    postLock: {\n        isLocked: false,\n    },\n    ...\n    // @frontkom/gutenberg-js settings\n    canAutosave: false,  // to disable the Editor Autosave feature (default: true)\n    canPublish: false,   // to disable the Editor Publish feature (default: true)\n    canSave: false,      // to disable the Editor Save feature (default: true)\n    mediaLibrary: false, // to disable the Media Library feature (default: true)\n};\n\n// Post properties to override\nconst overridePost = {};\n\n// Et voilá... Initializing the editor!\nwindow._wpLoadGutenbergEditor = new Promise(function (resolve) {\n    domReady(function () {\n        resolve(editPost.initializeEditor(target, postType, postId, settings, overridePost));\n    });\n});\n```\n\n**Note**: Gutenberg requires utf-8 encoding, so don't forget to add `\u003cmeta charset=\"utf-8\"\u003e` tag to your html `\u003chead\u003e`.\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n### Gutenberg Stores\n\nAdditionally, after initializing the editor, you can have access to Gutenberg stores (`core`, `core/blocks`, `core/data`, `core/edit-post`, `core/editor`, `core/viewport`) through the `data` module and its `select` and `dispatch` methods:\n\n```js\n// Importing select and dispatch methods from @frontkom/gutenberg-js package\nimport { data } from '@frontkom/gutenberg-js';\n\n// Use dispatch to change the state of something\ndata.dispatch('core/edit-post').openGeneralSidebar('edit-post/block');\ndata.dispatch('core/edit-post').closeGeneralSidebar();\n\n// Use select to get the state of something\ndata.select( 'core/editor' ).getEditedPostContent();\n// \u003c!-- wp:paragraph --\u003e\n// \u003cp\u003eHello\u003c/p\u003e\n// \u003c!-- /wp:paragraph --\u003e\n\n```\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n### Registering Custom Blocks\n\nYou can create your custom blocks using the `registerBlockType` method from `blocks` module. Check out the example below and the Wordpress [documentation](https://wordpress.org/gutenberg/handbook/blocks/) to read more about it.\n\n```js\nimport { blocks, editor } from '@frontkom/gutenberg-js';\n\nconst {\n    AlignmentToolbar,\n    BlockControls,\n    RichText,\n} = editor;\n\nblocks.registerBlockType('custom/my-block', {\n    title: 'My first block',\n    icon: 'universal-access-alt',\n    category: 'common',\n    attributes: {\n        content: {\n            type: 'array',\n            source: 'children',\n            selector: 'p',\n        },\n        alignment: {\n            type: 'string',\n        },\n    },\n    edit({ attributes, className, setAttributes }) {\n        const { content, alignment } = attributes;\n\n        function onChangeContent( newContent ) {\n            setAttributes( { content: newContent } );\n        }\n\n        function onChangeAlignment( newAlignment ) {\n            setAttributes( { alignment: newAlignment } );\n        }\n\n        return [\n            \u003cBlockControls\u003e\n                \u003cAlignmentToolbar\n                    value={ alignment }\n                    onChange={ onChangeAlignment }\n                /\u003e\n            \u003c/BlockControls\u003e,\n            \u003cRichText\n                tagName=\"p\"\n                className={ className }\n                style={ { textAlign: alignment } }\n                onChange={ onChangeContent }\n                value={ content }\n            /\u003e\n        ];\n    },\n    save({ attributes, className }) {\n        const { content, alignment } = attributes;\n\n        return (\n            \u003cRichText.Content\n                className={ className }\n                style={ { textAlign: alignment } }\n                value={ content }\n            /\u003e\n        );\n    },\n});\n\n```\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n## Customize your Gutenberg\n\nFollowing the same logic, we've created the `customGutenberg` global object where you can set everything that we made customizable on Gutenberg.\n\n```js\nwindow.customGutenberg = { ... };\n```\n\nAs the other global variables, `customGutenberg` should be defined **before** Gutenberg import.\n\nImportant to say that Gutenberg works perfectly without the settings of this object :)\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n### Events\n\n**gutenberg-js** makes possible to define callbacks (or effects) for Gutenberg actions. Since it is an experimental feature, we are only providing this for 'OPEN_GENERAL_SIDEBAR' and 'CLOSE_GENERAL_SIDEBAR' actions.\n\n```js\nwindow.customGutenberg = {\n    ...,\n    events: {\n        'OPEN_GENERAL_SIDEBAR': function(action, store) {\n            console.log( 'OPEN_GENERAL_SIDEBAR', action, store );\n        },\n        'CLOSE_GENERAL_SIDEBAR': function(action, store) {\n            console.log( 'CLOSE_GENERAL_SIDEBAR', action, store );\n        },\n    },\n    ...,\n};\n```\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n## Rendering Dynamic Blocks\n\nAs you probably know, Gutenberg allows us to create our owns blocks inside the editor and make them reusable. We can look for reusable blocks in the 'Add Blocks Menu' search bar.\n\nIf we change to 'Code Editor' mode, we can check that only a `ref` id is saved for our reusable block.\n\n```js\n\u003c!-- wp:block {\"ref\":1537389905603} /--\u003e\n```\n\nGutenberg uses `wp/v2/wp_blocks/[:id]` request to get the block content inside the editor. Make sure you do the same process when your app do the final render of the page (outside of the editor).\n\nThe same happens with embed blocks:\n\n```js\n\u003c!-- wp:core-embed/twitter {\"url\": \"https://twitter.com/drupalgutenberg/status/1040203765452820480\", \"type\": \"rich\", \"providerNameSlug\": \"twitter\"} --\u003e\n\u003cfigure class=\"wp-block-embed-twitter wp-block-embed is-type-rich is-provider-twitter\"\u003e\n    \u003cdiv class=\"wp-block-embed__wrapper\"\u003e\n        https://twitter.com/drupalgutenberg/status/1040203765452820480\n    \u003c/div\u003e\n\u003c/figure\u003e\n\u003c!-- /wp:core-embed/twitter --\u003e\n```\n\nAnd latest posts widget:\n\n```js\n\u003c!-- wp:latest-posts /--\u003e\n```\n\nYour app must be in charge of the render of the dynamic blocks.\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n## Custom Blocks\n\nWe can create **custom blocks** to our Gutenberg editor and used them to build our website pages.\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n### Creating and Registering\n\nA Gutenberg block requires some properties like a `title`, an `icon`, a `category` and the `edit` and the `save` methods which describe the structure of the block inside the editor and what block content should be saved.\n\n```js\nconst myFirstBlock = {\n    title: 'My first block!',\n    icon: 'universal-access-alt',\n    category: 'cloudblocks',\n\n    edit() {\n        return \u003cp\u003eHello editor.\u003c/p\u003e;\n    },\n\n    save() {\n        return \u003cp\u003eHello saved content.\u003c/p\u003e;\n    },\n};\n```\n\nAfter defining all the properties, the new block must be registered so it becomes available in editor inserter dialog under the chosen category. If the blocks's category doesn't exist yet, we must add it to the editor inserter dialog.\n\nA blocks category requires a slug and a title:\n\n```js\nconst category = {\n    slug: 'cloudblocks',\n    title: 'Gutenberg-Cloud Blocks',\n};\n```\n\nTo check which categories already exist, we can use `getCategories()` selector and to add a new category to the editor we can use `setCategories()` action. Both methods are provided by Gutenberg `core/blocks` store which are accessible througg `wp.data`.\n\n```js\nconst { dispatch, select } = wp.data;\n\nconst currentCategories = select('core/blocks').getCategories().filter(item =\u003e item.slug !== category.slug);\n\ndispatch('core/blocks').setCategories([ category, ...currentCategories ]);\n```\n\nFinally, we are ready to register our custom block using `registerBlockType` method:\n\n```js\nconst { registerBlockType } = wp.blocks;\n\nregisterBlockType(`${category.slug}/my-first-block`, { category: category.slug, ...hero.settings });\n```\n\nAnd the block is available in the editor inserter dialog! Full example:\n\n```js\nconst { dispatch, select } = wp.data;\nconst { registerBlockType } = wp.blocks;\n\n// Setting block's properties\nconst myFirstBlock = {\n    title: 'My first block!',\n    icon: 'universal-access-alt',\n    category: 'cloudblocks',\n\n    edit() {\n        return \u003cp\u003eHello editor.\u003c/p\u003e;\n    },\n\n    save() {\n        return \u003cp\u003eHello saved content.\u003c/p\u003e;\n    },\n};\n\n// Setting category's properties\nconst category = {\n    slug: 'cloudblocks',\n    title: 'Gutenberg-Cloud Blocks',\n};\n\n// Checking the category\nconst currentCategories = select('core/blocks').getCategories().filter(item =\u003e item.slug !== category.slug);\ndispatch('core/blocks').setCategories([ category, ...currentCategories ]);\n\n// Registering the new block\nregisterBlockType(`${category.slug}/my-first-block`, myFirstBlock);\n```\n\nIn [Creating Block Types](https://wordpress.org/gutenberg/handbook/blocks/) section of Gutenberg handbook, we can check more examples of how to custom blocks with more complexity. Also we can check more details about blocks properties in [Block API](https://wordpress.org/gutenberg/handbook/block-api/) documentation.\n\n[↑ Go up to Table of contents](#table-of-contents)\n\n### Sharing\n\nAn easy way to share a custom block is to publish the block as a npm package.\n\nHere is an example of a custom block npm package, the [Hero Section](https://github.com/front/g-hero-section).\n\n[↑ Go up to Table of contents](#table-of-contents)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffront%2Fgutenberg-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffront%2Fgutenberg-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffront%2Fgutenberg-js/lists"}