This file provides guidance to AI coding agents when working with code in this repository.
WordPress Playground is a monorepo that runs WordPress and PHP entirely in WebAssembly, enabling zero-setup WordPress environments in browsers, Node.js, and CLIs. The project consists of two major architectural layers and several supporting packages:
- PHP-WASM Layer (
packages/php-wasm/*): Emscripten-compiled PHP runtime for web and Node.js - Playground Layer (
packages/playground/*): WordPress-specific tooling, client libraries, and applications - Supporting packages:
packages/nx-extensions/(custom NX executors),packages/docs/(Docusaurus documentation site),packages/meta/(ESLint plugin, changelog tooling),packages/bun-extensions/andpackages/vite-extensions/(build tooling)
This is an NX monorepo with npm workspaces. All commands use NX for task orchestration.
Node.js version: This project requires a specific Node.js version (defined in .nvmrc and the engines field in root package.json). Before running any commands, ensure the correct version is active (e.g., via nvm use or other version manager).
# Development
npm run dev # Start website dev server (localhost:5400); auto-reloads on code changes, no restart needed
npm run dev:docs # Start documentation site
npx nx dev playground-cli server # Run CLI from source
# Building
npm run build # Build all packages
npm run build:website # Build the main website
npm run build:docs # Build documentation
npx nx build <package-name> # Build specific package
# Testing
npm test # Run all tests
npx nx test <package-name> # Test specific package
npx nx e2e playground-website # Run end-to-end tests
# Running a single test file
npx nx test <package-name> --testFile=<test-file-name>
# Linting & Formatting
npm run lint # Lint all packages
npm run typecheck # Type check all packages
npm run format # Format code with Prettier
npm run format:uncommitted # Format only uncommitted files
# PHP Recompilation (see compile-php-wasm skill for details)
npm run recompile:php:web # Recompile all PHP versions for web
npm run recompile:php:node # Recompile all PHP versions for Node.js
npm run recompile:php:web:next # Recompile PHP next web binaries
npm run sync:php-next # Fetch gitignored PHP next assets for local dev
# WordPress Builds
npm run rebuild:wordpress-builds # Rebuild all WordPress versions@php-wasm/<name>: PHP runtime and utilities@wp-playground/<name>: WordPress Playground features and tools
The core architecture uses an iframe-based isolation model:
@wp-playground/client (parent window)
↓ postMessage API
@wp-playground/remote (iframe content, runs PHP)
↓
@php-wasm/web (PHP runtime)
↓
@php-wasm/universal (abstract interface)
Key packages:
@wp-playground/client: JavaScript API for embedding Playground in iframes@wp-playground/remote: The HTML application running inside the iframe@php-wasm/web: Browser-based PHP runtime (uses Emscripten WASM)@php-wasm/node: Node.js-based PHP runtime@php-wasm/universal: Abstract interface shared by web and node implementations
Blueprints are declarative JSON configurations that define WordPress site states. Located in @wp-playground/blueprints.
Key concepts:
- Blueprint steps execute sequentially (e.g.,
installPlugin,login,runPHP) - Two execution modes: V1 (TypeScript runner) and V2 (experimental PHP runner)
- Steps are defined in
packages/playground/blueprints/src/lib/steps/ - Each step has a
.tsimplementation and.spec.tstest file
Common blueprint steps:
installPlugin,activatePlugin: Plugin managementinstallTheme,activateTheme: Theme managementlogin: User authenticationrunPHP,runPHPWithOptions: Execute PHP codedefineWpConfigConsts: Modify wp-config.phpimportWxr,importWordPressFiles: Import content
Schema generation: Blueprint JSON schemas are auto-generated from TypeScript types.
After modifying step interfaces, rebuild with npx nx build playground-blueprints.
The schema is NOT auto-rebuilt in npm run dev mode.
@wp-playground/storage: Provides filesystem backends (IndexedDB in browser, filesystem in Node)@wp-playground/sync: Multi-client synchronization for collaborative editing@php-wasm/fs-journal: Tracks filesystem changes for synchronization
@wp-playground/wordpress-builds compiles specific WordPress versions into the playground format. Each version is tested and bundled separately.
PHP binaries are compiled separately for:
- Web (browser): Asyncify and JSPI variants for different browser capabilities
- Node.js: Native async/await support
Version-specific builds: @php-wasm/web-7-4 through @php-wasm/web-8-5 (and corresponding node-builds)
- Type imports must be explicit: Use
import type { Foo } from 'bar'(required for Node.js type stripping) - No parameter properties: TypeScript parameter properties are not supported by Node.js type stripping
- Module resolution:
bundlermode in tsconfig - Target: ES2021 with ESNext modules
- Path aliases defined in
tsconfig.base.jsonfor cross-package imports
- Comment length: Max 100 characters per line (enforced by ESLint)
- No console.log: Disallowed except in tests and bin/ scripts
- Consistent type imports: Required by
@typescript-eslint/consistent-type-imports - Module boundaries: Enforced via
@nx/enforce-module-boundaries- Packages tagged
scope:web-clientcannot be depended on by others scope:independent-from-php-binariespackages cannot depend onscope:php-binaries
- Packages tagged
- Function ordering: First caller, then callee. When function A calls function B, write first A, then B.
- Method ordering: First public, then protected, then private. Respect Function ordering as well.
- Path manipulation: Never use ad-hoc string operations for file paths. Use the POSIX path utilities from
@php-wasm/util(joinPaths,dirname,basename,normalizePath, etc.) instead of Node.jspath(which can produce Windows-style paths). If the package you're modifying has its ownpaths.ts, prefer that; otherwise import from@php-wasm/util. New path helpers should be co-located with the existingpaths.tsin the relevant package, with tests.
- Test files: Co-located with implementation as
*.spec.ts - Test runner: Vitest (via
@nx/vite:test) for most packages; some packages use Jest (via@nx/jest) - Coverage: Reports to
coverage/packages/<package-name> - E2E tests: Playwright and Cypress for website testing
- Always fix failing tests: Never skip failing tests; fix the code to make tests pass
All published packages follow this pattern:
packages/[layer]/[package-name]/
├── src/
│ ├── index.ts # Main entry point
│ └── lib/ # Implementation
├── package.json # npm metadata
├── project.json # NX build configuration
├── tsconfig.json # Base TypeScript config
├── tsconfig.lib.json # Library build config
├── tsconfig.spec.json # Test config
└── README.md # Package documentation
Some packages have their own AGENTS.md with package-specific guidance. Check
for one when working within a package.
- Dual format: All packages publish both ESM (
.js) and CommonJS (.cjs) - publishConfig.directory: Points to
dist/packages/[layer]/[package-name] - Lerna: Used for coordinated multi-package publishing (
npm run release) - Exports field: Defines both
importandrequireconditions - Version management: All packages versioned together (see
lerna.jsonfor current version)
# Individual package test
npx nx test playground-blueprints
# Run specific test file
npx nx test playground-blueprints --testFile=activate-plugin.spec.ts
# Run with coverage
npx nx test playground-blueprints --coverage
# E2E tests
npx nx e2e playground-websiteThe Playground CLI (@wp-playground/cli) can be run directly from source:
npx nx dev playground-cli server --wp=6.8 --php=8.4 --auto-mountCLI features:
--auto-mount: Automatically detect and mount plugin/theme/WordPress directory--blueprint=<path>: Execute a blueprint JSON file--mount=<src>:<dest>: Manually mount directories--login: Auto-login as admin--php=<version>: Choose PHP version (7.4-8.5)--wp=<version>: Choose WordPress version
- Default branch:
trunkis the primary development branch - Never use bare
git push: Always specify remote and branch explicitly - Shallow clone recommended:
git clone -b trunk --single-branch --depth 1 --recurse-submodules - Submodules: isomorphic-git submodule provides browser-based git operations
PHP binaries are pre-compiled and committed to the repository. Recompilation is rarely needed.
For compilation commands, build flags, and troubleshooting, see the compile-php-wasm skill.
For debugging WASM crashes, see the debug-php-wasm-main-module and debug-php-wasm-side-modules skills.
Located in packages/nx-extensions/src/executors/:
build: Builds packagesbuilt-script: Runs scripts from built outputpackage-json: Generates package.json with correct exportsassert-built-esm-and-cjs: Verifies dual-format buildpackage-for-self-hosting: Creates distributable archives
nx.json: NX workspace configurationtsconfig.base.json: TypeScript path aliases and compiler optionspackage.json: Root package with all npm scriptslerna.json: Version management and publish configuration.eslintrc.json: ESLint rules including module boundariespackages/playground/blueprints/src/lib/steps/: Blueprint step implementationspackages/php-wasm/universal/: Core PHP abstraction layerpackages/php-wasm/compile/: Docker/Emscripten PHP build pipelinepackages/playground/website/: Main demo applicationpackages/playground/cli/: CLI tool implementationpackages/docs/: Docusaurus documentation sitepackages/meta/: Internal tooling (ESLint plugin, changelog)isomorphic-git/: Git operations in browser (submodule)
- Deployed to https://wordpress.github.io/wordpress-playground/
- Built with Docusaurus in
packages/docs/ - API reference generated with TypeDoc from package source
- Backwards compatibility: Breaking changes are acceptable and often useful during development, but must be surfaced to the developer. When creating a PR, clearly document any breaking changes in the PR description. Key downstream consumers include Telex, Studio, and wp-env
- Offline support: Website can be built for offline use with service workers
- WordPress major and beta versions: Auto-refreshed via GitHub Actions
- SQLite integration: WordPress uses SQLite by default (no MySQL required)
- Security: Iframe-based isolation prevents untrusted code execution in parent window
- For navigating/exploring the workspace, invoke the
nx-workspaceskill first - it has patterns for querying projects, targets, and dependencies - When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through
nx(i.e.nx run,nx run-many,nx affected) instead of using the underlying tooling directly - Prefix nx commands with the workspace's package manager (e.g.,
pnpm nx build,npm exec nx test) - avoids using globally installed CLI - You have access to the Nx MCP server and its tools, use them to help the user
- For Nx plugin best practices, check
node_modules/@nx/<plugin>/PLUGIN.md. Not all plugins have this file - proceed without it if unavailable. - NEVER guess CLI flags - always check nx_docs or
--helpfirst when unsure
- For scaffolding tasks (creating apps, libs, project structure, setup), ALWAYS invoke the
nx-generateskill FIRST before exploring or calling MCP tools
- USE for: advanced config options, unfamiliar flags, migration guides, plugin configuration, edge cases
- DON'T USE for: basic generator syntax (
nx g @nx/react:app), standard commands, things you already know - The
nx-generateskill handles generator discovery internally - don't call nx_docs just to look up generator syntax