npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jverneaut/html-to-gutenberg

v2.5.0

Published

Create custom Gutenberg blocks from the HTML templates you already have.

Readme

Node LTS Tests Status GitHub Release

HTML To Gutenberg

HTML to Gutenberg is a Webpack plugin that compiles your HTML into real, native Gutenberg blocks. No shortcodes. No server-side hacks. The real Gutenberg editing experience.

It generates proper edit.js, render.php, block.json, and index.js files, exactly what you'd write by hand if you were building blocks with React and PHP.

But you won’t have to write a single line of React or PHP.

Just structure your HTML with a few intuitive attributes, and let the plugin handle the heavy lifting.

The output is 100% native Gutenberg code. If you stop using the plugin, your blocks still work. No lock-in.

Try it out in the Live Editor to see how quickly you can convert your HTML into Gutenberg blocks.

HTML To Gutenberg WordCamp Demo

Quick start

This project is built with ESM and requires Node.js version 20.0.0 or later.

1. Scaffold an HTML To Gutenberg blocks plugin

cd wp-content/plugins
npx @wordpress/create-block --template html-to-gutenberg-template

This sets up everything you need — pre-configured to work with HTML To Gutenberg.

2. Start development

cd html-to-gutenberg-blocks # Your block plugin directory
npm run start

3. Edit the default block

Open wp-content/plugins/html-to-gutenberg-blocks/src/block.html and make changes to the default block.

HTML To Gutenberg will automatically convert it into a working Gutenberg block.

4. Add additional blocks

To create additional blocks, simply add new .html files in the src folder.

Each HTML file becomes its own block and is automatically processed by HTML To Gutenberg.

src/
├── block.html         # Default block (can be safely deleted)
├── hero.html          # Another custom block
├── testimonial.html   # Yet another one

Note about blocks deletion

When you delete an HTML file from src, its corresponding Gutenberg block is removed on the next build.

However, depending on your setup, you may also need to manually delete the removed block folder inside the build/ directory to fully clean it up.

5. Activate your plugin

Enable your block in the WordPress admin and drop it into any page or post.

Make sure you set a title when generating the plugin with @wordpress/create-block. If you don’t, the plugin may not appear in the WordPress plugins page.

If you forgot to add one, open the root PHP file of your plugin and add a Plugin Name like so:

<?php

/**
 * Plugin Name:       HTML To Gutenberg Blocks <------ Add a name here
 * Version:           0.1.0
 * Requires at least: 6.7
 * Requires PHP:      7.4
 * Author:            The WordPress Contributors
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       textdomain
 *
 * @package CreateBlock
 */

6. Build for production

npm run build

Bundles and minifies your blocks.

Documentation

Visit the official documentation.

Quick links

Example

Visit the official documentation to try this code in a live interactive editor.

block.html

<section
  class="py-20 bg-blue-200"
  data-parent="custom/parent-block"
  data-editing-mode="contentOnly"
>
  <inspector-controls>
    <panel-body title="Settings">
      <select-control data-bind="postType" label="Post Type">
        <select-control-option value="posts">Posts</select-control-option>
        <select-control-option value="pages">Pages</select-control-option>
      </select-control>
    </panel-body>
  </inspector-controls>

  <div class="container">
    <div class="grid grid-cols-12 gap-4">
      <div class="col-span-12 md:col-span-6">
        <h2 class="text-2xl" data-bind="sectionTitle">
          Edit me inside the editor
        </h2>
        <img class="aspect-square object-cover" data-bind="sectionImage" />
      </div>

      <div class="col-span-12 md:col-span-6">
        <inner-blocks allowedBlocks="all" templateLock="all">
          <inner-block name="core/group">
            <inner-block name="core/heading" level="3"></inner-block>
            <inner-block name="core/paragraph">
              <block-attribute name="content">
                Lorem ipsum dolor sit amet consectetur.
              </block-attribute>
            </inner-block>
          </inner-block>
        </inner-blocks>
      </div>
    </div>
  </div>
</section>

block/edit.js

import {
  useBlockProps,
  useBlockEditingMode,
  InnerBlocks,
  RichText,
  MediaUpload,
  InspectorControls,
} from "@wordpress/block-editor";
import { PanelBody, SelectControl } from "@wordpress/components";
import { Image } from "@10up/block-components/components/image";

export default ({ attributes, setAttributes }) => {
  useBlockEditingMode("contentOnly");

  return (
    <section {...useBlockProps({ className: "py-20 bg-blue-200" })}>
      <InspectorControls>
        <PanelBody title="Settings">
          <SelectControl
            label="Post Type"
            value={attributes.postType}
            onChange={(postType) => setAttributes({ postType })}
            options={[
              { label: "Posts", value: "posts" },
              { label: "Pages", value: "pages" },
            ]}
          ></SelectControl>
        </PanelBody>
      </InspectorControls>

      <div className="container">
        <div className="grid grid-cols-12 gap-4">
          <div className="col-span-12 md:col-span-6">
            <RichText
              className="text-2xl"
              tagName="h2"
              value={attributes.sectionTitle}
              onChange={(sectionTitle) => setAttributes({ sectionTitle })}
              placeholder="Section title"
            ></RichText>
            <MediaUpload
              value={attributes.sectionImage}
              onSelect={(image) => setAttributes({ sectionImage: image.id })}
              render={({ open }) => (
                <Image
                  style={{ cursor: "pointer", pointerEvents: "all" }}
                  onClick={open}
                  className="aspect-square object-cover"
                  id={attributes.sectionImage}
                  onSelect={(image) =>
                    setAttributes({ sectionImage: image.id })
                  }
                />
              )}
            ></MediaUpload>
          </div>

          <div className="col-span-12 md:col-span-6">
            <InnerBlocks
              template={[
                [
                  "core/group",
                  {},
                  [
                    ["core/heading", { level: 3 }],
                    [
                      "core/paragraph",
                      { content: "Lorem ipsum dolor sit amet consectetur." },
                    ],
                  ],
                ],
              ]}
              templateLock="all"
            ></InnerBlocks>
          </div>
        </div>
      </div>
    </section>
  );
};

block/render.php

<?php

$sectionImage_id = $attributes['sectionImage'] ?? '';
$sectionImage = $sectionImage_id ? wp_get_attachment_image_src($sectionImage_id, 'full') : [''];
$sectionImage_src = $sectionImage[0] ?? '';
$sectionImage_srcset = $sectionImage_id ? wp_get_attachment_image_srcset($sectionImage_id, 'full') : '';
$sectionImage_sizes = $sectionImage_id ? wp_get_attachment_image_sizes($sectionImage_id, 'full') : '';
$sectionImage_alt = $sectionImage_id ? get_post_meta($sectionImage_id, '_wp_attachment_image_alt', true) : '';

?>

<section <?php echo get_block_wrapper_attributes(['class' => 'py-20 bg-blue-200']); ?>>
  <div class="container">
    <div class="grid grid-cols-12 gap-4">
      <div class="col-span-12 md:col-span-6">
        <h2 class="text-2xl"><?php echo wp_kses_post($attributes['sectionTitle'] ?? ''); ?></h2>
        <img class="aspect-square object-cover" src="<?php echo esc_url($sectionImage_src); ?>" srcset="<?php echo esc_attr($sectionImage_srcset); ?>" sizes="<?php echo esc_attr($sectionImage_sizes); ?>" alt="<?php echo esc_attr($sectionImage_alt); ?>" />
      </div>
      <div class="col-span-12 md:col-span-6">
        <?php echo $content; ?>
      </div>
    </div>
  </div>
</section>

block/block.json

{
  "name": "custom/block",
  "title": "Block",
  "textdomain": "block",
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "version": "0.1.0",
  "category": "theme",
  "example": {},
  "parent": ["custom/parent-block"],
  "attributes": {
    "align": { "type": "string", "default": "full" },
    "sectionImage": { "type": "integer" },
    "postType": { "type": "string", "default": "posts" },
    "sectionTitle": { "type": "string", "default": "Edit me inside the editor" }
  },
  "supports": { "html": false, "align": ["full"] },
  "editorScript": "file:./index.js",
  "render": "file:./render.php"
}

block/index.js

import { registerBlockType } from "@wordpress/blocks";
import { InnerBlocks } from "@wordpress/block-editor";

import Edit from "./edit.js";
import metadata from "./block.json";

registerBlockType(metadata.name, {
  edit: Edit,
  save: () => <InnerBlocks.Content />,
});