| name | build-theme |
| description | Select, install, and activate an FSE block theme from WP.org based on a natural language site description — queries API with full-site-editing tag, evaluates by relevance/popularity/rating, falls back to curated list |
| requires | ["wp-cli","docker-mysql-running"] |
| runs-after | ["build-mcp Section 2"] |
| runs-before | ["build-content"] |
Build Theme Skill
Selects, installs, and activates a Full Site Editing (FSE) block theme from WP.org based on the user's natural language site description. This skill queries the WP.org Themes API v1.2 for FSE themes, evaluates results against the NL prompt using Claude's in-context judgment, and installs the best match. A curated fallback list provides guaranteed theme selection when the API returns poor results or is unavailable.
Critical sequencing: This skill runs AFTER build-mcp Section 2 (MCP adapter activated, DB re-exported) and BEFORE build-content (content seeding requires the theme to be active for menu location discovery).
This skill expects the following variables to already be set by the calling command:
BUILD_DIR — absolute path to the build directory (set by build-scaffold Section 2)
WP — the WP-CLI command prefix (e.g., wp --path=$BUILD_DIR or the Docker equivalent, set by build-scaffold Section 4)
NL_PROMPT — the user's natural language site description string
SITE_TITLE — site title derived from the NL prompt (set by the calling command before this skill)
Section 1: WP.org Themes API Query
Query the WP.org Themes API v1.2 for the FSE block theme pool. Use the full-site-editing tag to filter — this is the canonical public signal for FSE/block themes. Do NOT combine search and tag parameters: the API ignores tags when search is present. Claude evaluates theme relevance in-context after fetching the pool.
echo "[Build] Querying WP.org Themes API for FSE theme pool..."
FSE_THEMES_FILE="/tmp/fse_themes_$$.json"
curl -s --max-time 10 \
"https://api.wordpress.org/themes/info/1.2/?action=query_themes&request[tag][]=full-site-editing&request[browse]=popular&request[per_page]=50&request[fields][active_installs]=true&request[fields][rating]=true&request[fields][description]=true&request[fields][tags]=true" \
-o "$FSE_THEMES_FILE"
CURL_EXIT=$?
API_AVAILABLE=false
if [ $CURL_EXIT -eq 0 ] && [ -s "$FSE_THEMES_FILE" ]; then
THEME_COUNT=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(len(d.get('themes', [])))" "$FSE_THEMES_FILE" 2>/dev/null || echo "0")
if [ "$THEME_COUNT" -ge 3 ]; then
API_AVAILABLE=true
echo "[Build] WP.org API returned $THEME_COUNT FSE themes for evaluation."
else
echo "[Build] WP.org API returned fewer than 3 themes ($THEME_COUNT). Falling back to curated list."
fi
else
echo "[Build] WP.org API unavailable — using curated theme list"
fi
Section 2: Theme Evaluation and Selection
Claude reads the API JSON response and evaluates each theme against the NL_PROMPT. If the API returned a viable pool, Claude selects the best match using relevance, popularity, and rating. If the API failed or returned too few results, Claude selects from the curated fallback list based on the site category in NL_PROMPT.
Scoring criteria (in priority order):
- Name, description, and tag relevance to the site type described in
NL_PROMPT
active_installs — popularity signal (higher = better)
rating — quality signal (higher = better)
Classic themes are never selected, even if they would score higher on other metrics.
if [ "$API_AVAILABLE" = "true" ]; then
echo "[Build] Evaluating FSE themes against NL prompt: \"$NL_PROMPT\""
echo "[Build] Reading themes from: $FSE_THEMES_FILE"
THEME_SLUG="<slug selected by Claude from API results>"
THEME_NAME="<display name selected by Claude from API results>"
else
THEME_SLUG="<slug selected by Claude from curated fallback list>"
THEME_NAME="<display name for the selected curated theme>"
fi
echo "[Build] Theme selected: $THEME_NAME ($THEME_SLUG)"
Section 3: Theme Installation and Activation
Install and activate the selected theme using WP-CLI. If installation fails, attempt fallback candidates. Always validate FSE compatibility by checking for theme.json post-install. Use warn-and-continue — never abort the build for theme failures.
echo "[Build] Installing theme: $THEME_SLUG..."
install_theme() {
local slug="$1"
if $WP theme install "$slug" --activate 2>&1; then
echo "[Build] Theme installed and activated: $slug"
if [ -f "$BUILD_DIR/wp-content/themes/$slug/theme.json" ]; then
echo "[Build] FSE validated: theme.json found."
else
echo "[Build] WARNING: theme.json not found — theme may not be FSE-compatible."
echo "[Build] Proceeding — Claude selected this theme from the full-site-editing tag pool."
fi
THEME_VERSION=$($WP theme get "$slug" --field=version 2>/dev/null || \
grep -m 1 "^Version:" "$BUILD_DIR/wp-content/themes/$slug/style.css" 2>/dev/null | \
sed 's/Version: //' | tr -d '[:space:]' || \
echo "unknown")
THEME_INSTALLED=true
echo "[Build] Theme installed and activated: $slug (v$THEME_VERSION)"
return 0
else
echo "[Build] WARNING: Theme installation failed for slug: $slug"
THEME_INSTALLED=false
return 1
fi
}
if ! install_theme "$THEME_SLUG"; then
if [ "$API_AVAILABLE" = "true" ]; then
echo "[Build] Trying curated fallback theme for site category..."
FALLBACK_SLUG="twentytwentyfour"
if ! install_theme "$FALLBACK_SLUG"; then
if [ "$FALLBACK_SLUG" != "twentytwentyfour" ]; then
echo "[Build] Trying last-resort fallback: twentytwentyfour..."
if ! install_theme "twentytwentyfour"; then
echo "[Build] WARNING: All theme installation attempts failed."
echo "[Build] WARNING: Build continues with WordPress default theme."
THEME_SLUG="default"
THEME_NAME="WordPress Default"
THEME_VERSION="unknown"
THEME_INSTALLED=false
fi
else
echo "[Build] WARNING: All theme installation attempts failed."
echo "[Build] WARNING: Build continues with WordPress default theme."
THEME_SLUG="default"
THEME_NAME="WordPress Default"
THEME_VERSION="unknown"
THEME_INSTALLED=false
fi
fi
else
if [ "$THEME_SLUG" != "twentytwentyfour" ]; then
echo "[Build] Trying last-resort fallback: twentytwentyfour..."
if ! install_theme "twentytwentyfour"; then
echo "[Build] WARNING: All theme installation attempts failed."
echo "[Build] WARNING: Build continues with WordPress default theme."
THEME_SLUG="default"
THEME_NAME="WordPress Default"
THEME_VERSION="unknown"
THEME_INSTALLED=false
fi
else
echo "[Build] WARNING: All theme installation attempts failed."
echo "[Build] WARNING: Build continues with WordPress default theme."
THEME_SLUG="default"
THEME_NAME="WordPress Default"
THEME_VERSION="unknown"
THEME_INSTALLED=false
fi
fi
fi
rm -f "$FSE_THEMES_FILE"
Section 4: Site Title Update
Update the WordPress site title and tagline to match the site described in NL_PROMPT. The site title was already set during build-scaffold WP install (--title="$SITE_TITLE"), but this step allows the tagline to be set with a contextual description generated from the NL prompt.
echo "[Build] Setting site title and tagline..."
$WP option update blogname "$SITE_TITLE" 2>&1
SITE_TAGLINE="<Claude-generated tagline from NL_PROMPT>"
$WP option update blogdescription "$SITE_TAGLINE" 2>&1
echo "[Build] Site title set: $SITE_TITLE"
echo "[Build] Site tagline set: $SITE_TAGLINE"
Implementation Notes
Pipeline position: This skill runs in the NL build pipeline strictly AFTER build-mcp Section 2 (MCP adapter activated, database re-exported) and BEFORE build-content. Theme must be active before content seeding because build-content uses $WP menu location list to discover the active theme's navigation locations — this requires the target theme to already be installed and active.
Docker MySQL container: The ephemeral Docker MySQL container from build-scaffold Section 3 must still be running when this skill executes. WP-CLI theme installation writes to the WordPress options table (active theme slug). The EXIT trap set in build-scaffold Section 3 remains active for the entire build session and fires only when the full command exits — not between skill invocations.
Output variables: The following variables are set by this skill and consumed by downstream skills (build-content, build-setup):
THEME_SLUG — installed theme slug (e.g., twentytwentyfour, flavor)
THEME_NAME — display name (e.g., Twenty Twenty-Four, Flavor)
THEME_VERSION — installed version string (e.g., 1.3, unknown if undetectable)
THEME_INSTALLED — true if installation succeeded, false if all fallbacks failed
Curated fallback list maintenance: The curated list maps site categories to known FSE block themes on WP.org. If a curated theme slug is removed from WP.org (causing wp theme install to fail with "not found"), update this list with a current FSE-compatible replacement. twentytwentyfour is the guaranteed last-resort fallback — it is maintained by the WordPress.org core team and will not be removed.
No post-install customization (locked decision): Per user decision captured in CONTEXT.md, no theme.json overrides, child themes, or Global Styles modifications are applied after installation. The theme is installed and activated as-is from WP.org. Its built-in design stands as delivered.
FSE validation reliability: The theme.json check is a secondary confirmation — Claude already filtered themes by the full-site-editing tag in the API query. A "WARNING: theme.json not found" message does not indicate an error: the theme may store theme.json at a non-standard location, or the WP.org tag may have been set correctly but the file path check failed. Build continues regardless.
References:
- @references/wp-block-themes/SKILL.md — FSE theme structure, theme.json schema, block template patterns
- @references/wp-wpcli-and-ops/SKILL.md — WP-CLI theme command reference, --activate flag, --field output