PHPackages                             jazzman/wp-performance-shortcode - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. jazzman/wp-performance-shortcode

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

jazzman/wp-performance-shortcode
================================

0.2(6y ago)02MITPHP

Since Mar 5Pushed 6y ago1 watchersCompare

[ Source](https://github.com/Jazz-Man/wp-performance-shortcode)[ Packagist](https://packagist.org/packages/jazzman/wp-performance-shortcode)[ RSS](/packages/jazzman-wp-performance-shortcode/feed)WikiDiscussions develop Synced 1w ago

READMEChangelog (2)Dependencies (4)Versions (4)Used By (0)

Better Shortcode Parser
=======================

[](#better-shortcode-parser)

[Greg Schoppe](https://gschoppe.com)
------------------------------------

[](#greg-schoppe)

### Install:

[](#install)

```
composer require jazzman/wp-performance-shortcode

```

an exploratory WordPress plugin that reimplements Shortcodes, using a three stage process, consisting of a tokenizer, a parser, and a renderer.

> **NOTE:** This is an early-stage proof of concept. It should not be used on any production site.

### Why?

[](#why)

The current shortcode parser uses a single regular expression to parse both self-closing shortcodes, like `[image id="14"]` and wrapping shortcodes, like `[callout]This is some content[/callout]`. Because of the limitations of regular expressions, This prevents WordPress from natively supporting nested shortcodes, such as `[row][col]left content[/col][col]right content[/col][/row]`. Even with complicated systems that nest calls to the `do_shortcode` functionality, the current parser still can't handle self-nested shortcodes, such as `[container type="outer"][container type="inner"]area 1[/container][container type="inner"]area 2[/container][/container]`.

Beyond this, the current shortcode parser is just logically inconsistent. Its specification is defined by the technical limitations of the parser, rather than vice versa.

### How?

[](#how)

Better Shortcode Parser is based on the PHP Block Parser from Gutenberg, which was spearheaded by Dennis Snell. It has been improved with support for matching opening and closing tags, and some shortcode-specific enhancements, like retroactively converting wrapping shortcodes to self-closing shortcodes if no closing tag is found.

### Changes

[](#changes)

If you are currently using a plugin that supports nestable shortcodes it will probably continue to work fine, however there are a couple of changes you should be aware of:

**Better Shortcode Parser renders nested shortcodes from the innermost level outwards**This is necessary to meet the specifications for existing shortcode functions, which expect `$content` to be a string, rather than a tree structure. **Shortcodes in Better Shortcode Parser are context-free.**This means that shortcode rendering functions don't know what other shortcodes the current content is wrapped in.

### Resolving Context issues

[](#resolving-context-issues)

The changes listed above mean that it is no longer possible to give context to nested shortcodes from inside the shortcode render callback itself. There are three possible routes to address this change

#### Modify Shortcodes to be Context-Free (preferred option)

[](#modify-shortcodes-to-be-context-free-preferred-option)

In the case of shortcode structures like

```
[post id="37"]
  [featured-image]
  [title]
  [excerpt]
[/post]

```

It is a much better idea to refactor into a structure like this:

```
[post]
  [featured-image id="37"]
  [title id="37"]
  [excerpt id="37"]
[/post]

```

Shortcodes were not originally designed to be contextual, and keeping them atomic highlights the value of other structures, such as ACF repeaters, private post types, etc.

this sort of structure could also be accomplished via a private "template" post type, such that the main post would have a structure like this:

```
[post id="37" template="12"/]

```

and the accompanying template post would have a structure like this:

```
  [featured-image]
  [title]
  [excerpt]

```

#### Inject context via filter (alternate route)

[](#inject-context-via-filter-alternate-route)

For those use cases where it is unfeasible to refactor existing tropes, or where some confounding factor makes context a necessity, the renderer offers two filters that can be used to provide context to nested shortcodes.

Here is a simple example of how to implement such context-passing for the example structure used above:

```
function shortcode_context_injector( $retval, $tag, $atts, $shortcode ) {
  global $post, $my_backup_post;
  if( $tag == 'post' ) {
    if( !empty( $atts['id'] ) ) {
      $my_backup_post = $post;
      $post = get_post( $atts['id'] );
      setup_postdata( $post );
    } else {
      return ''; // in case of error, prevent all nested shortcodes from being parsed
    }
  }
  return $retval; // this is a short circuit filter. You must return the initial value for parsing to continue.
}

function shortcode_context_restorer( $retval, $tag, $atts, $shortcode ) {
  global $post, $my_backup_post;
  if( $tag == 'post' ) {
    if( !empty( $atts['id'] ) ) {
      $post = $my_backup_post;
      $my_backup_post = null;
      setup_postdata( $post );
    }
  }
  return $retval;
}

add_filter('shortcode_pre_render_nested_content',  'shortcode_context_injector', 10, 4);
add_filter('shortcode_post_render_nested_content', 'shortcode_context_restorer', 10, 4);

```

It should be noted that rather than using a single temporary global, as seen above, a more complete implementation would be implemented with a class property, containing a stack of parent-contexts that could be pushed and popped, as needed. This would enable self-nested shortcode structures with attached context, such as:

```
[context-switcher context="1"]
  [inner-shortcode]
  [context-switcher context="2"]
    [inner-shortcode]
  [/context-switcher]
[/context-switcher]

```

#### Disable nested shortcode rendering (legacy option)

[](#disable-nested-shortcode-rendering-legacy-option)

If you are working with a legacy codebase where refactoring shortcodes to support these means of passing context is impossible, your last resort is to disable the rendering of nested shortcodes, and rely on the existing functionality of your shortcode library to handle these situations.

This can be done globally like so:

```
apply_filters('shortcode_disable_nested_rendering', '__return_true');

```

or on a shortcode-specific basis, like so:

```
function disable_nesting_on_post_shortcodes( $retval, $tag ) {
  $blacklist = array('post', 'column');
  if( in_array( $tag, $blacklist ) ) {
    return true;
  }
  return $retval;
}
apply_filters('shortcode_disable_nested_rendering', 'disable_nesting_on_post_shortcodes', 10, 2);

```

###  Health Score

21

—

LowBetter than 18% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity2

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

2265d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/bde0917196a65a6134bf7aa9d1b3969ae4ef3b92907662fdf68070d033de6843?d=identicon)[Jazz-Man](/maintainers/Jazz-Man)

---

Top Contributors

[![Jazz-Man](https://avatars.githubusercontent.com/u/6892898?v=4)](https://github.com/Jazz-Man "Jazz-Man (1 commits)")

### Embed Badge

![Health badge](/badges/jazzman-wp-performance-shortcode/health.svg)

```
[![Health](https://phpackages.com/badges/jazzman-wp-performance-shortcode/health.svg)](https://phpackages.com/packages/jazzman-wp-performance-shortcode)
```

###  Alternatives

[rainlab/blog-plugin

Blog plugin for October CMS

17257.7k](/packages/rainlab-blog-plugin)[rainlab/builder-plugin

Builder plugin for October CMS

17147.2k1](/packages/rainlab-builder-plugin)[pfefferle/wordpress-activitypub

The ActivityPub protocol is a decentralized social networking protocol based upon the ActivityStreams 2.0 data format.

5671.4k1](/packages/pfefferle-wordpress-activitypub)[civicrm/civicrm-drupal-8

Open source constituent relationship management for non-profits, NGOs and advocacy organizations.

18238.1k2](/packages/civicrm-civicrm-drupal-8)[mediawiki/semantic-glossary

A terminology markup extension with a Semantic MediaWiki back-end

1352.4k](/packages/mediawiki-semantic-glossary)[humanmade/lottie-lite

A lightweight Lottie Animations Extension for WordPress

374.3k](/packages/humanmade-lottie-lite)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
