{"id":20878,"date":"2026-10-27T07:17:00","date_gmt":"2026-10-27T06:17:00","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/?p=20878"},"modified":"2026-10-25T22:34:28","modified_gmt":"2026-10-25T21:34:28","slug":"wordpress-playground-blueprints-for-plugin-previews-and-tests","status":"publish","type":"post","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/","title":{"rendered":"WordPress Playground: Blueprints for plugin previews and tests"},"content":{"rendered":"<p>The Preview button on a WordPress.org plugin page opens a temporary test site in the browser. PHP and the database run through WebAssembly. A file called a Blueprint describes setup, including the PHP version, required plugins and demo values. The same file can serve the directory preview, local Playground runs and the Playground part of automated tests.<\/p>\n<p>The example adds a small demonstration plugin to Contact Form 7. A second test path uses Docker and <code>wp-env<\/code>. That path has a separate <code>.wp-env.json<\/code> and does not execute the Blueprint. The difference is visible: the Blueprint sets an option to 14, while activation without the Blueprint sets 30. This article separates those paths and states exactly what the tests check. Original runtime evidence dates from 29 September 2026; the schema, sources and isolated checks were reviewed again on 25 October 2026.<\/p>\n<h2>Playground, Blueprints and the steps that matter<\/h2>\n<p>In the browser, Playground needs no separate PHP web server. PHP runs as WebAssembly, and the database uses SQLite. The command-line version uses that runtime under Node.js. The V1 Blueprint shown here is a JSON file: <code>landingPage<\/code> selects the first page, <code>preferredVersions<\/code> contains <code>php<\/code> and <code>wp<\/code>, and <code>steps<\/code> lists setup actions in order. The example enables <code>features.networking<\/code> explicitly. <code>$schema<\/code> points to the rules an editor can use to validate the file.<\/p>\n<p>Supported versions depend on the Playground release. The current schema accepts PHP 7.4 and 8.0 through 8.5, among others, plus <code>latest<\/code> and <code>next<\/code>; special legacy entries also exist. Concrete combinations matter more for this example than a supposedly permanent complete list. The matrix therefore names WordPress 6.7.9, 7.0.6 and 7.1.2. These are selected test versions, not a claim about the latest release today.<\/p>\n<p>Three steps prepare this example:<\/p>\n<ul>\n<li><code>installPlugin<\/code> takes a <code>pluginData<\/code> resource, for example <code>{\"resource\": \"wordpress.org\/plugins\", \"slug\": \"contact-form-7\"}<\/code>, a <code>url<\/code> pointing to a ZIP file, or a <code>git:directory<\/code> reference. Activation is controlled by <code>options.activate<\/code>, which belongs inside <code>options<\/code> and not inside <code>pluginData<\/code>. In the source code, a missing <code>activate<\/code> counts as true.<\/li>\n<li><code>login<\/code> needs only <code>username<\/code>, defaulting to <code>admin<\/code>. The <code>password<\/code> field is marked deprecated in the schema.<\/li>\n<li><code>setSiteOptions<\/code> stores the values from <code>options<\/code> as WordPress options. The example sets the site title and demo value 14.<\/li>\n<\/ul>\n<p>Other steps can execute PHP, install language packs or set values in <code>wp-config.php<\/code>. Older examples contain <code>pluginZipFile<\/code> and <code>phpExtensionBundles<\/code>; the current schema marks these as deprecated or unused. Dependency order is written explicitly in <code>steps<\/code>. A publicly reachable Blueprint can open through <code>?blueprint-url=<\/code>; a JSON fragment after <code>https:\/\/playground.wordpress.net\/#<\/code> is another option. The diagram separates the shared Playground Blueprint from the independent Docker configuration.<\/p>\n<figure class=\"lw-diagram\">\n<img loading=\"lazy\" decoding=\"async\" alt=\"The Blueprint serves the preview, local Playground runs and Playground CI. Docker separately uses .wp-env.json and the example\u2019s default 30 rather than demo value 14.\" height=\"580\" src=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/diagrams\/playground-reviewed-20878-en-20261025.png\" width=\"1120\"\/>\n<figcaption>The Blueprint serves the preview, local Playground runs and Playground CI. Docker separately uses .wp-env.json and the example\u2019s default 30 rather than demo value 14.<\/figcaption>\n<\/figure>\n<h2>The Blueprint for the directory preview<\/h2>\n<p>A public preview requires a valid file at <code>assets\/blueprints\/blueprint.json<\/code> in the SVN directory. A committer, meaning an account allowed to write to that plugin repository, must also set the preview to \u201cpublic\u201d under Advanced. Before that, Test Preview is visible only to those accounts. An automatically generated test Blueprint can provide a starting point.<\/p>\n<p>The inspected importer accepts matching Blueprint files from this folder up to 102,400 bytes each and retains at most ten. Normal previews use <code>blueprint.json<\/code>. Invalid JSON is discarded. For this example plugin, the importer appends installation of the plugin\u2019s own slug if no matching step exists, and forces activation. Matching ZIP URLs on <code>downloads.wordpress.org<\/code> are rewritten as slug resources. A version specified in the filename can therefore be lost. Import does not simply pass the file through unchanged.<\/p>\n<p>The Blueprint therefore installs Contact Form 7 and sets demo values, but does not download the add-on itself. The directory appends that download. The local Playground CLI instead mounts the working folder and activates the add-on after the Blueprint steps. A separate download step could fail for an unpublished plugin or replace the mounted working folder through the default <code>overwrite<\/code> option. The Docker path independently uses its own plugin list.<\/p>\n<p>In this Git project, the file is stored at <code>.wordpress-org\/blueprints\/blueprint.json<\/code>. That folder name is a convention of the example. The release process then copies the file to <code>assets\/blueprints\/<\/code> in SVN. A leading dot does not automatically exclude a folder from a plugin ZIP; the packaging process must actually apply that exclusion.<\/p>\n<figure style=\"--cbb-digits:2\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"json\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">JSON<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-json\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\">{\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"$schema\"<\/span>: <span class=\"cbb-string\">\"https:\/\/playground.wordpress.net\/blueprint-schema.json\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"landingPage\"<\/span>: <span class=\"cbb-string\">\"\/wp-admin\/plugins.php\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"preferredVersions\"<\/span>: {\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-attr\">\"php\"<\/span>: <span class=\"cbb-string\">\"8.3\"<\/span>,\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-attr\">\"wp\"<\/span>: <span class=\"cbb-string\">\"latest\"<\/span>\n<\/span><span class=\"cbb-line\">\t},\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"features\"<\/span>: {\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-attr\">\"networking\"<\/span>: <span class=\"cbb-constant\">true<\/span>\n<\/span><span class=\"cbb-line\">\t},\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"steps\"<\/span>: &#91;\n<\/span><span class=\"cbb-line\">\t\t{\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"step\"<\/span>: <span class=\"cbb-string\">\"login\"<\/span>,\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"username\"<\/span>: <span class=\"cbb-string\">\"admin\"<\/span>\n<\/span><span class=\"cbb-line\">\t\t},\n<\/span><span class=\"cbb-line\">\t\t{\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"step\"<\/span>: <span class=\"cbb-string\">\"installPlugin\"<\/span>,\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"pluginData\"<\/span>: {\n<\/span><span class=\"cbb-line\">\t\t\t\t<span class=\"cbb-attr\">\"resource\"<\/span>: <span class=\"cbb-string\">\"wordpress.org\/plugins\"<\/span>,\n<\/span><span class=\"cbb-line\">\t\t\t\t<span class=\"cbb-attr\">\"slug\"<\/span>: <span class=\"cbb-string\">\"contact-form-7\"<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t},\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"options\"<\/span>: {\n<\/span><span class=\"cbb-line\">\t\t\t\t<span class=\"cbb-attr\">\"activate\"<\/span>: <span class=\"cbb-constant\">true<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t}\n<\/span><span class=\"cbb-line\">\t\t},\n<\/span><span class=\"cbb-line\">\t\t{\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"step\"<\/span>: <span class=\"cbb-string\">\"setSiteOptions\"<\/span>,\n<\/span><span class=\"cbb-line\">\t\t\t<span class=\"cbb-attr\">\"options\"<\/span>: {\n<\/span><span class=\"cbb-line\">\t\t\t\t<span class=\"cbb-attr\">\"blogname\"<\/span>: <span class=\"cbb-string\">\"LW Form Archive Demo\"<\/span>,\n<\/span><span class=\"cbb-line\">\t\t\t\t<span class=\"cbb-attr\">\"lw_form_archive_retention_days\"<\/span>: <span class=\"cbb-number\">14<\/span>\n<\/span><span class=\"cbb-line\">\t\t\t}\n<\/span><span class=\"cbb-line\">\t\t}\n<\/span><span class=\"cbb-line\">\t&#93;\n<\/span><span class=\"cbb-line\">}<\/span><\/code><\/pre><\/figure>\n<p>The landing page is the plugin list. WordPress 6.5 and later show the dependency between the add-on and Contact Form 7 there. The older Handbook example contains outdated Blueprint fields and older runtime versions. The Blueprint here uses <code>pluginData<\/code> and passed validation against the schema retrieved on 25 October 2026.<\/p>\n<h2>Requires Plugins: declaring the dependency<\/h2>\n<p>Since WordPress 6.5, the <code>Requires Plugins<\/code> header names required plugins. The expected values are comma-separated WordPress.org slugs such as <code>contact-form-7<\/code>, not file paths. A slug is the plugin\u2019s short directory identifier. Invalid spellings can disappear from the dependency list. The following add-on demonstrates only this header and a stored option. Despite the name Form Archive, the code does not archive forms or delete data after the specified number of days.<\/p>\n<figure style=\"--cbb-digits:2\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"php\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">PHP<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-php\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"><span class=\"cbb-meta\">&lt;?php<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\">\/**<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Plugin Name:       LW Form Archive<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Description:       Example add-on for Contact Form 7 (dependency and Blueprint demo).<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Version:           1.0.0<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Requires at least: 6.7<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Requires PHP:      7.4<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Requires Plugins:  contact-form-7<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Author:            Lukas Wojcik<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * License:           GPL-2.0-or-later<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * License URI:       https:\/\/www.gnu.org\/licenses\/gpl-2.0.html<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Text Domain:       lw-form-archive<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> *<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * @package LW_Form_Archive<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> *\/<\/span>\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"><span class=\"cbb-function\">defined<\/span>( <span class=\"cbb-string\">'ABSPATH'<\/span> ) || exit;\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\">\/**<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Stores the default retention period on activation.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> *<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * add_option() leaves an existing value untouched, so a value that a<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Blueprint wrote earlier with setSiteOptions survives activation.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> *\/<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">function<\/span> <span class=\"cbb-function\">lw_form_archive_activate<\/span>() {\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-function\">add_option<\/span>( <span class=\"cbb-string\">'lw_form_archive_retention_days'<\/span>, <span class=\"cbb-number\">30<\/span> );\n<\/span><span class=\"cbb-line\">}\n<\/span><span class=\"cbb-line\"><span class=\"cbb-function\">register_activation_hook<\/span>( __FILE__, <span class=\"cbb-string\">'lw_form_archive_activate'<\/span> );\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\">\/**<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * Returns the retention period in days.<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> *<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> * @return int<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-comment\"> *\/<\/span>\n<\/span><span class=\"cbb-line\"><span class=\"cbb-keyword\">function<\/span> <span class=\"cbb-function\">lw_form_archive_retention_days<\/span>() {\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-keyword\">return<\/span> <span class=\"cbb-function\">absint<\/span>( <span class=\"cbb-function\">get_option<\/span>( <span class=\"cbb-string\">'lw_form_archive_retention_days'<\/span>, <span class=\"cbb-number\">30<\/span> ) );\n<\/span><span class=\"cbb-line\">}<\/span><\/code><\/pre><\/figure>\n<p>Plugin administration checks dependencies during installation and activation. A required plugin must be present and active before the add-on can activate. The interface also prevents certain deletion and deactivation actions while dependent plugins would be affected. This does not protect files against removal through FTP. WP-CLI can also install a plugin without dependencies initially; activation can then fail.<\/p>\n<p><code>activate_plugin()<\/code> is the relevant function for this flow. WordPress checks requirements for WordPress, PHP and dependent plugins. A failure produces a <code>WP_Error<\/code>. Playground then checks whether the plugin actually became active. Missing Contact Form 7 or a downloaded version requiring a newer environment makes setup fail. A successful run initially proves only that this setup completed.<\/p>\n<p><code>add_option()<\/code> leaves an existing option unchanged. The Blueprint\u2019s value of 14 therefore survives activation; a fresh installation without the Blueprint receives the default 30. These are example values with no deletion routine behind them. <a href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/activation-deactivation-uninstall-the-plugin-lifecycle\/\">A separate article in this series<\/a> explains the lifecycle. The dependency header specifies neither minimum versions of the other plugin nor loading order. Code needing a particular class or function must also check availability.<\/p>\n<h2>Local runs with the Playground CLI<\/h2>\n<p>The examples pin <code>@wp-playground\/cli@3.1.56<\/code>, the release used for the documented original tests. <code>start<\/code> keeps a development site between runs. <code>server<\/code> starts local web access; <code>run-blueprint<\/code> performs setup without a persistent web server. Export and individual PHP commands are also available. Fresh default runs are easier to compare than an already modified development site. Mounted working folders still contain real local files and are not automatically reset.<\/p>\n<figure style=\"--cbb-digits:2\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"javascript\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">JavaScript<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-javascript\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"># <span class=\"cbb-type\">Run<\/span> <span class=\"cbb-keyword\">from<\/span> the plugin <span class=\"cbb-function\">folder<\/span> (the folder name must match the slug).\n<\/span><span class=\"cbb-line\">cd lw-form-archive\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"># <span class=\"cbb-type\">Interactive<\/span>: fresh site on http:<span class=\"cbb-comment\">\/\/127.0.0.1:9400 with the working copy mounted.<\/span>\n<\/span><span class=\"cbb-line\"># PHP and <span class=\"cbb-type\">WordPress<\/span> come <span class=\"cbb-keyword\">from<\/span> preferredVersions <span class=\"cbb-keyword\">in<\/span> the <span class=\"cbb-function\">Blueprint<\/span> (<span class=\"cbb-number\">8.3<\/span>, latest);\n<\/span><span class=\"cbb-line\"># --php and --wp have no effect together <span class=\"cbb-keyword\">with<\/span> a <span class=\"cbb-type\">Blueprint<\/span> <span class=\"cbb-function\">file<\/span> (CLI 3.1.56).\n<\/span><span class=\"cbb-line\">npx @wp-playground\/cli@3.1.56 server --auto-mount \\\n<\/span><span class=\"cbb-line\">  --blueprint=.wordpress-org\/blueprints\/blueprint.json\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\"># <span class=\"cbb-type\">Smoke<\/span> test on the oldest tested <span class=\"cbb-function\">branch<\/span> (patched release): a copy <span class=\"cbb-keyword\">with<\/span> other preferredVersions,\n<\/span><span class=\"cbb-line\"># run without a server, non-zero exit code on failure.\n<\/span><span class=\"cbb-line\">node -e \"<span class=\"cbb-keyword\">const<\/span> fs = <span class=\"cbb-function\">require<\/span>(<span class=\"cbb-string\">'fs'<\/span>);\n<\/span><span class=\"cbb-line\">  <span class=\"cbb-keyword\">const<\/span> b = JSON.<span class=\"cbb-function\">parse<\/span>(fs.<span class=\"cbb-function\">readFileSync<\/span>(<span class=\"cbb-string\">'.wordpress-org\/blueprints\/blueprint.json'<\/span>, <span class=\"cbb-string\">'utf8'<\/span>));\n<\/span><span class=\"cbb-line\">  b.preferredVersions = { php: <span class=\"cbb-string\">'7.4'<\/span>, wp: <span class=\"cbb-string\">'6.7.9'<\/span> };\n<\/span><span class=\"cbb-line\">  fs.<span class=\"cbb-function\">writeFileSync<\/span>(<span class=\"cbb-string\">'\/tmp\/blueprint-min.json'<\/span>, JSON.<span class=\"cbb-function\">stringify<\/span>(b));\"\n<\/span><span class=\"cbb-line\">npx @wp-playground\/cli@3.1.56 run-blueprint --auto-mount \\\n<\/span><span class=\"cbb-line\">  --blueprint=\/tmp\/blueprint-min.json<\/span><\/code><\/pre><\/figure>\n<p>With no additional value, <code>--auto-mount<\/code> mounts the current folder. The CLI detects the plugin header and uses the folder name under <code>\/wordpress\/wp-content\/plugins\/<\/code>. Activation follows the Blueprint steps. The folder is therefore named <code>lw-form-archive<\/code>, matching the slug. A mount is not a read-only snapshot; tests belong in a disposable working copy.<\/p>\n<p>In the documented CLI 3.1.56 test, <code>--wp<\/code> and <code>--php<\/code> did not take the expected precedence when a Blueprint file was supplied. The runtime used <code>preferredVersions<\/code>, even though a startup message showed other values. The example therefore writes a copy with the intended combination for each test. A failed setup ends <code>run-blueprint<\/code> with an error status. This short setup check does not test form processing. Browser tests through <code>?blueprint-url=<\/code> also need a reachable file and appropriate CORS permission for retrieval.<\/p>\n<h2>wp-env: fixed versions in Docker<\/h2>\n<p>The second path uses <code>@wordpress\/env@11.16.0<\/code> with Docker and MariaDB. This is useful for database queries whose behavior can differ from SQLite. <code>.wp-env.json<\/code> names the WordPress version, PHP and plugins to install. <code>WP_ENV_CORE<\/code> and <code>WP_ENV_PHP_VERSION<\/code> supply the matrix versions. The Blueprint is not loaded, so its demo values are initially absent.<\/p>\n<figure style=\"--cbb-digits:2\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"json\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">JSON<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-json\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\">{\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"$schema\"<\/span>: <span class=\"cbb-string\">\"https:\/\/schemas.wp.org\/trunk\/wp-env.json\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"core\"<\/span>: <span class=\"cbb-string\">\"WordPress\/WordPress#7.1.2\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"phpVersion\"<\/span>: <span class=\"cbb-string\">\"8.3\"<\/span>,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"plugins\"<\/span>: &#91;\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-string\">\"https:\/\/downloads.wordpress.org\/plugin\/contact-form-7.6.1.7.zip\"<\/span>,\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-string\">\".\"<\/span>\n<\/span><span class=\"cbb-line\">\t&#93;,\n<\/span><span class=\"cbb-line\">\t<span class=\"cbb-attr\">\"config\"<\/span>: {\n<\/span><span class=\"cbb-line\">\t\t<span class=\"cbb-attr\">\"WP_DEBUG_LOG\"<\/span>: <span class=\"cbb-constant\">true<\/span>\n<\/span><span class=\"cbb-line\">\t}\n<\/span><span class=\"cbb-line\">}<\/span><\/code><\/pre><\/figure>\n<p>The inspected wp-env release activates plugins in the listed order. Contact Form 7 therefore comes before the add-on. The versioned ZIP URL specifies 6.1.7. The parser removes the version suffix and obtains the expected <code>contact-form-7<\/code> folder. The <code>.latest-stable.zip<\/code> form produces a different result in that parser and is not an equivalent replacement. A fixed ZIP reduces changes in the dependency but guarantees neither download availability nor identical results for the whole environment.<\/p>\n<p><code>WordPress\/WordPress#7.1.2<\/code> identifies an exact tag in the GitHub mirror. A <code>7.1<\/code> tag does not automatically mean 7.1.2. Inside Docker, <code>wp-env run cli<\/code> starts the command supplied afterwards. The alternative Playground runtime uses SQLite and, according to the current documentation, does not support <code>run<\/code>. The workflow below therefore explicitly stays with Docker.<\/p>\n<h2>A CI matrix across WordPress and PHP versions<\/h2>\n<p>A test matrix repeats a procedure with several combinations. Both jobs use exactly WordPress 6.7.9\/PHP 7.4, WordPress 7.0.6\/PHP 8.2 and WordPress 7.1.2\/PHP 8.4. The first row checks a patched release of the oldest selected branch, not every earlier 6.7 release. Playground receives the values through a Blueprint copy; wp-env receives the same version numbers through environment variables. The example also names the <code>v7<\/code> action tags and Node 24 explicitly.<\/p>\n<figure style=\"--cbb-digits:2\" class=\"cbb cbb--wrap cbb--numbers wp-block-code-beautifier-block-code\" data-lang=\"javascript\"><figcaption class=\"cbb-head\"><span class=\"cbb-lang\">JavaScript<\/span><span class=\"cbb-actions\"><button type=\"button\" class=\"cbb-button cbb-button--wrap\" data-cbb-action=\"wrap\" aria-pressed=\"true\" title=\"Wrap long lines\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M2 4h12M2 8h9.5a2.5 2.5 0 0 1 0 5H8m0 0 1.5-1.5M8 13l1.5 1.5M2 12h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><span class=\"cbb-sr\">Wrap long lines<\/span><\/button><button type=\"button\" class=\"cbb-button cbb-button--copy\" data-cbb-action=\"copy\" data-cbb-done=\"Copied\" data-cbb-failed=\"Press Ctrl+C to copy\"><svg aria-hidden=\"true\" focusable=\"false\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8.5\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><path d=\"M10.5 3.5V3A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v6.5A1.5 1.5 0 0 0 4 11h.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"\/><\/svg><span class=\"cbb-button-label\" aria-live=\"polite\">Copy<\/span><\/button><\/span><\/figcaption><pre class=\"cbb-pre\" tabindex=\"0\"><code class=\"cbb-code language-javascript\" style=\"counter-reset:cbb-line 0;\"><span class=\"cbb-line\"># .github\/workflows\/compatibility.yml\n<\/span><span class=\"cbb-line\">name: <span class=\"cbb-type\">Compatibility<\/span>\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\">on:\n<\/span><span class=\"cbb-line\">  push:\n<\/span><span class=\"cbb-line\">    branches: &#91; trunk &#93;\n<\/span><span class=\"cbb-line\">  pull_request:\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\">permissions:\n<\/span><span class=\"cbb-line\">  contents: read\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\">jobs:\n<\/span><span class=\"cbb-line\">  playground:\n<\/span><span class=\"cbb-line\">    name: <span class=\"cbb-type\">Playground<\/span> \/ WP ${{ matrix.wp }} \/ PHP ${{ matrix.php }}\n<\/span><span class=\"cbb-line\">    runs-on: ubuntu-latest\n<\/span><span class=\"cbb-line\">    strategy:\n<\/span><span class=\"cbb-line\">      fail-fast: <span class=\"cbb-constant\">false<\/span>\n<\/span><span class=\"cbb-line\">      matrix:\n<\/span><span class=\"cbb-line\">        include:\n<\/span><span class=\"cbb-line\">          - { wp: <span class=\"cbb-string\">'6.7.9'<\/span>, php: <span class=\"cbb-string\">'7.4'<\/span> }\n<\/span><span class=\"cbb-line\">          - { wp: <span class=\"cbb-string\">'7.0.6'<\/span>, php: <span class=\"cbb-string\">'8.2'<\/span> }\n<\/span><span class=\"cbb-line\">          - { wp: <span class=\"cbb-string\">'7.1.2'<\/span>, php: <span class=\"cbb-string\">'8.4'<\/span> }\n<\/span><span class=\"cbb-line\">    steps:\n<\/span><span class=\"cbb-line\">      # <span class=\"cbb-type\">Check<\/span> out into a folder named like the slug: auto-mount uses the folder name.\n<\/span><span class=\"cbb-line\">      - uses: actions\/checkout@v7\n<\/span><span class=\"cbb-line\">        <span class=\"cbb-keyword\">with<\/span>:\n<\/span><span class=\"cbb-line\">          path: lw-form-archive\n<\/span><span class=\"cbb-line\">      - uses: actions\/setup-node@v7\n<\/span><span class=\"cbb-line\">        <span class=\"cbb-keyword\">with<\/span>:\n<\/span><span class=\"cbb-line\">          node-version: <span class=\"cbb-number\">24<\/span>\n<\/span><span class=\"cbb-line\">      # <span class=\"cbb-type\">For<\/span> a <span class=\"cbb-type\">Blueprint<\/span> file the CLI ignores --wp and --<span class=\"cbb-function\">php<\/span> (CLI 3.1.56),\n<\/span><span class=\"cbb-line\">      # so the matrix versions go into preferredVersions <span class=\"cbb-keyword\">of<\/span> a copy.\n<\/span><span class=\"cbb-line\">      - name: <span class=\"cbb-type\">Write<\/span> the matrix versions into a copy <span class=\"cbb-keyword\">of<\/span> the <span class=\"cbb-type\">Blueprint<\/span>\n<\/span><span class=\"cbb-line\">        working-directory: lw-form-archive\n<\/span><span class=\"cbb-line\">        run: &gt;-\n<\/span><span class=\"cbb-line\">          node -e \"<span class=\"cbb-keyword\">const<\/span> fs = <span class=\"cbb-function\">require<\/span>(<span class=\"cbb-string\">'fs'<\/span>);\n<\/span><span class=\"cbb-line\">          <span class=\"cbb-keyword\">const<\/span> b = JSON.<span class=\"cbb-function\">parse<\/span>(fs.<span class=\"cbb-function\">readFileSync<\/span>(<span class=\"cbb-string\">'.wordpress-org\/blueprints\/blueprint.json'<\/span>, <span class=\"cbb-string\">'utf8'<\/span>));\n<\/span><span class=\"cbb-line\">          b.preferredVersions = { php: <span class=\"cbb-string\">'${{ matrix.php }}'<\/span>, wp: <span class=\"cbb-string\">'${{ matrix.wp }}'<\/span> };\n<\/span><span class=\"cbb-line\">          fs.<span class=\"cbb-function\">writeFileSync<\/span>(process.env.RUNNER_TEMP + <span class=\"cbb-string\">'\/blueprint.json'<\/span>, JSON.<span class=\"cbb-function\">stringify<\/span>(b));\"\n<\/span><span class=\"cbb-line\">      - name: <span class=\"cbb-type\">Run<\/span> the <span class=\"cbb-type\">Blueprint<\/span> against the working copy\n<\/span><span class=\"cbb-line\">        working-directory: lw-form-archive\n<\/span><span class=\"cbb-line\">        run: &gt;-\n<\/span><span class=\"cbb-line\">          npx --yes @wp-playground\/cli@3.1.56 run-blueprint\n<\/span><span class=\"cbb-line\">          --auto-mount\n<\/span><span class=\"cbb-line\">          --blueprint=${{ runner.temp }}\/blueprint.json\n<\/span><span class=\"cbb-line\">\n<\/span><span class=\"cbb-line\">  wp-env:\n<\/span><span class=\"cbb-line\">    name: wp-env \/ WP ${{ matrix.wp }} \/ PHP ${{ matrix.php }}\n<\/span><span class=\"cbb-line\">    runs-on: ubuntu-latest\n<\/span><span class=\"cbb-line\">    strategy:\n<\/span><span class=\"cbb-line\">      fail-fast: <span class=\"cbb-constant\">false<\/span>\n<\/span><span class=\"cbb-line\">      matrix:\n<\/span><span class=\"cbb-line\">        include:\n<\/span><span class=\"cbb-line\">          - { wp: <span class=\"cbb-string\">'6.7.9'<\/span>, php: <span class=\"cbb-string\">'7.4'<\/span> }\n<\/span><span class=\"cbb-line\">          - { wp: <span class=\"cbb-string\">'7.0.6'<\/span>, php: <span class=\"cbb-string\">'8.2'<\/span> }\n<\/span><span class=\"cbb-line\">          - { wp: <span class=\"cbb-string\">'7.1.2'<\/span>, php: <span class=\"cbb-string\">'8.4'<\/span> }\n<\/span><span class=\"cbb-line\">    env:\n<\/span><span class=\"cbb-line\">      # <span class=\"cbb-type\">Override<\/span> core and phpVersion <span class=\"cbb-keyword\">from<\/span> .wp-env.json <span class=\"cbb-keyword\">for<\/span> <span class=\"cbb-constant\">this<\/span> matrix cell.\n<\/span><span class=\"cbb-line\">      WP_ENV_CORE: <span class=\"cbb-type\">WordPress<\/span>\/<span class=\"cbb-type\">WordPress<\/span>#${{ matrix.wp }}\n<\/span><span class=\"cbb-line\">      WP_ENV_PHP_VERSION: ${{ matrix.php }}\n<\/span><span class=\"cbb-line\">    steps:\n<\/span><span class=\"cbb-line\">      - uses: actions\/checkout@v7\n<\/span><span class=\"cbb-line\">        <span class=\"cbb-keyword\">with<\/span>:\n<\/span><span class=\"cbb-line\">          path: lw-form-archive\n<\/span><span class=\"cbb-line\">      - uses: actions\/setup-node@v7\n<\/span><span class=\"cbb-line\">        <span class=\"cbb-keyword\">with<\/span>:\n<\/span><span class=\"cbb-line\">          node-version: <span class=\"cbb-number\">24<\/span>\n<\/span><span class=\"cbb-line\">      - name: <span class=\"cbb-type\">Install<\/span> wp-env\n<\/span><span class=\"cbb-line\">        run: npm install -g @wordpress\/env@11.16.0\n<\/span><span class=\"cbb-line\">      - name: <span class=\"cbb-type\">Start<\/span> <span class=\"cbb-function\">WordPress<\/span> (activates the plugins <span class=\"cbb-keyword\">from<\/span> .wp-env.json)\n<\/span><span class=\"cbb-line\">        working-directory: lw-form-archive\n<\/span><span class=\"cbb-line\">        run: wp-env start\n<\/span><span class=\"cbb-line\">      - name: <span class=\"cbb-type\">Check<\/span> activation and the <span class=\"cbb-keyword\">default<\/span> option\n<\/span><span class=\"cbb-line\">        working-directory: lw-form-archive\n<\/span><span class=\"cbb-line\">        run: |\n<\/span><span class=\"cbb-line\">          wp-env run cli wp plugin is-active contact-form-7\n<\/span><span class=\"cbb-line\">          wp-env run cli wp plugin is-active lw-form-archive\n<\/span><span class=\"cbb-line\">          wp-env run cli wp eval <span class=\"cbb-string\">'if (lw_form_archive_retention_days() !== 30) { WP_CLI::error(\"Unexpected default retention value\"); }'<\/span><\/span><\/code><\/pre><\/figure>\n<p>The Playground job checks setup with the stable Contact Form 7 version currently supplied by the directory. The Docker job uses the fixed 6.1.7 release instead. That job also checks whether both plugins are active and the example function returns the default value 30. <code>wp plugin is-active<\/code> returns an error status for an inactive plugin. These are focused activation and option checks, not comprehensive functional tests or a promise of identical results.<\/p>\n<p>The historical tests explain the value of both paths: Contact Form 7 6.1.7 used the older requirements, whereas the development version 6.2-rc at that time required newer WordPress and PHP releases. A future stable dependency with higher requirements can therefore fail the Playground job while the Docker job with its older fixed dependency still works. The signal concerns those tested combinations first. Correct behavior of the actual add-on needs additional tests.<\/p>\n<p>A PR preview can also be configured with <code>WordPress\/action-wp-playground-pr-preview@v4<\/code>. Actual PHPUnit tests require a test suite in the project. A complete invocation includes the program, for example <code>wp-env run cli --env-cwd=wp-content\/plugins\/lw-form-archive phpunit<\/code>. Naming only the container and working folder does not run a test. <a href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/plugin-check-locally-and-in-ci-findings-before-the-review\/\">A separate article in this series<\/a> covers static checks with Plugin Check.<\/p>\n<h2>Limits and open points<\/h2>\n<ul>\n<li>The historical report records three successful local Playground cells with CLI 3.1.56. A GitHub or Docker run was not established. Checks of this revised version covered JSON, YAML, shell and PHP syntax, four Blueprint combinations against the current schema, and option values 14 and 30 with isolated substitute functions. The added Docker checks were not executed in a real WordPress environment.<\/li>\n<li>Playground uses SQLite. Plugins with their own tables or hand-written SQL can behave differently from MySQL; for tables of their own, the wp-env job is the more meaningful check. <a href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/web-development\/wordpress-database-performance-limits-of-wp_postmeta-and-how-to-build-custom-tables\/\">The article on performance limits of <code>wp_postmeta<\/code> and custom tables<\/a> covers that topic.<\/li>\n<li>The schema does not accept a <code>version<\/code> field for <code>wordpress.org\/plugins<\/code> resources (only <code>resource<\/code> and <code>slug<\/code>), and for a plain slug the source code builds the download URL with <code>latest-stable<\/code>. A slug that already ends in <code>.zip<\/code>, such as <code>contact-form-7.6.1.7.zip<\/code>, is used unchanged as the file name: in a test with version 6.1.5, the Playground CLI installed exactly that version, and the directory importer only rewrites <code>url<\/code> resources. Whether the directory preview accepts this form has not been tested.<\/li>\n<li>Fixed tool versions reduce changes between runs. Action tags, Node releases, images, downloads and external services can still change. A package lockfile and recorded runtime versions improve traceability, but do not guarantee identical results.<\/li>\n<li>The directory imports up to ten Blueprint files but uses only <code>blueprint.json<\/code>. The handbook describes support for several files as a possible future step, without a date.<\/li>\n<li>The folder name <code>.wordpress-org\/blueprints\/<\/code> and the transfer to SVN assets are conventions of this article; the SVN structure itself is covered by <a href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/readme-txt-header-and-svn-how-wordpress-org-reads-a-plugin\/\">a separate article in this series<\/a>.<\/li>\n<\/ul>\n<div class=\"lw-faq\">\n<h2>Questions and answers<\/h2>\n<h3>Why does the Preview button not appear although blueprint.json is committed to SVN?<\/h3>\n<p>A valid file at <code>assets\/blueprints\/blueprint.json<\/code> first enables the internal test for committers. The public button also requires the \u201cpublic\u201d setting under Advanced. The inspected importer rejects invalid JSON and files over 102,400 bytes. A successful import still does not replace testing the Blueprint at runtime.<\/p>\n<h3>Can a dependency be pinned to a specific version in the directory preview?<\/h3>\n<p>The plain slug <code>contact-form-7<\/code> downloads the stable release through <code>wordpress.org\/plugins<\/code>. An additional <code>version<\/code> field is not allowed for this resource in the inspected schema. The directory importer can also rewrite versioned download URLs into a plain slug.<\/p>\n<p>A historical CLI test confirmed a ZIP filename used as the slug, such as <code>contact-form-7.6.1.5.zip<\/code>. This loaded a specific version. That workaround was not tested in the public directory preview and is not a guarantee for that environment. The Docker test instead uses an explicitly versioned ZIP URL in <code>.wp-env.json<\/code>.<\/p>\n<h3>Does Requires Plugins replace class_exists() checks in the plugin code?<\/h3>\n<p>No. The header helps WordPress check whether a required plugin is installed and active. It specifies neither a minimum dependency version nor a fixed loading order. A suitable availability check is still needed before calling a particular class or function.<\/p>\n<\/div>\n<div class=\"lw-quellen\">\n<h2>Sources<\/h2>\n<ul>\n<li><a href=\"https:\/\/developer.wordpress.org\/plugins\/wordpress-org\/previews-and-blueprints\/\" rel=\"noopener noreferrer\" target=\"_blank\">Plugin Handbook: Previews and Blueprints<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/playground\/blueprints\/data-format\" rel=\"noopener noreferrer\" target=\"_blank\">Playground: Blueprint data format<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/playground\/blueprints\/steps\/\" rel=\"noopener noreferrer\" target=\"_blank\">Playground: Blueprint steps<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/playground.wordpress.net\/blueprint-schema.json\" rel=\"noopener noreferrer\" target=\"_blank\">Blueprint JSON schema<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/playground\/developers\/local-development\/wp-playground-cli\/\" rel=\"noopener noreferrer\" target=\"_blank\">Playground CLI<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/playground\/developers\/apis\/query-api\/\" rel=\"noopener noreferrer\" target=\"_blank\">Playground Query API<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-playground\" rel=\"noopener noreferrer\" target=\"_blank\">GitHub: WordPress\/wordpress-playground (CLI, steps, resources, WordPress version list)<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress.org\/tree\/trunk\/wordpress.org\/public_html\/wp-content\/plugins\/plugin-directory\" rel=\"noopener noreferrer\" target=\"_blank\">GitHub: WordPress.org plugin directory source (Blueprint import and endpoint)<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/block-editor\/reference-guides\/packages\/packages-env\/\" rel=\"noopener noreferrer\" target=\"_blank\">@wordpress\/env documentation<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/tree\/trunk\/packages\/env\" rel=\"noopener noreferrer\" target=\"_blank\">GitHub: packages\/env source<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/make.wordpress.org\/core\/2024\/03\/05\/introducing-plugin-dependencies-in-wordpress-6-5\/\" rel=\"noopener noreferrer\" target=\"_blank\">Make WordPress Core: Introducing Plugin Dependencies in WordPress 6.5<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/trunk\/src\/wp-includes\/class-wp-plugin-dependencies.php\" rel=\"noopener noreferrer\" target=\"_blank\">wordpress-develop: class-wp-plugin-dependencies.php<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/trunk\/src\/wp-admin\/includes\/plugin.php\" rel=\"noopener noreferrer\" target=\"_blank\">wordpress-develop: wp-admin\/includes\/plugin.php (validate_plugin_requirements)<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/add_option\/\" rel=\"noopener noreferrer\" target=\"_blank\">Code Reference: add_option()<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/cli\/commands\/plugin\/is-active\/\" rel=\"noopener noreferrer\" target=\"_blank\">WP-CLI: wp plugin is-active<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/api.wordpress.org\/core\/version-check\/1.7\/\" rel=\"noopener noreferrer\" target=\"_blank\">WordPress.org version check API<\/a> and <a href=\"https:\/\/github.com\/WordPress\/WordPress\/tags\" rel=\"noopener noreferrer\" target=\"_blank\">tags of the WordPress GitHub mirror<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/plugins.svn.wordpress.org\/contact-form-7\/\" rel=\"noopener noreferrer\" target=\"_blank\">Contact Form 7 in the plugin SVN (tags\/6.1.7 and trunk)<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-playground\/blob\/trunk\/packages\/docs\/site\/docs\/main\/guides\/github-action-pr-preview.md\" rel=\"noopener noreferrer\" target=\"_blank\">Playground guide: PR preview buttons with GitHub Actions<\/a> (retrieved 29.09.2026)<\/li>\n<li><a href=\"https:\/\/github.com\/actions\/checkout\" rel=\"noopener noreferrer\" target=\"_blank\">actions\/checkout<\/a> and <a href=\"https:\/\/github.com\/actions\/setup-node\" rel=\"noopener noreferrer\" target=\"_blank\">actions\/setup-node<\/a> (retrieved 29.09.2026)<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>A Blueprint prepares the directory preview and Playground tests. Docker uses a separate configuration. The example explains dependency order, fixed test versions and the limits of activation checks.<\/p>\n","protected":false},"author":1,"featured_media":20935,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[92642],"tags":[91144,91219,92757,91096],"class_list":["post-20878","post","type-post","status-publish","format-standard","hentry","category-tutorials-en-wordpress-plugins-tricks","tag-devops","tag-tutorial","tag-web-development","tag-wordpress"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>WordPress Playground: Blueprints for plugin previews and tests | Lukas Wojcik<\/title>\n<meta name=\"description\" content=\"How one Blueprint powers the WordPress.org plugin preview, local Playground CLI and wp-env runs, and a GitHub Actions matrix across WP and PHP versions.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"WordPress Playground: Blueprints for plugin previews and tests | Lukas Wojcik\" \/>\n<meta property=\"og:description\" content=\"How one Blueprint powers the WordPress.org plugin preview, local Playground CLI and wp-env runs, and a GitHub Actions matrix across WP and PHP versions.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-27T06:17:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20878-playground-blueprints.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Lukas Wojcik\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Lukas Wojcik\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"15 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/tutorials-en-wordpress-plugins-tricks\\\/wordpress-playground-blueprints-for-plugin-previews-and-tests\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/tutorials-en-wordpress-plugins-tricks\\\/wordpress-playground-blueprints-for-plugin-previews-and-tests\\\/\"},\"author\":{\"name\":\"Lukas Wojcik\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"headline\":\"WordPress Playground: Blueprints for plugin previews and tests\",\"datePublished\":\"2026-10-27T06:17:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/tutorials-en-wordpress-plugins-tricks\\\/wordpress-playground-blueprints-for-plugin-previews-and-tests\\\/\"},\"wordCount\":2160,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/tutorials-en-wordpress-plugins-tricks\\\/wordpress-playground-blueprints-for-plugin-previews-and-tests\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20878-playground-blueprints.png\",\"keywords\":[\"DevOps\",\"Tutorial\",\"Web Development\",\"WordPress\"],\"articleSection\":[\"Tutorials\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/tutorials-en-wordpress-plugins-tricks\\\/wordpress-playground-blueprints-for-plugin-previews-and-tests\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/tutorials-en-wordpress-plugins-tricks\\\/wordpress-playground-blueprints-for-plugin-previews-and-tests\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/tutorials-en-wordpress-plugins-tricks\\\/wordpress-playground-blueprints-for-plugin-previews-and-tests\\\/\",\"name\":\"WordPress Playground: Blueprints for plugin previews and tests | Lukas Wojcik\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/tutorials-en-wordpress-plugins-tricks\\\/wordpress-playground-blueprints-for-plugin-previews-and-tests\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/tutorials-en-wordpress-plugins-tricks\\\/wordpress-playground-blueprints-for-plugin-previews-and-tests\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20878-playground-blueprints.png\",\"datePublished\":\"2026-10-27T06:17:00+00:00\",\"description\":\"How one Blueprint powers the WordPress.org plugin preview, local Playground CLI and wp-env runs, and a GitHub Actions matrix across WP and PHP versions.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/tutorials-en-wordpress-plugins-tricks\\\/wordpress-playground-blueprints-for-plugin-previews-and-tests\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/tutorials-en-wordpress-plugins-tricks\\\/wordpress-playground-blueprints-for-plugin-previews-and-tests\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/tutorials-en-wordpress-plugins-tricks\\\/wordpress-playground-blueprints-for-plugin-previews-and-tests\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20878-playground-blueprints.png\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20878-playground-blueprints.png\",\"width\":1200,\"height\":630,\"caption\":\"WordPress Playground: Blueprints for plugin previews and tests\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/tutorials-en-wordpress-plugins-tricks\\\/wordpress-playground-blueprints-for-plugin-previews-and-tests\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"WordPress Playground: Blueprints for plugin previews and tests\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"Lukas Wojcik\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"Lukas Wojcik\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"WordPress Playground: Blueprints for plugin previews and tests | Lukas Wojcik","description":"How one Blueprint powers the WordPress.org plugin preview, local Playground CLI and wp-env runs, and a GitHub Actions matrix across WP and PHP versions.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/","og_locale":"en_US","og_type":"article","og_title":"WordPress Playground: Blueprints for plugin previews and tests | Lukas Wojcik","og_description":"How one Blueprint powers the WordPress.org plugin preview, local Playground CLI and wp-env runs, and a GitHub Actions matrix across WP and PHP versions.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/","og_site_name":"Lukas Wojcik - Blog","article_published_time":"2026-10-27T06:17:00+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20878-playground-blueprints.png","type":"image\/png"}],"author":"Lukas Wojcik","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Lukas Wojcik","Est. reading time":"15 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/#article","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/"},"author":{"name":"Lukas Wojcik","@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"headline":"WordPress Playground: Blueprints for plugin previews and tests","datePublished":"2026-10-27T06:17:00+00:00","mainEntityOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/"},"wordCount":2160,"commentCount":0,"publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20878-playground-blueprints.png","keywords":["DevOps","Tutorial","Web Development","WordPress"],"articleSection":["Tutorials"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/","name":"WordPress Playground: Blueprints for plugin previews and tests | Lukas Wojcik","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/#primaryimage"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20878-playground-blueprints.png","datePublished":"2026-10-27T06:17:00+00:00","description":"How one Blueprint powers the WordPress.org plugin preview, local Playground CLI and wp-env runs, and a GitHub Actions matrix across WP and PHP versions.","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/#primaryimage","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20878-playground-blueprints.png","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20878-playground-blueprints.png","width":1200,"height":630,"caption":"WordPress Playground: Blueprints for plugin previews and tests"},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/tutorials-en-wordpress-plugins-tricks\/wordpress-playground-blueprints-for-plugin-previews-and-tests\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"WordPress Playground: Blueprints for plugin previews and tests"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"Lukas Wojcik","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"Lukas Wojcik"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/20878","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=20878"}],"version-history":[{"count":3,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/20878\/revisions"}],"predecessor-version":[{"id":25943,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/20878\/revisions\/25943"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media\/20935"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=20878"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/categories?post=20878"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=20878"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}