Vibe Code a WordPress Site with Claude Code: Complete Setup Guide
Most WordPress development still happens in a browser — clicking through wp-admin, dragging page builder blocks, refreshing to see changes. Vibe coding ...
Most WordPress development still happens in a browser — clicking through wp-admin, dragging page builder blocks, refreshing to see changes. Vibe coding a WordPress site with Claude Code is a faster way.
This guide walks you through the complete workflow: WordPress Docker setup, block theme creation, custom plugin development, content management via REST API, and deployment to a live server — all from the CLI, without opening wp-admin.
We’ve used this workflow to build and manage two production WordPress sites: a personal brand site (sandeepkelvadi.com) and a marketing skills directory (marketingskills.directory) with 160+ custom post type entries, 6 mu-plugins, custom sitemaps, and automated content pipelines.
What You’ll Build
By the end of this guide, you’ll have:
- A local WordPress development environment running in Docker
- Claude Code connected to your WordPress instance via REST API
- A workflow for creating themes, plugins, and content from the CLI
- A deployment pipeline from local to live server
- The skills and patterns to manage an entire WordPress site from your terminal
Prerequisites: Docker Desktop installed, Claude Code installed, a code editor, and a hosting account with FTP access for deployment.
Step 1: Set Up WordPress Locally with Docker
Create your project folder:
mkdir my-wordpress-site && cd my-wordpress-site
Now open Claude Code in this folder and ask it to create the Docker setup:
Claude Code prompt — copy and paste this:
Create a docker-compose.yml for a WordPress development environment with:
- MySQL 8.0 with health checks (ping every 5s, 10 retries)
- WordPress latest on port 8088, depending on healthy MySQL
- A WP-CLI service for command-line management
- Volume mounts for: a custom plugin folder (./my-plugin), a custom theme folder (./my-theme), and mu-plugins (./mu-plugins)
- Named volumes for wp_data and db_data persistence
- Database credentials: user "wordpress", password "wordpress", database "wordpress"
Start the containers:
docker compose up -d
Visit your local WordPress site in the browser. The URL depends on the port you configured in docker-compose.yml (the prompt above uses 8088, so it would be http://localhost:8088). Complete the WordPress setup wizard and create your admin account — you’ll need the username for API access.
Key architecture decisions:
- The
wpcliservice gives you WP-CLI access without installing PHP locally. Run commands with:docker compose run --rm wpcli - Volume mounts for your plugin, theme, and mu-plugins mean code is live-reloaded. Edit a file → refresh → changes appear.
- The MySQL health check prevents WordPress from starting before the database is ready (a common Docker timing issue).
Important for media imports: When importing images via WP-CLI, use the www-data user flag or files get wrong permissions:
docker compose run --rm -u 33:33 wpcli media import /var/www/html/wp-content/uploads/my-image.jpg
Step 2: Enable REST API Access
WordPress exposes a REST API at /wp-json/wp/v2/. To use it from Claude Code (or any script), you need authentication.
Create an Application Password
1. Go to http://localhost:8088/wp-admin/users.php
2. Click your username → scroll to “Application Passwords”
3. Enter a name (e.g., “Claude Code”) and click “Add New Application Password”
4. Copy the generated password (shown once — save it)
Create a `.env` File
Claude Code prompt:
Create a .env file with WordPress credentials: WP_URL=http://localhost:8088, WP_USER (my username), and WP_APP_PASS (the application password I just generated). Also add placeholder entries for live site credentials (LIVE_WP_URL, LIVE_WP_USER, LIVE_WP_APP_PASS) and FTP credentials (FTP_HOST, FTP_USER, FTP_PASS, FTP_PORT). Add .env to .gitignore.
Test the Connection
Claude Code prompt:
Write a Python script test_api.py that loads credentials from .env and tests the WordPress REST API connection by fetching posts from /wp-json/wp/v2/posts with Basic Auth. Print the status code and number of posts found. Then run it.
If you get a 401, check that Application Passwords is active (built into WordPress 5.6+) and your credentials are correct.
Note: Application Passwords require HTTPS in production. Local environments (localhost) are automatically exempt from this requirement. If your staging server doesn’t have SSL, you’ll need the wp_is_application_passwords_available filter to enable API auth over HTTP.
Step 3: Initialize Claude Code in Your Project
Create a CLAUDE.md file that gives Claude full context about your WordPress setup:
Claude Code prompt:
Create a CLAUDE.md file for this project that documents:
- Project overview: WordPress site running locally via Docker on port 8088
- Build commands: docker compose up/down, WP-CLI access via docker compose run --rm wpcli, media import with -u 33:33 flag
- REST API details: local URL, authentication method (Application Password in .env)
- File structure: my-theme/, my-plugin/, mu-plugins/, content/ for HTML page files, .env for credentials
- Deployment: FTP credentials in .env, deploy script
Install WordPress Skills
Claude Code skills give Claude specialized knowledge for specific tasks. Install these inside Claude Code:
/install-skill wordpress-blocks
/install-skill wp-migration
The WordPress Block Builder skill teaches Claude the correct Gutenberg block markup syntax — which blocks need JSON attributes in HTML comments, how Cover blocks work (the most error-prone block), Query Loop patterns for blog grids, and navigation menu creation.
The WordPress Migration skill covers local-to-live deployment patterns — FTP, REST API sync, URL replacement, and post-migration checklists.
Step 4: Build a Block Theme
Modern WordPress uses block themes (Full Site Editing). Instead of PHP template files, you define layouts with block markup in HTML files. This is where vibe coding WordPress starts to feel different from traditional development — you describe the theme to Claude Code and it generates the block markup.
Claude Code prompt:
Create a minimal WordPress block theme called "my-theme" in the my-theme/ folder with:
- style.css with proper theme metadata (Theme Name, Version, Requires at least, Tested up to, Requires PHP)
- theme.json with version 3 and $schema "https://schemas.wp.org/trunk/theme.json", a color palette (primary dark, accent, background), font families (a body font and a heading font loaded from Google Fonts), layout settings (contentSize 720px, wideSize 1100px), spacing scale, and shadow presets
- templates/index.html (homepage layout)
- templates/single.html (single blog post with featured image, title, meta, content)
- templates/page.html (static page layout)
- templates/archive.html (blog archive with Query Loop showing post grid)
- parts/header.html (site header with logo text and navigation)
- parts/footer.html (site footer with columns)
All templates should use proper block markup with wp:template-part references for header and footer.
theme.json: Your Design System
theme.json is where you define your entire design system — colors, fonts, spacing, shadows. WordPress reads this file and generates CSS variables automatically.
These tokens become CSS variables like var(--wp--preset--color--primary) and var(--wp--preset--spacing--40). Use them in your block markup instead of hardcoding values.
Activate the Theme
docker compose run --rm wpcli theme activate my-theme
Step 5: Build a Custom Plugin
Plugins extend WordPress functionality. For a site managed via CLI, you’ll typically need a plugin that registers custom post types, exposes meta fields to the REST API, adds block patterns, and handles SEO meta tags.
Claude Code prompt:
Create a WordPress plugin called "my-plugin" in the my-plugin/ folder with:
- Main plugin file with proper WordPress plugin headers
- A custom post type "project" with fields: client_name, project_url, year
- All meta fields registered with register_post_meta() and exposed to REST API (show_in_rest => true) with proper auth_callback
- An SEO module in inc/seo-meta.php that adds meta description and Open Graph tags (og:title, og:description, og:type, og:url, og:image) via the wp_head hook
- The custom post type must include 'custom-fields' in its supports array or meta fields won't appear in REST API responses even with show_in_rest => true
- All meta field sanitize_callbacks should use WordPress sanitization functions: sanitize_text_field for strings, absint for integers, esc_url_raw for URLs
- Note: protected meta fields (prefixed with _) need explicit show_in_rest and auth_callback or REST API writes will silently fail
Why show_in_rest matters: Without it, you can create posts via the API but can’t set custom field values. The API call succeeds, returns 200, but meta fields are empty. Two requirements must both be met: the meta field must have show_in_rest => true in register_post_meta(), AND the custom post type must include 'custom-fields' in its supports array. Miss either one and the writes silently fail.
Activate the Plugin
docker compose run --rm wpcli plugin activate my-plugin
Step 6: Create and Manage Content via REST API
Now you can create pages, posts, and custom post type entries entirely from Claude Code.
Create a Page
Claude Code prompt:
Create an About page on the local WordPress site. Write the page content using proper Gutenberg block markup (wp:heading, wp:paragraph, wp:columns, wp:image blocks). Then push it to WordPress via the REST API using the credentials in .env. Use POST to /wp-json/wp/v2/pages with title, slug "about", content, and status "publish".
Create a Custom Post Type Entry
Claude Code prompt:
Create a new "project" entry on WordPress via REST API. Title: "Client Website Redesign", slug: "client-redesign". Set meta fields: _project_client = "Acme Corp", _project_url = "https://acme.com", _project_year = "2026". The content should use block markup with a paragraph describing the project. Push it via POST to /wp-json/wp/v2/project.
Update Existing Content
Claude Code prompt:
Fetch the current content of the "about" page from WordPress REST API (GET /wp-json/wp/v2/pages?slug=about), update the team section to include a new team member named [Name] with [Role], and push the updated content back via POST to /wp-json/wp/v2/pages/{id}.
Content Workflow Pattern
The most effective pattern we’ve found:
1. Store page content as HTML files in your project (content/about.html, content/services.html)
2. Edit them in Claude Code (or have Claude generate them)
3. Push to WordPress via REST API with a deploy script
4. Pull from live to local with a download script (keeps them in sync)
This means your content is version-controlled in Git, editable by Claude Code, and deployable with a single command.
Step 7: Must-Use Plugins (MU-Plugins)
MU-plugins load automatically — no activation required, no deactivation possible. They’re ideal for functionality that should always be active: custom REST endpoints, rewrite rules, sitemaps, download handlers, tracking.
Claude Code prompt — custom REST endpoint:
Create an mu-plugin at mu-plugins/custom-api.php that registers a public REST API endpoint at /wp-json/my/v1/catalog. It should return all published "project" posts with their ID, title (HTML entity decoded), slug, and client_name meta field. Use get_posts() with numberposts=-1 and permission_callback __return_true. Escape all output with esc_html() for strings and esc_url() for URLs.
Security note: __return_true makes this endpoint fully public — accessible from any site without authentication. This is appropriate for read-only catalog data. For endpoints that create, update, or delete data, always use a proper permission check like current_user_can('edit_posts') instead.
Claude Code prompt — custom XML sitemap:
Create an mu-plugin at mu-plugins/sitemaps.php that:
1. Disables WordPress default sitemaps (wp_sitemaps_enabled filter)
2. Adds custom rewrite rules for /sitemap.xml and /sitemap-{type}.xml
3. Registers "custom_sitemap" as a query var
4. Handles the request in template_redirect — serves XML with proper Content-Type header
5. Supports types: "index" (lists all sub-sitemaps), "pages" (all published pages), "projects" (all project CPT entries)
After creating it, flush rewrite rules with: docker compose run --rm wpcli rewrite flush
MU-plugins are deployed separately from regular plugins. WordPress only loads PHP files at the root of the mu-plugins directory — subdirectories are ignored entirely. If you need to organize code into folders, create a loader file at the root that requires files from subdirectories. Deploy mu-plugins as individual files rather than mirroring a directory.
Step 8: Deploy to Live
Deployment has two parts: uploading code (FTP) and syncing content (REST API).
Create a Deploy Script
Claude Code prompt:
Create a deploy.sh bash script that reads FTP credentials from .env and uses lftp to:
1. Mirror ./my-theme to public_html/wp-content/themes/my-theme (with --reverse --delete --verbose flags)
2. Mirror ./my-plugin to public_html/wp-content/plugins/my-plugin
3. Upload individual mu-plugin files (not mirror — use lftp put command for each .php file in mu-plugins/)
Make it accept arguments: "./deploy.sh theme", "./deploy.sh plugin", "./deploy.sh mu", or "./deploy.sh all" to deploy selectively.
The --reverse --delete flags mean: upload local to remote, and delete remote files that don’t exist locally. This keeps the live site clean.
Security tip: Use SFTP (FTP over SSH) rather than plain FTP whenever your host supports it — plain FTP transmits credentials and files unencrypted. For hosts that support SSH, rsync over SSH is faster and more reliable than lftp. Change the lftp command to lftp -e "set ftp:ssl-allow true; ..." at minimum.
Create a Content Deploy Script
Claude Code prompt:
Create a deploy_content.py Python script that:
1. Loads LIVE_WP_URL, LIVE_WP_USER, LIVE_WP_APP_PASS from .env
2. Has a rewrite_urls() function that replaces "http://localhost:8088" with the live URL
3. For each HTML file in content/ folder: reads the file, rewrites URLs, checks if a page with that slug exists on live (GET /wp-json/wp/v2/pages?slug=), creates or updates it via REST API
4. Prints "Created: {slug}" or "Updated: {slug}" for each page
5. After all pages are deployed, runs verification: GET each page URL and print status codes
For a comprehensive deployment checklist, see the WordPress Migration — Local to Live skill.
Post-Deployment Hardening
After your first deployment, harden the live site’s wp-config.php:
Claude Code prompt:
Connect to the live WordPress site and set these wp-config.php constants:
- DISALLOW_FILE_EDIT = true (prevents editing theme/plugin files from wp-admin — you manage code via FTP/Git)
- WP_DEBUG = false (prevents error messages from leaking server paths and PHP info)
If you have WP-CLI on the server, use: wp config set DISALLOW_FILE_EDIT true --raw && wp config set WP_DEBUG false --raw
Also flush rewrite rules after any deployment that adds custom post types, REST endpoints, or rewrite rules:
# On the live server (if WP-CLI is available):
wp rewrite flush
# Or visit Settings → Permalinks in wp-admin and click Save (forces a flush)
Step 9: Ongoing Management
Once the site is live, you manage it the same way — from Claude Code.
Claude Code prompt — content update:
Read the about page from the live WordPress site (use LIVE credentials from .env), update the team section with [new information], and push the changes back. Verify the page loads correctly after update.
Claude Code prompt — plugin update:
I've made changes to the my-plugin/ files. Deploy just the plugin to live using the deploy.sh script, then verify the site still loads correctly.
Claude Code prompt — site health check:
Run a health check on the local WordPress site: list active plugins, check for available updates (dry run), verify all key pages return 200, and export a content backup to the wp-content directory.
Common Patterns and Gotchas
Block Markup Validation
WordPress Gutenberg block markup is strict. Common errors:
- Cover blocks need the image URL in JSON attributes, not as an inner Image block
- Navigation uses
wp_navigationpost type — create it withdocker compose run --rm wpcli post create --post_type=wp_navigation --post_status=publish --post_content=''
- Query Loop requires specific inner block structure (
post-template→post-title,post-excerpt, etc.)
The WordPress Block Builder skill has the full reference for correct block markup.
URL Replacement
Always replace local URLs before deploying content to live. Miss this step and your live site will have links pointing to localhost. The rewrite_urls() function in your deploy script handles this automatically — but check for hardcoded URLs in block markup JSON attributes too.
WP-CLI User Permissions
Media imports via Docker WP-CLI fail silently without the correct user flag:
# Wrong (creates file with root ownership, WordPress can't read it)
docker compose run --rm wpcli media import image.jpg
# Right (www-data user, ID 33)
docker compose run --rm -u 33:33 wpcli media import image.jpg
Protected Meta Fields
Meta keys prefixed with _ (underscore) are “protected” in WordPress. The REST API silently ignores writes to protected fields unless you explicitly register them with register_post_meta() and show_in_rest => true. The API returns 200 but the meta value is empty. This is the most confusing WordPress API behavior — the request “succeeds” but does nothing.
Sanitize Input, Escape Output
WordPress has a strict security principle: sanitize all data on input, escape all data on output. When registering meta fields with register_post_meta(), always include a sanitize_callback — use sanitize_text_field for strings, absint for integers, esc_url_raw for URLs. In your theme templates or REST API responses, escape output with esc_html(), esc_attr(), or esc_url(). Claude Code will generate sanitization code if you mention it in your prompts, but verify it uses WordPress sanitization functions (not generic PHP functions like strip_tags or htmlspecialchars). The WordPress Plugin Handbook security chapter covers this in detail.
FTP Rate Limiting
Some hosts rate-limit FTP connections. If your deploy script gets blocked:
- Wait 2-3 minutes for connections to expire
- Remove
--parallel=5from lftp commands - Deploy in smaller batches (theme first, then plugin, then mu-plugins)
WAF Blocking REST API Calls
Some hosting WAFs block rapid REST API calls (you’ll see 403 responses during bulk content deployment). Workarounds:
- Add
time.sleep(1)between requests - Deploy PHP mu-plugins via FTP that handle content import server-side
wp-cron.phpis typically WAF-whitelisted and can trigger mu-plugin code
Frequently Asked Questions
Can Claude Code build a complete WordPress site from scratch?
Yes. Claude Code can generate a Docker setup, create a full block theme with theme.json design tokens, build custom plugins with post types and REST API integration, write page content in proper Gutenberg block markup, and deploy everything to a live server via FTP and REST API. The workflow covered in this guide produces production-ready WordPress sites managed entirely from the terminal.
Do I need to know PHP to vibe code a WordPress site?
Not to get started. Claude Code generates the PHP for plugins, mu-plugins, and theme functions. You describe what you need in plain language and Claude writes the code. However, understanding WordPress concepts (hooks, filters, custom post types, REST API) helps you write better prompts and catch errors. As you progress, reading the generated PHP will build your understanding.
Is Docker required or can I use MAMP/Local?
Docker is recommended because it gives you WP-CLI access without installing PHP locally, and the volume mount system means code changes appear instantly. MAMP, Local by Flywheel, or any local WordPress environment will work for the theme/plugin development parts — you’d just skip the Docker-specific commands and use WP-CLI directly if it’s installed on your system.
How does this compare to using Elementor or Divi?
Page builders generate proprietary markup locked to their plugin. Block themes generate native WordPress block markup that works with any block theme and survives plugin deactivation. The CLI workflow is faster for developers and produces lighter, more portable output. The trade-off: no visual drag-and-drop. You describe the layout to Claude Code instead of clicking in a builder.
Can I use this workflow for client sites?
We manage two production client sites with this exact workflow. The deployment script, content sync, and REST API management patterns are designed for ongoing site management. The WordPress Migration — Local to Live skill covers client-specific concerns like staging environments, content backups, and rollback procedures.
Related Skills and Resources
- WordPress Block Builder — Correct Gutenberg block markup patterns, Cover block rules, Query Loop, navigation menus
- WordPress Migration — Local to Live — Full deployment checklist, REST API sync, URL replacement, post-migration verification
- SEO Technical Audit — 15 audit types to run on your WordPress site after launch
- SEO Rank Tracking — Set up keyword tracking via Google Search Console
- WP Performance Review — WordPress-specific performance audit and optimization patterns
What We Built With This Workflow
sandeepkelvadi.com — Personal brand site with:
- Block theme based on Twenty Twenty-Five
- Custom plugin: design tokens (theme.json injection), custom post type for talks with 6 meta fields, SEO meta module, block patterns
- Google Fonts, GA4 + Clarity tracking
- 4-phase deployment: FTP upload → theme/plugin activation → content sync via REST API → verification
- All content managed as HTML files in Git, deployed with a single command
marketingskills.directory — Skills directory with:
- Custom theme + custom plugin (160+ CPT entries)
- 6 mu-plugins: REST API for npm CLI, skill downloads with access code validation, AI-readable indexes (llms.txt), custom XML sitemaps (9 types), premium access codes, download tracking
deploy.shorchestrating: theme → plugin → mu-plugins → content validation → verification- Content pipeline: Markdown SKILL.md → Python validation → WordPress import → FTP deploy
Both sites are managed entirely via vibe coding with Claude Code. No Elementor, no Divi, no page builders. WordPress Docker for local development, block markup for themes, REST API for content, and a deployment script for going live.