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

@10up/block-renderer-serializer

v0.3.0

Published

Render WordPress block markup from JSON block trees using native serialization

Downloads

803

Readme

@10up/block-renderer-serializer

Render JSON block trees to WordPress block markup using native @wordpress/blocks serialization. This package converts the flat BlockTree JSON format into valid WordPress block markup.

Installation

npm install @10up/block-renderer-serializer
# or
pnpm add @10up/block-renderer-serializer

Overview

This package:

  1. Sets up browser environment - Provides DOM APIs needed by @wordpress/blocks in Node.js
  2. Registers blocks - Core blocks and third-party blocks from plugins/themes
  3. Converts JSON to blocks - Transforms BlockTree format to WordPress block objects
  4. Serializes to markup - Produces valid <!-- wp:block-name --> markup

Usage

Basic Rendering

import {
  setupBrowserEnvironment,
  registerCoreBlocks,
  renderBlockTree
} from '@10up/block-renderer-serializer';

// Setup environment (required once at startup)
setupBrowserEnvironment();
await registerCoreBlocks();

// Render a block tree
const tree = {
  root: 'para-1',
  elements: {
    'para-1': {
      key: 'para-1',
      type: 'core/paragraph',
      props: { content: 'Hello world' }
    }
  }
};

const markup = renderBlockTree(tree);
// <!-- wp:paragraph -->
// <p>Hello world</p>
// <!-- /wp:paragraph -->

Nested Blocks

const tree = {
  root: 'group-1',
  elements: {
    'group-1': {
      key: 'group-1',
      type: 'core/group',
      props: { layout: { type: 'constrained' } },
      children: ['heading-1', 'para-1']
    },
    'heading-1': {
      key: 'heading-1',
      type: 'core/heading',
      props: { content: 'Title', level: 2 },
      parentKey: 'group-1'
    },
    'para-1': {
      key: 'para-1',
      type: 'core/paragraph',
      props: { content: 'Content here' },
      parentKey: 'group-1'
    }
  }
};

const markup = renderBlockTree(tree);

Multiple Root Blocks

Render multiple separate block trees:

import { renderBlockTrees } from '@10up/block-renderer-serializer';

const trees = [tree1, tree2, tree3];
const markup = renderBlockTrees(trees);

Custom Serialization Options

import { renderBlockTree } from '@10up/block-renderer-serializer';

const markup = renderBlockTree(tree, {
  // Add newlines between blocks
  pretty: true
});

Rendering Block Templates

For migration scripts or when working with PHP-style block templates, use renderBlockTemplate:

import {
  setupBrowserEnvironment,
  registerCoreBlocks,
  renderBlockTemplate
} from '@10up/block-renderer-serializer';

setupBrowserEnvironment();
await registerCoreBlocks();

// PHP-style nested array format: [blockType, attributes?, innerBlocks?]
const template = [
  ['core/paragraph', { content: 'Hello world' }],
  ['core/group', { layout: { type: 'constrained' } }, [
    ['core/heading', { content: 'Title', level: 2 }],
    ['core/paragraph', { content: 'Content here' }],
  ]],
];

const markup = renderBlockTemplate(template);

Block Template Format

Each entry in the template is an array with 1-3 elements:

type BlockTemplateEntry =
  | [string]                                          // Just block type
  | [string, Record<string, unknown>]                 // Block type + attributes
  | [string, Record<string, unknown>, BlockTemplateEntry[]]; // Full entry

Examples:

  • ['core/separator'] - Block with no attributes
  • ['core/paragraph', { content: 'Hello' }] - Block with attributes
  • ['core/group', {}, [['core/paragraph', {}]]] - Block with inner blocks

Convert Template to Blocks

For lower-level access, convert templates to WordPress block objects:

import { convertTemplateToBlocks } from '@10up/block-renderer-serializer';

const blocks = convertTemplateToBlocks(template);
// Returns WordPress BlockInstance[] for further manipulation

Third-Party Block Support

The renderer supports third-party blocks from plugins and themes. These blocks must be registered before rendering.

Scan WordPress Installation

Scan a WordPress installation for all available blocks:

import {
  scanBlocksFromWpContent,
  registerScannedBlocks
} from '@10up/block-renderer-serializer';

// Scan wp-content directory for block.json files
const scannedBlocks = scanBlocksFromWpContent(
  '/path/to/wp-content',
  'theme-name'  // Optional: scan theme blocks too
);

// Register all found blocks
const registeredCount = registerScannedBlocks(scannedBlocks);
console.log(`Registered ${registeredCount} third-party blocks`);

This scans:

  • /wp-content/plugins/*/ (excluding gutenberg to avoid duplicates)
  • /wp-content/mu-plugins/*/
  • /wp-content/client-mu-plugins/*/ (VIP)
  • /wp-content/themes/{theme-name}/blocks/

Register Custom Block Manually

import { registerCustomBlock } from '@10up/block-renderer-serializer';

registerCustomBlock({
  name: 'my-plugin/custom-block',
  title: 'Custom Block',
  attributes: {
    title: { type: 'string' }
  }
});

Path Utilities

import {
  getThemeNameFromPath,
  getWpContentPathFromTheme
} from '@10up/block-renderer-serializer';

getThemeNameFromPath('/var/www/wp-content/themes/my-theme');
// 'my-theme'

getWpContentPathFromTheme('/var/www/wp-content/themes/my-theme');
// '/var/www/wp-content'

Block Registration

Check Registration Status

import {
  areCoreBlocksRegistered,
  isBlockRegistered,
  getRegisteredBlockNames,
  getBlocksByCategory
} from '@10up/block-renderer-serializer';

// Check if core blocks are registered
if (!areCoreBlocksRegistered()) {
  await registerCoreBlocks();
}

// Check specific block
isBlockRegistered('core/paragraph');  // true
isBlockRegistered('my-plugin/block'); // false (until registered)

// Get all registered block names
const allBlocks = getRegisteredBlockNames();
// ['core/paragraph', 'core/heading', 'core/group', ...]

// Get blocks by category
const categories = getBlocksByCategory();
// Map { 'text' => ['core/paragraph', 'core/heading', ...], ... }

Unregister Blocks

import { unregisterBlock } from '@10up/block-renderer-serializer';

unregisterBlock('core/freeform'); // Remove Classic Editor block

Browser Environment

WordPress block APIs require DOM APIs that don't exist in Node.js. This package sets up a browser-like environment using jsdom.

import {
  setupBrowserEnvironment,
  isBrowserEnvironmentReady
} from '@10up/block-renderer-serializer';

// Check if already initialized
if (!isBrowserEnvironmentReady()) {
  setupBrowserEnvironment();
}

The setup must be called before registering any blocks.

Low-Level APIs

Convert Tree to Blocks

Convert without serializing (useful for inspection):

import { convertTreeToBlocks } from '@10up/block-renderer-serializer';

const blocks = convertTreeToBlocks(tree);
// Returns WordPress block objects

Convert Multiple Roots

import { convertMultipleRootsToBlocks } from '@10up/block-renderer-serializer';

const blocks = convertMultipleRootsToBlocks([tree1, tree2]);

Validate Tree References

Check that all parent/child references are valid:

import { validateTreeReferences } from '@10up/block-renderer-serializer';

const isValid = validateTreeReferences(tree);
// true if all children/parentKey references exist

Serialize Block Objects

import { serializeBlocks } from '@10up/block-renderer-serializer';

const markup = serializeBlocks(blocks);

Pretty Printing

Format block markup for better readability:

import { formatBlockMarkup } from '@10up/block-renderer-serializer';
import type { PrettyPrintOptions } from '@10up/block-renderer-serializer';

const markup = '<!-- wp:paragraph --><p>Hello</p><!-- /wp:paragraph -->';

const formatted = formatBlockMarkup(markup, {
  indentSize: 2,       // Spaces per indent level (default: 2)
  indentChar: ' ',     // Character to use for indentation (default: space)
  newlineBetweenBlocks: true  // Add newlines between top-level blocks (default: true)
});

// Output:
// <!-- wp:paragraph -->
// <p>Hello</p>
// <!-- /wp:paragraph -->

PHP Pattern Transformation

Transform rendered markup into production-ready PHP patterns with internationalization:

import {
  transformToPhpPatternSync,
  escapeText,
  escapeImagePath,
} from '@10up/block-renderer-serializer';

// Transform complete markup
const phpMarkup = transformToPhpPatternSync(markup, {
  textDomain: 'my-theme',
  assetDirectory: 'assets'  // optional, default: 'assets'
});

// Individual escape functions
escapeText('Hello World', 'my-theme');
// "<?php echo esc_html__( 'Hello World', 'my-theme' ); ?>"

escapeImagePath('assets/images/hero.jpg', 'my-theme');
// "<?php echo esc_url( get_template_directory_uri() ); ?>/assets/images/hero.jpg"

What Gets Transformed

| Content Type | Transformation | |-------------|----------------| | Text inside tags (<h2>, <p>, <a>, etc.) | <?php echo esc_html__( 'text', 'domain' ); ?> | | alt attribute | <?php echo esc_attr__( 'text', 'domain' ); ?> | | title attribute | <?php echo esc_attr__( 'text', 'domain' ); ?> | | placeholder attribute | <?php echo esc_attr__( 'text', 'domain' ); ?> | | aria-label attribute | <?php echo esc_attr__( 'text', 'domain' ); ?> | | Local image src (with assets/) | <?php echo esc_url( get_template_directory_uri() ); ?>/assets/... | | Block JSON attributes | Escaped with appropriate function |

Usage with renderBlockTree

import { renderBlockTree } from '@10up/block-renderer-serializer';

// Regular rendering
const markup = renderBlockTree(tree);

// With PHP pattern transformation
const phpMarkup = renderBlockTree(tree, {
  phpPattern: {
    textDomain: 'my-theme',
    assetDirectory: 'assets'
  }
});

Types

ScannedBlock

interface ScannedBlock {
  blockJson: BlockJson;
  source: 'plugin' | 'mu-plugin' | 'theme';
  sourceName: string;
}

SerializeOptions

interface SerializeOptions {
  pretty?: boolean;
}

Complete Exports

Setup Functions

| Function | Description | |----------|-------------| | setupBrowserEnvironment() | Initialize browser environment for Node.js | | isBrowserEnvironmentReady() | Check if environment is initialized |

Rendering Functions

| Function | Description | |----------|-------------| | renderBlockTree(tree, options?) | Render single block tree to markup | | renderBlockTrees(trees, options?) | Render multiple block trees | | renderBlockTemplate(template, options?) | Render PHP-style nested array template to markup | | serializeBlocks(blocks) | Serialize WordPress block objects |

Conversion Functions

| Function | Description | |----------|-------------| | convertTreeToBlocks(tree) | Convert BlockTree to WordPress blocks | | convertTemplateToBlocks(template) | Convert BlockTemplate to WordPress blocks | | convertMultipleRootsToBlocks(trees) | Convert multiple trees to blocks | | validateTreeReferences(tree) | Validate tree has valid references |

Registration Functions

| Function | Description | |----------|-------------| | registerCoreBlocks() | Register all WordPress core blocks | | registerCustomBlock(blockJson, settings?) | Register a custom block | | registerScannedBlocks(blocks) | Register multiple scanned blocks | | unregisterBlock(name) | Unregister a block | | areCoreBlocksRegistered() | Check if core blocks are registered | | isBlockRegistered(name) | Check if specific block is registered | | getRegisteredBlockNames() | Get all registered block names | | getBlocksByCategory() | Get blocks grouped by category |

Block Scanning Functions

| Function | Description | |----------|-------------| | scanBlocksFromWpContent(wpContentPath, themeName?) | Scan for block.json files | | getThemeNameFromPath(themePath) | Extract theme name from path | | getWpContentPathFromTheme(themePath) | Derive wp-content path from theme |

Pretty Printing Functions

| Function | Description | |----------|-------------| | formatBlockMarkup(markup, options?) | Format block markup for readability |

PHP Pattern Transformation Functions

| Function | Description | |----------|-------------| | transformToPhpPatternSync(markup, options) | Transform markup to PHP pattern | | escapeText(text, textDomain) | Escape text with esc_html__ | | escapeImagePath(path, textDomain) | Escape image path with get_template_directory_uri |

Types

| Type | Description | |------|-------------| | ScannedBlock | Block found during scanning | | SerializeOptions | Options for BlockTree serialization | | TemplateSerializeOptions | Options for BlockTemplate serialization | | PrettyPrintOptions | Options for formatBlockMarkup | | BlockTemplate | PHP-style nested array template (from core) | | BlockTemplateEntry | Single entry in a BlockTemplate (from core) |

License

MIT