WP Manifestindependent plugin directory
manifest / utilities / headless-wordpress-nextjs-plugin-bundles

Headless WordPress NEXT.js Plugin Bundles WP Registry grade D. High-severity findings · 12 findingsWP RegistryHigh-severity findings · 12 findingsOpen the reportD

A bundled collection of essential WordPress plugins for headless WordPress with NEXT.js, with automatic installation and activation.

★ 0stars
39release downloads
v1.3.0latest · 5mo ago

Install

Download duplicate-page.zip From the v1.3.0 release on GitHub, May 18, 2026

Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/giolobatski/headless-wordpress-nextjs-plugin-bundles/releases/download/v1.3.0/duplicate-page.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

A comprehensive WordPress plugin bundle manager for headless WordPress with NEXT.js, featuring on-demand plugin downloads from GitHub Releases.

Version: 1.3.0

Features

  • ✅ Installation Wizard - Choose your site type before installation
  • ✅ GitHub Auto-Downloads - Plugins downloaded individually from GitHub Releases
  • ✅ Smart Plugin Selection - Install only the plugins you need
  • ✅ Two Installation Modes - Basic/Portfolio or Shop/Catalogue
  • ✅ Automatic Activation - Plugins are automatically activated after installation
  • ✅ Admin Dashboard - Beautiful admin interface to manage bundled plugins
  • ✅ Status Monitoring - Track which plugins are active/inactive
  • ✅ One-Click Reinstall - Easily reinstall all bundled plugins
  • ✅ Lightweight Plugin - Main plugin is only ~2MB (plugins downloaded on-demand)

How It Works

Instead of bundling all plugins inside the main plugin ZIP (which would make it 100MB+), each plugin is uploaded as an individual ZIP asset to a GitHub Release. The plugin downloads only the ZIPs it needs, one at a time:

  1. The wizard reads includes/plugin-download-config.php to know which ZIPs to fetch
  2. Each plugin ZIP is downloaded directly from the GitHub Release as an asset
  3. WordPress extracts the ZIP and activates the plugin

Asset URL format:

https://github.com/gioLobatski/headless-wordpress-nextjs-plugin-bundles/releases/download/{version}/{plugin-slug}.zip

Installation

  1. Download the plugin from GitHub Releases
  2. Upload to your WordPress wp-content/plugins/ directory
  3. Activate the "Headless WordPress NEXT.js Plugin Bundles" plugin
  4. Complete the Setup Wizard - Choose your site type:
    • Basic/Portfolio Site: Installs 15 plugins (excludes WooCommerce)
    • Shop/Catalogue Site: Installs 17 plugins (including WooCommerce)

The wizard will automatically download all plugins from GitHub! 🚀

Included Plugins

Basic/Portfolio Site (15 plugins):

  • Advanced Custom Fields Pro
  • Classic Editor
  • Duplicate Page
  • iThemes Security Pro
  • Imagify (Image Optimization)
  • Rank Math SEO
  • SVG Support
  • InfiniteWP Client
  • WP Time Capsule (Backup)
  • WP GraphQL
  • WP GraphQL ACF
  • WP GraphQL IDE
  • WP GraphQL Smart Cache
  • WP GraphQL Tax Query

Shop/Catalogue Site (17 plugins):

  • All Basic Site plugins (15)
  • WooCommerce - E-commerce platform
  • Gravity Forms - Form builder

Directory Structure

wp-plugin-bundle/
├── wp-plugin-bundle.php              # Main plugin file
├── includes/
│   ├── class-plugin-installer.php    # Handles plugin installation
│   ├── class-plugin-manager.php      # Manages bundled plugins
│   └── plugin-download-config.php    # GitHub download configuration
├── admin/
│   ├── class-admin.php               # Admin interface
│   └── class-installation-wizard.php # Setup wizard
├── assets/
│   ├── css/
│   │   ├── admin.css                 # Admin styles
│   │   └── wizard.css                # Wizard styles
│   └── js/
│       ├── admin.js                  # Admin JavaScript
│       └── wizard.js                 # Wizard JavaScript
├── bundled-plugins/                  #  Plugin source files (in repository)
└── README.md

Changelog

Version 1.3.0 (Current)

  • Individual Plugin ZIPs - Reverted to per-plugin ZIP downloads from GitHub Release assets (more reliable than source archives)
  • Build Script - New create-plugin-zips.ps1 PowerShell script that auto-packages every folder in bundled-plugins/ into a properly structured ZIP
  • Cleaner Installer - Removed the source-archive code path; one ZIP per plugin keeps installation fast and easy to debug

Version 1.2.0

  • GitHub source archive downloads (deprecated in 1.3.0)
  • Lightweight plugin - Main plugin reduced from 60MB to ~2MB
  • On-demand installation - Plugins downloaded during setup wizard
  • Improved error logging - Detailed debug output for troubleshooting
  • Reset wizard feature
  • Updated admin UI

Version 1.1.0

  • Setup wizard UI improvements
  • Plugin installation progress
  • Admin dashboard enhancements
  • Site type selection
  • Bug fixes

Version 1.0.0

  • Initial release

Development

Releasing a New Version

  1. Add or update plugin folders inside bundled-plugins/ (this folder is gitignored — it's a local working area)
  2. Run the build script to package every folder into a properly structured ZIP:
    .\create-plugin-zips.ps1 -Version v1.3.0

    The output is written to plugin-zips/v1.3.0/ (one <slug>.zip per plugin).

  3. Create a new GitHub release with tag v1.3.0 and upload every ZIP from the output folder as release assets.
  4. Update includes/plugin-download-config.php so each entry's version matches the new tag.
  5. Bump Version: and WP_BUNDLE_VERSION in wp-plugin-bundle.php.

Adding a New Plugin

  1. Drop the plugin folder into bundled-plugins/
  2. Add an entry to the $PluginMap in create-plugin-zips.ps1
  3. Add an entry to includes/plugin-download-config.php
  4. Re-run the build script and re-upload the new ZIP to the release

Support

License

GPL v2 or later


Built for headless WordPress with NEXT.js

Releases

3 releases. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
v1.3.0 latest
May 18, 2026 5mo ago
duplicate-page.zip +9 assets
38
duplicate-page.zip5
classic-editor.zip4
imagify.zip4
gravityforms.zip4
advanced-custom-fields-pro.zip4
iwp-client.zip4
ithemes-security-pro.zip4
seo-by-rank-math.zip4
svg-support.zip4
woocommerce.zip1
May 18, 2026 5mo ago
advanced-custom-fields-pro.zip +9 assets
0
advanced-custom-fields-pro.zip0
classic-editor.zip0
duplicate-page.zip0
gravityforms.zip0
imagify.zip0
includes.zip0
ithemes-security-pro.zip0
iwp-client.zip0
seo-by-rank-math.zip0
svg-support.zip0
May 17, 2026 5mo ago
advanced-custom-fields-pro.zip +9 assets
1
advanced-custom-fields-pro.zip1
classic-editor.zip0
duplicate-page.zip0
gravityforms.zip0
imagify.zip0
includes.zip0
ithemes-security-pro.zip0
iwp-client.zip0
seo-by-rank-math.zip0
svg-support.zip0
D grade

Security

Headless WordPress NEXT.js Plugin Bundles 4.5.9 · audited by WP Registry

High-severity findings.

1 high 2 medium 9 low
Audited release
4.5.9
Findings
12
Worst severity
high
Content hash
562ffb5a4f9013b8613bccc4…

Findings

  • high Missing per-post/type authorization allows edit_others_posts users to clone restricted content

    missing_capability

    dt_duplicate_post_as_draft verifies a post-bound nonce, then authorizes with: (1) manage_options OR edit_others_posts → immediate clone; (2) edit_posts without publish_posts and post_author match → clone as pending; (3) edit_post for that ID AND post_author match → clone with configured status. Branch (1) never calls current_user_can('edit_post', $post_id) or create/publish caps for the target post type. WordPress maps edit_others_posts only to the 'post' type; Editors still receive it while many CPTs (WooCommerce products, LMS courses, etc.) use separate edit_others_{type} caps. Any such user who obtains a nonce can clone arbitrary post IDs: wp_insert_post creates a new post authored as themselves, copies all taxonomies and post meta (including page-builder data), and assigns duplicate_page_options['duplicate_post_status'] (draft|publish|private|pending) without checking publish capability for that type. Practical nonce source: duplicate_page_admin_bar_link adds a 'Duplicate This' admin-bar item with a valid nonce on singular front-end views whenever the post type show_ui, with no current_user_can('edit_post') guard — so viewing a public product/CPT while logged in as Editor yields a working exploit URL. Observable impact: unauthorized read of full post content/meta via the new clone and possible public publication of restricted CPT content under the attacker's account.

    duplicatepage.php:107-135,355-374

    Recommendation

    Authorize solely with current_user_can('edit_post', $post_id) (and optionally current_user_can($pto->cap->create_posts)). Do not treat edit_others_posts as a global allow for all post types. When applying configured post_status=publish, also require the user can publish that type. Gate admin-bar, row-action, and editor UI on the same edit_post check. Reject clone if get_post() is null before capability decisions.

  • medium AJAX handler mk_dp_close_dp_help missing capability check

    missing_capability

    The wp_ajax_mk_dp_close_dp_help action handler only verifies a nonce ('nc_help_desk') but performs no capability check. Any authenticated user (including subscribers) who can obtain or guess the nonce can update the 'mk_fm_close_fm_help_c' option. The nonce is generated in custom_assets() and localized to the script for any admin who loads the settings page, so a leaked nonce could be reused by lower-privileged users. Impact is limited because the only writable option is an internal flag, but the missing capability gate is a real authorization issue and a pattern problem.

    duplicatepage.php:408-425

    Recommendation

    Add a capability check (e.g. current_user_can('manage_options')) before processing the request, since this handler manipulates a site-wide option. Also wrap $_REQUEST['nonce'] access in isset() to avoid PHP notices.

  • medium Duplicate handler grants any edit_others_posts user a clone of any post ID regardless of post type or per-post edit rights

    missing_capability

    admin.php?action=dt_duplicate_post_as_draft&post=<id>&nonce=<n> takes the first branch for any user holding the generic edit_others_posts capability (the Editor role) and never calls current_user_can('edit_post', $post_id) for that path. The nonce is per-post but any logged-in user can mint one: duplicate_page_admin_bar_link() (lines 355-374) emits the link and nonce on the front end for every queried object without a capability check, and wp_create_nonce is not a capability. duplicate_edit_post() then copies post_content, taxonomies, and ALL post meta (including protected _-prefixed keys) into a new post authored by the caller with post_type preserved and post_status taken from the site setting (draft by default; publish/private/pending are admin-selectable). Consequences: (1) an Editor can create posts in post types whose meta-caps they do not hold, e.g. WooCommerce product/shop_coupon (Editors lack edit_products), or any CPT with custom capability_type; (2) where a permissions plugin denies edit_post on specific pages/posts to Editors, the Editor obtains a copy they own and can open, reading restricted content; (3) with duplicate_post_status set to publish, an unreleased draft or private item is republished publicly under the Editor's name. Unauthenticated users and Subscribers/Contributors/Authors are correctly stopped by the later branches (own-post + edit_post checks). Requires Editor or a role granted edit_others_posts, so the blast radius is bounded; impact is bounded further by the default draft status.

    duplicatepage.php:117-135

    Recommendation

    Replace the edit_others_posts shortcut with current_user_can('edit_post', $post_id) plus a check that the user can create posts of that type ($post_type_object->cap->create_posts / edit_others_posts of the type), and refuse post types with show_ui false. Only emit the admin-bar link when current_user_can('edit_post', $post->ID).

  • low Direct-access guard uses && instead of ||

    file_access

    inc/admin-settings.php opens with `if (!defined('ABSPATH') && !current_user_can('manage_options')) { exit; }`. The intent is presumably 'block direct access OR users without manage_options', but the AND short-circuits so the file only exits when BOTH are true. In practice the file is included from duplicate_page_settings() which already gates on current_user_can('manage_options'), so this is defense-in-depth rather than directly exploitable, but the logic is inverted from typical WordPress practice.

    inc/admin-settings.php:1-3

    Recommendation

    Replace && with ||, or split into two distinct guards: `if (!defined('ABSPATH')) { exit; } if (!current_user_can('manage_options')) { exit; }`.

  • low dp_redirect inlines URL into JS string literal without esc_js

    missing_escaping

    dp_redirect() builds a JavaScript snippet with wp_add_inline_script: ` window.location.href="'.$web_url.'" ;`. The URL is sanitized with esc_url_raw, which is appropriate for storage/redirects but does not escape characters that could break out of a JS double-quoted string (e.g. quotes or backslashes are not encoded). All current callers pass hardcoded admin URLs, so it is not exploitable today, but the pattern is unsafe. If any future caller passes user-controlled data, this becomes a stored/reflected XSS sink.

    duplicatepage.php:379-388

    Recommendation

    Use wp_safe_redirect()/wp_redirect() with exit instead of an inline-JS redirect. If client-side redirection is required, use esc_js() on the URL before inlining, or use wp_json_encode() to safely embed the string.

  • low load_help_desk applies the_content filter to admin notice markup

    unnecessary_attack_surface

    load_help_desk() builds a static HTML notice and outputs it via `echo apply_filters('the_content', $mkcontent)`. Running 'the_content' on plugin-generated admin markup invites unrelated content filters (oEmbed, shortcodes, third-party plugin filters) to execute on the admin settings page. The string itself is hardcoded so direct injection is not present, but the use of 'the_content' on admin output is unnecessary and can amplify other plugins' XSS or shortcode-handler bugs into the settings screen.

    duplicatepage.php:392-404

    Recommendation

    Echo the markup directly (it is hardcoded and known-safe). Avoid running the_content on plugin-generated admin output.

  • low Translatable wp_die message built from untrusted error string

    debug_code

    When wp_insert_post() fails, the plugin calls `wp_die(__($new_post_id->get_error_message(),'duplicate-page'))`. Passing a dynamic string to __() is incorrect (the translation lookup never matches and the string-extraction tooling cannot pick it up), and it conceptually treats the WP_Error message as a translation key. The error string is HTML-escaped by wp_die's default handler, so this is not an XSS, but it is a code-quality issue worth flagging in a security review because the pattern can mask future user-controlled data flowing into translation calls.

    duplicatepage.php:189-191

    Recommendation

    Use `wp_die(esc_html($new_post_id->get_error_message()))` and drop the __() call, since the message originates from WordPress core/other plugins and is not a translation key.

  • low Help-desk dismiss AJAX updates an option with nonce only (no capability check)

    missing_capability

    mk_dp_close_dp_help is registered as wp_ajax_mk_dp_close_dp_help (authenticated). It verifies nonce nc_help_desk then unconditionally update_option('mk_fm_close_fm_help_c', 'done'). There is no current_user_can('manage_options') (or similar) check. The nonce is only localized on the settings page loaded under manage_options, so practical abuse is limited to admins or nonce leak/CSRF against an admin who has the settings page nonce; impact is only dismissing the plugin help notice site-wide.

    duplicatepage.php:408-425

    Recommendation

    Require current_user_can('manage_options') after nonce verification before update_option, and use check_ajax_referer for consistency.

  • low Admin bar duplicate link rendered without capability check

    missing_capability

    duplicate_page_admin_bar_link adds a clone URL (including a valid user-specific nonce) for the current singular object whenever the post type has show_ui (or is attachment), without checking current_user_can('edit_post', $post->ID). Users who can see the admin bar but cannot edit the post still receive a functional-looking control; combined with the coarse edit_others_posts authorization in the handler this supplies a ready-made exploit URL for Editors on public CPTs. Alone, for roles that fail the handler, it is UI/nonce disclosure only.

    duplicatepage.php:355-374

    Recommendation

    Only add the admin-bar node when current_user_can('edit_post', $post->ID) (same predicate as dt_duplicate_post_link).

  • low Rating-notice dismiss AJAX (mk_dp_close_dp_help) is nonce-only with no capability check

    missing_capability

    wp_ajax_mk_dp_close_dp_help writes the option mk_fm_close_fm_help_c after a nonce check only. The nc_help_desk nonce is only localized on the manage_options-gated settings page (custom_assets), and nonces are user-bound, so in practice only an admin can reach the write; the effect is hiding a review nag. Defense-in-depth only.

    duplicatepage.php:408-425

    Recommendation

    Add current_user_can('manage_options') before the update_option call.

  • low Admin-bar 'Duplicate This' link and nonce rendered for every logged-in user on the front end without an edit_post check

    missing_capability

    wp_before_admin_bar_render fires on the front end for any logged-in user; the plugin adds a duplicate link with a valid per-post nonce whenever the queried object has a show_ui post type, without checking the viewer can edit that post. The handler still enforces capabilities, so this is only the nonce-provisioning half of the medium finding above; on its own it exposes a dead link to low-privilege users.

    duplicatepage.php:355-374

    Recommendation

    Wrap the add_menu call in current_user_can('edit_post', $post->ID).

  • low Settings template direct-access guard uses && so a direct hit fatals instead of exiting cleanly

    insecure_config

    When ABSPATH is undefined the second operand calls an undefined function and PHP fatals (HTTP 500), which still prevents execution but leaks nothing beyond an error page. The only in-WordPress include path is duplicate_page_settings(), already gated by manage_options.

    inc/admin-settings.php:1-3

    Recommendation

    Use .

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.