<?xml version="1.0" encoding="UTF-8" standalone="no"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" version="2.0">

<channel>
	<title>Bill Erickson, WordPress Consultant</title>
	<atom:link href="https://www.billerickson.net/feed/" rel="self" type="application/rss+xml"/>
	<link>https://www.billerickson.net/</link>
	<description></description>
	<lastBuildDate>Wed, 24 Jun 2026 16:00:56 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.6.2</generator>

<image>
	<url>https://www.billerickson.net/wp-content/uploads/2018/11/cropped-favicon-150x150.png</url>
	<title>Bill Erickson</title>
	<link>https://www.billerickson.net/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Using DeepInfra models with Codex</title>
		<link>https://www.billerickson.net/using-deepinfra-models-with-codex/</link>
					<comments>https://www.billerickson.net/using-deepinfra-models-with-codex/#respond</comments>
		
		<dc:creator><![CDATA[Bill Erickson]]></dc:creator>
		<pubDate>Wed, 24 Jun 2026 16:00:35 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://www.billerickson.net/?p=8697</guid>

					<description><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-deepinfra-codex-shim-featured-1168x613.png" class="attachment-large size-large wp-image-8699 wp-post-image" alt="" decoding="async" fetchpriority="high" srcset="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-deepinfra-codex-shim-featured-1168x613.png 1168w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-deepinfra-codex-shim-featured-768x403.png 768w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-deepinfra-codex-shim-featured-1536x806.png 1536w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-deepinfra-codex-shim-featured.png 1800w" sizes="(max-width: 768px) 100vw, 768px" /><p>I was using Codex with DeepInfra-hosted models for my benchmarking test and ran into a small compatibility problem. Codex sends requests using OpenAI&#8217;s Responses API. DeepInfra provides an OpenAI-compatible API,&#8230;</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/using-deepinfra-models-with-codex/">Using DeepInfra models with Codex</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-deepinfra-codex-shim-featured-1168x613.png" class="attachment-large size-large wp-image-8699 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-deepinfra-codex-shim-featured-1168x613.png 1168w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-deepinfra-codex-shim-featured-768x403.png 768w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-deepinfra-codex-shim-featured-1536x806.png 1536w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-deepinfra-codex-shim-featured.png 1800w" sizes="(max-width: 768px) 100vw, 768px" />
<p>I was using Codex with <a href="https://deepinfra.com" target="_blank" rel="noreferrer noopener">DeepInfra</a>-hosted models for <a href="https://billerickson.net/benchmarking-llms-on-real-client-work/">my benchmarking test</a> and ran into a small compatibility problem.</p>



<p>Codex sends requests using OpenAI&#8217;s Responses API. DeepInfra provides an OpenAI-compatible API, but the endpoint I needed was chat completions. Those formats are close but not enough for Codex to use directly.</p>



<p>So I had Codex build&nbsp;<a href="https://github.com/billerickson/DeepInfra-Codex-Shim">DeepInfra-Codex-Shim</a>, a small local Node.js shim that sits between Codex and DeepInfra. Codex sends a&nbsp;<code>POST /v1/responses</code>&nbsp;request to the shim. The shim converts that request to&nbsp;<code>POST /chat/completions</code>&nbsp;for DeepInfra, then converts the result back into a Responses-shaped object Codex can understand.</p>



<p>This is not a full Responses API implementation. It is a practical bridge for using DeepInfra-hosted coding models with Codex.</p>



<p>The main thing a human has to do is provide a DeepInfra API token. The shim reads&nbsp;<code>DEEPINFRA_TOKEN</code>, and Codex uses that same environment variable in its provider config. Once that token is available, the rest is the kind of setup an AI coding harness can handle &#8211; just point it to this blog post.</p>



<h2 class="wp-block-heading" id="what-the-shim-does">What the shim does</h2>



<p>The shim runs locally:</p>



<pre class="wp-block-code"><code>http:&#47;&#47;127.0.0.1:8797/v1
</code></pre>



<p>Codex points to that local URL. The request path looks like this:</p>



<pre class="wp-block-code"><code>Codex
  -&gt; local shim /v1/responses
  -&gt; DeepInfra /v1/openai/chat/completions
  -&gt; local shim
  -&gt; Responses-shaped output for Codex
</code></pre>



<p>The shim handles the pieces I needed for Codex coding workflows:</p>



<ul class="wp-block-list">
<li>System instructions</li>



<li>User and assistant messages</li>



<li>Function tools</li>



<li>Function calls</li>



<li>Function call outputs</li>



<li>Basic token usage</li>



<li>Model listing</li>



<li>DeepInfra streaming responses converted into Responses-style events for Codex</li>
</ul>



<p>If Codex asks for streaming, the shim sends&nbsp;<code>stream: true</code>&nbsp;to DeepInfra and converts DeepInfra&#8217;s streamed chat-completion chunks into Responses-style server-sent events for Codex.</p>



<p>Text deltas can stream through as they arrive. Tool calls are a little different because chat completions providers send function names and arguments in fragments, so the shim accumulates those fragments and emits a complete Responses&nbsp;<code>function_call</code>&nbsp;item when the upstream stream finishes.</p>



<p>The shim also includes&nbsp;<code>POST /v1/chat/completions</code>&nbsp;as a passthrough endpoint. Codex does not need that endpoint, but it makes smoke testing easier because you can check your DeepInfra token and model before debugging the Responses conversion.</p>



<h2 class="wp-block-heading" id="setup">Setup</h2>



<p>First, put your DeepInfra API token in your shell environment. This is the one part you should do yourself because it is your credential.</p>



<pre class="wp-block-code"><code>export DEEPINFRA_TOKEN="your_deepinfra_api_token"
</code></pre>



<p>For persistent CLI use, add that line to&nbsp;<code>~/.zshrc</code>&nbsp;or your shell profile.</p>



<p>Clone the project:</p>



<pre class="wp-block-code"><code>git clone https://github.com/billerickson/DeepInfra-Codex-Shim.git
cd DeepInfra-Codex-Shim
</code></pre>



<p>Install and run the tests:</p>



<pre class="wp-block-code"><code>npm install
npm test
</code></pre>



<p>Start the shim:</p>



<pre class="wp-block-code"><code>npm start -- --log-requests
</code></pre>



<p>Or run the binary directly:</p>



<pre class="wp-block-code"><code>node bin/deepinfra-codex-shim.js --log-requests
</code></pre>



<p>By default, the shim listens on:</p>



<pre class="wp-block-code"><code>http:&#47;&#47;127.0.0.1:8797/v1
</code></pre>



<p>You can confirm it is running with:</p>



<pre class="wp-block-code"><code>curl http://127.0.0.1:8797/health
</code></pre>



<p>You should see a small JSON response showing the shim is up and pointing at DeepInfra.</p>



<h2 class="wp-block-heading" id="codex-configuration">Codex configuration</h2>



<p>Add a DeepInfra provider to your Codex config. The important line is&nbsp;<code>env_key = "DEEPINFRA_TOKEN"</code>, which tells Codex to use the same token you exported above. Codex config formats may change, so treat this as a template:</p>



<pre class="wp-block-code"><code>&#91;model_providers.deepinfra]
name = "DeepInfra via local shim"
base_url = "http://127.0.0.1:8797/v1"
env_key = "DEEPINFRA_TOKEN"
wire_api = "responses"
</code></pre>



<p>Then create or update a profile that uses that provider:</p>



<pre class="wp-block-code"><code>&#91;profiles.deepinfra]
model_provider = "deepinfra"
model = "deepseek-ai/DeepSeek-V4-Flash"
</code></pre>



<p>Now you can run Codex with a DeepInfra-hosted model:</p>



<pre class="wp-block-code"><code>codex --profile deepinfra --model deepseek-ai/DeepSeek-V4-Flash
</code></pre>



<p>You can swap in any DeepInfra model ID you want to test:</p>



<pre class="wp-block-code"><code>codex --profile deepinfra --model stepfun-ai/Step-3.5-Flash
codex --profile deepinfra --model MiniMaxAI/MiniMax-M2.5
</code></pre>



<p>Model behavior varies. The shim can translate function-call requests and responses, but it cannot make a model reliable at tool use if the model itself does not emit OpenAI-compatible tool calls consistently.</p>



<h2 class="wp-block-heading" id="smoke-testing-deepinfra">Smoke testing DeepInfra</h2>



<p>If Codex fails, test the chat passthrough first:</p>



<pre class="wp-block-code"><code>curl http://127.0.0.1:8797/v1/chat/completions \
  -H "content-type: application/json" \
  -H "authorization: Bearer $DEEPINFRA_TOKEN" \
  -d '{"model":"deepseek-ai/DeepSeek-V4-Flash","messages":&#91;{"role":"user","content":"Reply with exactly OK."}]}'
</code></pre>



<p>If that works, your token, upstream URL, and model are probably fine. Any remaining issue is more likely in the Responses conversion or in how a specific model handles tools.</p>



<p>The repo also includes an optional integration test:</p>



<pre class="wp-block-code"><code>DEEPINFRA_TOKEN=... npm run test:integration
</code></pre>



<p>It makes one harmless request,&nbsp;<code>Reply with exactly OK.</code>, and checks that the shim returns a Codex-compatible response.</p>



<h2 class="wp-block-heading" id="configuration">Configuration</h2>



<p>These are the main environment variables:</p>



<pre class="wp-block-code"><code>DEEPINFRA_CODEX_SHIM_HOST=127.0.0.1
DEEPINFRA_CODEX_SHIM_PORT=8797
DEEPINFRA_CODEX_SHIM_UPSTREAM=https://api.deepinfra.com/v1/openai
DEEPINFRA_CODEX_SHIM_API_KEY_ENV=DEEPINFRA_TOKEN
DEEPINFRA_CODEX_SHIM_LOG_LEVEL=info
DEEPINFRA_CODEX_SHIM_LOG_REQUESTS=false
DEEPINFRA_CODEX_SHIM_LOG_CONTENT=false
DEEPINFRA_CODEX_SHIM_TIMEOUT_MS=120000
DEEPINFRA_CODEX_SHIM_MAX_BODY_BYTES=10485760
DEEPINFRA_CODEX_SHIM_COMPAT_DROP_TOOL_CALL_CONTENT=false
</code></pre>



<p>You can set the same options with CLI flags:</p>



<pre class="wp-block-code"><code>node bin/deepinfra-codex-shim.js \
  --host 127.0.0.1 \
  --port 8797 \
  --upstream https://api.deepinfra.com/v1/openai \
  --api-key-env DEEPINFRA_TOKEN \
  --log-requests
</code></pre>



<p>The default host is&nbsp;<code>127.0.0.1</code>, and I would keep it that way unless you have a specific reason to expose the shim elsewhere. The shim does not add its own authentication layer. It just forwards provider credentials upstream.</p>



<h2 class="wp-block-heading" id="a-compatibility-note">A compatibility note</h2>



<p>During my benchmark tests, I dropped assistant text whenever the assistant also returned tool calls. That helped with some Codex tool-call turns, but it is a surprising default for a public project.</p>



<p>The packaged shim preserves assistant text by default, but it can now be removed with an explicit flag:</p>



<pre class="wp-block-code"><code>node bin/deepinfra-codex-shim.js --compat-drop-tool-call-content
</code></pre>



<h2 class="wp-block-heading" id="what-it-does-not-support">What it does not support</h2>



<p>The shim does not support images, audio, built-in OpenAI tools, web search, file search, reasoning item semantics, persistent response IDs, server-side conversation state, or advanced structured output behavior.</p>



<p>If your workflow is normal Codex coding with text and function/tool calls, the shim should be useful for actual work. If you need full Responses API compatibility, this is not that.</p>



<h2 class="wp-block-heading" id="privacy-and-logging">Privacy and logging</h2>



<p>Codex may send task prompts, repository context, file contents, diffs, commands, command output, and tool results to the model provider you choose.</p>



<p>The shim forwards your DeepInfra token upstream but does not store it. Normal request logging is off. When you run with&nbsp;<code>--log-requests</code>, it logs request shape, model names, tool counts, and request IDs. It does not log full prompts or tool outputs.</p>



<p>There is a&nbsp;<code>--log-content</code>&nbsp;flag for deeper debugging, but it can print code and prompt content. I would only use it locally, briefly, and intentionally.</p>



<p>Tokens and authorization headers are redacted from structured logs and upstream error snippets.</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/using-deepinfra-models-with-codex/">Using DeepInfra models with Codex</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.billerickson.net/using-deepinfra-models-with-codex/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Benchmarking LLMs on Real Client Work</title>
		<link>https://www.billerickson.net/benchmarking-llms-on-real-client-work/</link>
					<comments>https://www.billerickson.net/benchmarking-llms-on-real-client-work/#respond</comments>
		
		<dc:creator><![CDATA[Bill Erickson]]></dc:creator>
		<pubDate>Wed, 24 Jun 2026 16:00:03 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[WordPress Development]]></category>
		<guid isPermaLink="false">https://www.billerickson.net/?p=8693</guid>

					<description><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-llm-benchmark-featured-1168x613.png" class="attachment-large size-large wp-image-8695 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-llm-benchmark-featured-1168x613.png 1168w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-llm-benchmark-featured-768x403.png 768w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-llm-benchmark-featured-1536x806.png 1536w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-llm-benchmark-featured.png 1800w" sizes="(max-width: 768px) 100vw, 768px" /><p>I thought it would be fun to test a variety of open source LLMs on actual client work using Codex cli. I created a benchmark test based on real client&#8230;</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/benchmarking-llms-on-real-client-work/">Benchmarking LLMs on Real Client Work</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-llm-benchmark-featured-1168x613.png" class="attachment-large size-large wp-image-8695 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-llm-benchmark-featured-1168x613.png 1168w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-llm-benchmark-featured-768x403.png 768w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-llm-benchmark-featured-1536x806.png 1536w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-llm-benchmark-featured.png 1800w" sizes="(max-width: 768px) 100vw, 768px" />
<p>I thought it would be fun to test a variety of open source LLMs on actual client work using Codex cli.</p>



<p>I created a benchmark test based on real client requests in existing codebases, then sorted the tasks from easiest to hardest.</p>



<p>The results were surprising. I found that coding benchmarks don&#8217;t necessarily line up with my real work. The smarter models often got lost in a train of thought and didn&#8217;t actually edit the files. </p>



<p>A model that can solve a standalone programming problem is not always useful when the task is &#8220;fix this small client issue in a five-year-old WordPress theme without making a mess.&#8221;</p>



<p>If you plan to do a similar test using Codex cli and DeepInfra, <a href="https://www.billerickson.net/using-deepinfra-models-with-codex/">you might need a shim</a> to connect the two.</p>



<h2 class="wp-block-heading">Summary of Results</h2>



<figure class="wp-block-image alignwide size-full"><img decoding="async" width="1600" height="900" src="https://www.billerickson.net/wp-content/uploads/2026/06/benchmark-results-score-cost.png" alt="" class="wp-image-8696" srcset="https://www.billerickson.net/wp-content/uploads/2026/06/benchmark-results-score-cost.png 1600w, https://www.billerickson.net/wp-content/uploads/2026/06/benchmark-results-score-cost-768x432.png 768w, https://www.billerickson.net/wp-content/uploads/2026/06/benchmark-results-score-cost-1168x657.png 1168w, https://www.billerickson.net/wp-content/uploads/2026/06/benchmark-results-score-cost-1536x864.png 1536w" sizes="(max-width: 1600px) 100vw, 1600px" /></figure>



<p>The top OpenAI models won on quality. That was not surprising. I&#8217;m sure the Anthropic models would also be towards the top, but I wanted to compare my daily driver (gpt-5.5) to open source models.</p>



<p>MiniMax was the biggest price/performance surprise, which led me to do a second benchmark test comparing just those two models.</p>



<p>GLM-5.2&#8217;s poor performance was also unexpected. You can see the Test Details below for more details on its run.</p>



<p>The &#8220;cost&#8221; column shows the actual cost of running this test on DeepInfra based on the token usage. The OpenAI costs are based on their API pricing, but I&#8217;m on one of their paid plans which drastically subsidizes token usage.</p>



<p>I used a free local model (gpt-oss-20b) as a baseline benchmark, and didn&#8217;t expect it to score in the middle of the pack on these tasks.</p>



<h2 class="wp-block-heading">MiniMax Test</h2>



<p>Given the surprising results for the MiniMax models, and how close their scores were in the first test, I created a new benchmark test with six new tasks just to test those two models. All these tasks were medium-to-hard, so the scores are lower.</p>



<p>MiniMax-M2.7 averaged <strong>2.63</strong> across the 6 tasks and spent $0.146. It was cheaper on tokens, but it failed three precision tasks badly.</p>



<p>MiniMax-M2.5 averaged <strong>3.53</strong> across the 6 tasks and spend $0.150. It was slightly more expensive, but materially more reliable on PHP/WordPress tasks.</p>



<p><strong>Summary: </strong>I&#8217;m going to keep using gpt-5.5 as my primary model, but I might try using MiniMax-M2.5 for simpler tasks.<br></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Test Details</h2>



<p>This benchmark tested coding-agent behavior across four WordPress theme tasks using short, natural prompts while starting Codex in the correct theme directory with the CultivateWP standard profile active. Task 1 was a read-only brand-color lookup, Task 2 was a mechanical migration of custom ACF block manifests from apiVersion 2 to 3, Task 3 was a focused footer email/form link styling fix, and Task 4 was a broader post-header implementation. Scores are out of 5 per task, with the average reflecting the four-task run where available.</p>



<p>The clearest pattern was that lookup and mechanical edits were much easier than design-aware implementation. Several models could find `theme.json` or update 23 `block.json` files cleanly, but struggled once the task required choosing the right SCSS/PHP source target, preserving build parity, and stopping with a small reviewable diff.</p>



<h3 class="wp-block-heading" id="zai-orgglm-52">zai-org/GLM-5.2</h3>



<p>GLM-5.2 averaged&nbsp;<code>2.29</code>&nbsp;at an estimated cost of&nbsp;<code>$0.21</code>. It handled the brand-color lookup well after the benchmark metadata issue was corrected, but the implementation tasks exposed a persistent stall pattern. Task 2 produced some clean, narrow manifest edits but did not finish the full set, and Tasks 3 and 4 ended without meaningful implementation before operator caps.</p>



<p>The result was a model that could understand the repo and identify relevant areas, but did not reliably cross from inspection into completed changes. In this benchmark, that made it weak as a hands-off coding agent despite a solid read-only task result.</p>



<p>After the initial poor results, we made some process changes and re-ran the test, which you&#8217;ll find below in <a href="#zai-orgglm-52-v31-guarded">zai-org/GLM-5.2 v3.1 guarded</a>.</p>



<h3 class="wp-block-heading" id="moonshotaikimi-k27-code">moonshotai/Kimi-K2.7-Code</h3>



<p>Kimi-K2.7-Code averaged&nbsp;<code>2.38</code>&nbsp;at an estimated cost of&nbsp;<code>$0.080</code>. It answered the brand-color lookup accurately, but was slow for such a simple read-only task and did extra searching after finding the authoritative file. On the coding tasks, it repeatedly found the right files or target sets, then stalled without producing a usable diff before caps were reached.</p>



<p>This made Kimi one of the more frustrating runs: the exploration often looked promising, but the final worktree did not reflect that understanding. Its low estimated cost helps, but the lack of completed edits on Tasks 2-4 makes it hard to trust for unattended implementation.</p>



<h3 class="wp-block-heading" id="stepfun-aistep-35-flash">stepfun-ai/Step-3.5-Flash</h3>



<p>Step-3.5-Flash averaged&nbsp;<code>2.40</code>, with cost recorded as at least&nbsp;<code>$0.0016</code>&nbsp;because some interrupted tasks did not emit final usage. It was fast and accurate on the brand-color lookup, but Task 2 showed serious scope-control problems. Although it changed the API version, it also removed&nbsp;<code>acf.mode</code>, added&nbsp;<code>customClassName</code>&nbsp;broadly, reformatted files, and kept wandering after the useful part of the edit was done.</p>



<p>Task 3 found helpful context but produced no diff, and Task 4 was skipped after the reduced task set showed below-threshold implementation behavior. The tiny cost is interesting, but the edits were not reviewable enough for real production use.</p>



<h3 class="wp-block-heading" id="nvidianvidia-nemotron-3-ultra-550b-a55b">nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B</h3>



<p>Nemotron Ultra averaged&nbsp;<code>2.81</code>&nbsp;at an estimated cost of&nbsp;<code>$1.57</code>. It did well on the first two tasks: the lookup was accurate, and the ACF block API migration produced a strong final diff. However, those wins came with high token use and some malformed validation commands on the mechanical edit.</p>



<p>The implementation side was the problem. Task 3 did not land a source fix and left behind a backup artifact. Task 4 was skipped for budget protection, so the overall result was a strong start followed by an expensive implementation failure.</p>



<h3 class="wp-block-heading" id="deepseek-aideepseek-v4-pro">deepseek-ai/DeepSeek-V4-Pro</h3>



<p>DeepSeek-V4-Pro averaged&nbsp;<code>2.85</code>&nbsp;at an estimated cost of&nbsp;<code>$3.19</code>. Its first two tasks were excellent from a final-diff perspective: accurate lookup, clean mechanical manifest updates, and good validation shape. The downside was efficiency, especially Task 2, which took about four minutes and nearly 0.9M input tokens for a straightforward file-wide version bump.</p>



<p>Task 3 ran for more than 11 minutes without producing a diff, and Task 4 was not run. The model appeared to be reasoning toward a plausible source target, but for this benchmark the scored deliverable was the finished worktree, not the direction of travel. The cost-to-output ratio was poor.</p>



<h3 class="wp-block-heading" id="qwenqwen36-35b-a3b">Qwen/Qwen3.6-35B-A3B</h3>



<p>Qwen3.6-35B-A3B averaged&nbsp;<code>2.86</code>&nbsp;at an estimated cost of&nbsp;<code>$0.624</code>. It completed the lookup correctly and did make real edits on Tasks 2 and 3, but those edits were not especially disciplined. Task 2 finished the migration but introduced unnecessary support and context changes, making the diff riskier than the clean one-line manifest updates from stronger runs.</p>



<p>Task 3 produced a plausible local CSS change, but likely targeted the narrower block CSS path instead of the intended footer form output. It also burned more than 2M input tokens on that scoped styling task. Task 4 was effectively a no-op on the actual worktree, which kept the overall score below the stronger partial implementers.</p>



<h3 class="wp-block-heading" id="gpt-oss-20b">gpt-oss-20b</h3>



<p>The local&nbsp;<code>gpt-oss-20b</code>&nbsp;averaged&nbsp;<code>2.93</code>&nbsp;and was free to run locally. It remained a useful baseline: accurate on the brand-color lookup, clean on the mechanical&nbsp;<code>block.json</code>&nbsp;migration, and much better behaved than several paid hosted models on artifact hygiene. It avoided backup files and did not dump compiled CSS during the footer exploration.</p>



<p>Its weakness was implementation follow-through. Task 3 identified relevant block files but never edited CSS or SCSS, and Task 4 returned almost immediately with no useful work. The model was cheap and tidy, but not capable enough for the two realistic coding tasks in this benchmark.</p>



<h3 class="wp-block-heading" id="zai-orgglm-52-v31-guarded">zai-org/GLM-5.2 v3.1 guarded</h3>



<p>The guarded GLM-5.2 rerun averaged&nbsp;<code>3.06</code>, with cost recorded as at least&nbsp;<code>$0.14</code>. The neutral guardrail materially improved the mechanical ACF block task: Task 2 went from incomplete in the original GLM run to a clean, correct migration. That suggests GLM&#8217;s failures were partly sensitive to task framing and agent-loop behavior, not just inability to understand the repo.</p>



<p>The guardrail did not fix broader implementation reliability. Tasks 3 and 4 still burned time on inspection and ended without useful diffs before operator caps. It was a meaningful improvement over the original GLM run, but not enough to make it competitive with the better implementation performers.</p>



<h3 class="wp-block-heading" id="deepseek-aideepseek-v4-flash">deepseek-ai/DeepSeek-V4-Flash</h3>



<p>DeepSeek-V4-Flash averaged&nbsp;<code>3.51</code>&nbsp;at an estimated cost of&nbsp;<code>$0.52</code>. It was strong on the lookup and mechanical API migration, producing scoped, valid changes. Compared with V4-Pro, it was cheaper and ultimately more useful in this benchmark because it completed all four tasks and produced real implementation diffs.</p>



<p>The implementation quality was still only partial. Task 3 was syntactically clean but likely missed the intended footer form link target, and included a risky truncate-and-recover episode. Task 4 refreshed visible post-header styling with source and compiled CSS, but missed deeper template and recipe-card compatibility behavior expected by the golden path.</p>



<h3 class="wp-block-heading" id="gpt-53-codex-spark">gpt-5.3-codex-spark</h3>



<p><code>gpt-5.3-codex-spark</code>&nbsp;averaged&nbsp;<code>3.53</code>; cost was marked&nbsp;<code>n/a</code>&nbsp;because no official API price was available. It performed well on the lookup and mechanical manifest update, with a clean final diff on Task 2 despite some failed shell attempts and an inaccurate final file count. As with many models, the simple tasks were much stronger than the implementation tasks.</p>



<p>Task 3 attempted the right area but produced brittle styling work: malformed source SCSS, hand-appended compiled CSS, and a link color that likely failed the readability goal in the dark footer. Task 4 produced valid PHP and some useful conditional behavior, but skipped source SCSS and broader recipe-template compatibility. It was a real coding run, just not a clean one.</p>



<h3 class="wp-block-heading" id="minimaxaiminimax-m27">MiniMaxAI/MiniMax-M2.7</h3>



<p>MiniMax-M2.7 averaged&nbsp;<code>3.63</code>&nbsp;at an estimated cost of&nbsp;<code>$0.079</code>. It was one of the strongest price/performance results in the benchmark. The lookup was accurate and very fast, and Task 2 produced a correct final migration with clean validation, though it initially missed the nested manifest before recovering.</p>



<p>The implementation tasks were real but partial. Task 3 made a plausible local block CSS fix, but probably missed the actual footer form path. Task 4 improved PHP structure and source SCSS more cleanly than M2.5, but did not update compiled CSS or address the broader WPRM/template compatibility expected by the reference implementation. For the cost, it was impressive; for production use, it still needed careful review.</p>



<h3 class="wp-block-heading" id="minimaxaiminimax-m25">MiniMaxAI/MiniMax-M2.5</h3>



<p>MiniMax-M2.5 averaged&nbsp;<code>3.69</code>&nbsp;at an estimated cost of&nbsp;<code>$0.096</code>. It was the biggest pleasant surprise of the DeepInfra set. The model was accurate on the lookup, produced an exactly correct API-version diff on Task 2, and generally delivered useful edits at low cost.</p>



<p>Its weaker side was implementation precision. Task 3 produced a diff instead of stalling, but likely targeted the wrong footer form source path and dumped a large compiled CSS file into the transcript. Task 4 touched PHP, source SCSS, and compiled CSS, but missed major golden-path behavior around metadata helpers and WPRM recipe-template compatibility, and validation left some artifact concerns.</p>



<h3 class="wp-block-heading" id="gpt-54">gpt-5.4</h3>



<p>GPT-5.4 averaged&nbsp;<code>4.19</code>&nbsp;at an estimated cost of&nbsp;<code>$2.69</code>. It was strong across the first two tasks, including a sourced brand-color answer and an exact, well-scoped ACF block API edit. It spent more tokens than necessary on the simple migration, but the final work was correct and reviewable.</p>



<p>Its implementation tasks were meaningfully better than the open-model field, though still not perfect. Task 3 likely fixed the front-end readability issue, but did so with a new enqueued stylesheet and broader WPForms targeting than the reference. Task 4 delivered valid PHP and runtime CSS, but missed several golden touch points.</p>



<h3 class="wp-block-heading" id="gpt-55">gpt-5.5</h3>



<p>GPT-5.5 was the top performer, averaging&nbsp;<code>4.40</code>&nbsp;at an estimated cost of&nbsp;<code>$6.58</code>. It was accurate and well sourced on the lookup, produced the expected tracked diff for the ACF block API migration, and avoided backup artifacts. It was not perfectly efficient, but the final outputs were consistently more useful than the rest of the field.</p>



<p>The most important difference was that GPT-5.5 produced the strongest implementation results. Task 3 was functional, scoped, accessibility-conscious, and kept source SCSS and compiled CSS in sync. Task 4 was a genuine hard-task implementation with focused files, clean PHP syntax, compiled front-end CSS, and reasonable use of existing helpers. It still missed parts of the historical golden direction, but it was the best practical coding-agent result in the benchmark.</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/benchmarking-llms-on-real-client-work/">Benchmarking LLMs on Real Client Work</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.billerickson.net/benchmarking-llms-on-real-client-work/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Use Hermes Desktop with your remote Hermes agent</title>
		<link>https://www.billerickson.net/use-hermes-desktop-with-your-remote-hermes-agent/</link>
					<comments>https://www.billerickson.net/use-hermes-desktop-with-your-remote-hermes-agent/#respond</comments>
		
		<dc:creator><![CDATA[Bill Erickson]]></dc:creator>
		<pubDate>Tue, 23 Jun 2026 19:40:33 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Hermes]]></category>
		<guid isPermaLink="false">https://www.billerickson.net/?p=8700</guid>

					<description><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-hermes-desktop-remote-agent-featured-1168x613.png" class="attachment-large size-large wp-image-8702 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-hermes-desktop-remote-agent-featured-1168x613.png 1168w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-hermes-desktop-remote-agent-featured-768x403.png 768w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-hermes-desktop-remote-agent-featured-1536x806.png 1536w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-hermes-desktop-remote-agent-featured-800x420.png 800w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-hermes-desktop-remote-agent-featured-440x231.png 440w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-hermes-desktop-remote-agent-featured.png 1800w" sizes="(max-width: 768px) 100vw, 768px" /><p>I run Hermes on a separate computer instead of my daily machine. The remote machine can stay online all the time, keep scheduled jobs running, receive messages from Telegram or&#8230;</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/use-hermes-desktop-with-your-remote-hermes-agent/">Use Hermes Desktop with your remote Hermes agent</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-hermes-desktop-remote-agent-featured-1168x613.png" class="attachment-large size-large wp-image-8702 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-hermes-desktop-remote-agent-featured-1168x613.png 1168w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-hermes-desktop-remote-agent-featured-768x403.png 768w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-hermes-desktop-remote-agent-featured-1536x806.png 1536w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-hermes-desktop-remote-agent-featured-800x420.png 800w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-hermes-desktop-remote-agent-featured-440x231.png 440w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-hermes-desktop-remote-agent-featured.png 1800w" sizes="(max-width: 768px) 100vw, 768px" />
<p>I run <a href="https://hermes-agent.nousresearch.com/" target="_blank" rel="noreferrer noopener">Hermes</a> on a separate computer instead of my daily machine.</p>



<p>The remote machine can stay online all the time, keep scheduled jobs running, receive messages from Telegram or Mattermost, and handle long-running tasks without depending on whether my laptop is awake.</p>



<p>There’s also a security and separation benefit. Hermes can use tools, run terminal commands, read files, manage credentials, and connect to other services. I’d rather keep that on a dedicated machine with a narrower purpose than mix it into the computer I use all day.</p>



<p>I happened to have an unused mini PC, so I set it up there. If you don’t have spare hardware, you can also run Hermes on a small cloud server like DeepInfra, which is about $13/month, or a DigitalOcean droplet, which is closer to $24/month.</p>



<p>I use <a href="https://tailscale.com/">Tailscale</a> to put my Hermes computer, desktop, NAS, laptop, and phone on the same private network. That lets me reach each device from anywhere without opening it up to the public internet. My Hermes box still sits at home, but I can connect to it from my laptop whenever I’m away.</p>



<p>I use Telegram and Mattermost with Hermes occasionally, but most of my interaction has been through the terminal. That works well, but it also means SSHing into the machine, opening a shell, and using Hermes from there.</p>



<p>I recently found out the Hermes Desktop app can connect to a remote Hermes agent. You still install the desktop app locally, but instead of using the local Hermes instance it installs, you point it at the dashboard running on your remote Hermes machine.</p>



<p>That gives you the nicer desktop UI while keeping the actual Hermes runtime on your always-on machine.</p>



<h2 class="wp-block-heading"><strong>Enable the Hermes dashboard on the remote machine</strong></h2>



<p>The desktop app connects to the Hermes dashboard backend, not the messaging gateway used by Telegram, Mattermost, and Slack.</p>



<p>On the remote Hermes machine, first configure dashboard authentication. If you’re connecting over Tailscale and not exposing it to the public internet, the simplest option is username/password basic auth.</p>



<p>Stop any dashboard currently running in insecure mode:</p>



<pre class="wp-block-code"><code>pkill -f 'hermes dashboard.*9119'</code></pre>



<p>Then create a dashboard username, password hash, and stable signing secret:</p>



<pre class="wp-block-code"><code>cd ~/.hermes/hermes-agent

HASH=$(venv/bin/python3 -c 'from getpass import getpass; from plugins.dashboard_auth.basic import hash_password; print(hash_password(getpass("Dashboard password: ")))')
SECRET=$(openssl rand -base64 32)

cat >> ~/.hermes/.env &lt;&lt;EOF
HERMES_DASHBOARD_BASIC_AUTH_USERNAME=admin
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASH=$HASH
HERMES_DASHBOARD_BASIC_AUTH_SECRET=$SECRET
EOF

chmod 600 ~/.hermes/.env</code></pre>



<p>The stable <code>HERMES_DASHBOARD_BASIC_AUTH_SECRET</code> matters because it keeps your desktop session valid across dashboard restarts. Without it, you’ll get logged out every time the dashboard restarts.</p>



<p>Now start the dashboard on an address your desktop can reach. </p>



<p><em>In both of the following snippets, change <code>&lt;tailscale-ip></code> to your actual tailscale IP.</em></p>



<pre class="wp-block-code"><code>hermes dashboard --no-open --host &lt;tailscale-ip> --port 9119</code></pre>



<p>Verify that authentication is enabled:</p>



<pre class="wp-block-code"><code>curl -s http://&lt;tailscale-ip>:9119/api/status | jq '.auth_required, .auth_providers'</code></pre>



<p>You want to see:</p>



<pre class="wp-block-code"><code>true
&#91;
  "basic"
]</code></pre>



<p>If <code>auth_required</code> is false, the auth gate isn’t active. If <code>basic</code> is missing, the dashboard did not load your username/password settings.</p>



<p>For long-term use, keep the dashboard running under systemd, tmux, or whatever process manager you use on that machine. The desktop app can only connect while the dashboard process is running.</p>



<h2 class="wp-block-heading"><strong>Install Hermes Desktop locally</strong></h2>



<p>Next, install Hermes Desktop on your laptop or desktop computer.</p>



<p>The app will install Hermes locally. That’s fine, but it’s not the Hermes instance we’re going to use. The local install is mainly there so the desktop shell can run.</p>



<p>Once the app opens and gets to the local setup flow, go to: Settings → Gateway and select &#8220;Remote Gateway&#8221;.</p>



<p>Then paste your remote dashboard URL. If you’re on the same local network, that might be something like <code>http://192.168.1.50:9119</code>, but I prefer using the Tailscale IP: <code>http://100.x.y.z:9119</code></p>



<p>After entering the URL, sign in with the basic auth credentials you created earlier. User = admin, password = whatever you typed in.</p>



<p>Save and reconnect. Hermes Desktop should now be talking to your remote Hermes agent instead of the local one.</p>



<h2 class="wp-block-heading"><strong>Why this setup is better</strong></h2>



<p>The remote machine is responsible for the actual Hermes runtime: tools, memory, sessions, cron jobs, gateway connections, project files, and long-running work.</p>



<p>Your laptop or desktop computer gets the interface: a native app, easier session browsing, model controls, file previews, and a nicer way to chat with the same agent.</p>



<p>You can still use the terminal when you want, and you can still use Telegram or Mattermost when that’s more convenient. But for longer working sessions, Hermes Desktop gives you a better front end.</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/use-hermes-desktop-with-your-remote-hermes-agent/">Use Hermes Desktop with your remote Hermes agent</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.billerickson.net/use-hermes-desktop-with-your-remote-hermes-agent/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Migrating from WordPress to Astro</title>
		<link>https://www.billerickson.net/migrating-from-wordpress-to-astro/</link>
		
		<dc:creator><![CDATA[Bill Erickson]]></dc:creator>
		<pubDate>Fri, 19 Jun 2026 15:47:56 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Astro]]></category>
		<category><![CDATA[Website Owners]]></category>
		<guid isPermaLink="false">https://www.billerickson.net/?p=8691</guid>

					<description><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-wordpress-to-astro-featured-1168x613.png" class="attachment-large size-large wp-image-8698 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-wordpress-to-astro-featured-1168x613.png 1168w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-wordpress-to-astro-featured-768x403.png 768w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-wordpress-to-astro-featured-1536x806.png 1536w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-wordpress-to-astro-featured.png 1800w" sizes="(max-width: 768px) 100vw, 768px" /><p>A practical look at when Astro can replace WordPress, using a client migration to explain content inventory, SEO, forms, performance, and tradeoffs.</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/migrating-from-wordpress-to-astro/">Migrating from WordPress to Astro</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-wordpress-to-astro-featured-1168x613.png" class="attachment-large size-large wp-image-8698 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-wordpress-to-astro-featured-1168x613.png 1168w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-wordpress-to-astro-featured-768x403.png 768w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-wordpress-to-astro-featured-1536x806.png 1536w, https://www.billerickson.net/wp-content/uploads/2026/06/billerickson-wordpress-to-astro-featured.png 1800w" sizes="(max-width: 768px) 100vw, 768px" />
<p>WordPress is not always the right solution for a website.</p>



<p>That is a strange sentence for me to write. I have spent&nbsp;<strong>20 years</strong>&nbsp;building with WordPress, and I still think it is the right tool for many publishers, including my clients at&nbsp;<a href="https://cultivatewp.com/" target="_blank" rel="noreferrer noopener">CultivateWP</a>. </p>



<p>In the past, I used WordPress for everything: blogs, business sites,&nbsp;<a href="https://www.billerickson.net/twentyten-crm/" target="_blank" rel="noreferrer noopener">internal CRMs</a>, and even a&nbsp;<a href="https://github.com/billerickson/be-delicious" target="_blank" rel="noreferrer noopener">rebuild of my favorite bookmarking site</a>. But I no longer reach for WordPress automatically.</p>



<p>For many small business and brochure sites, Astro is now a better fit. A site that once needed a CMS, custom fields, plugins, and an admin workflow can often become a simple static site with Markdown content and developer-assisted, or AI-assisted, updates.</p>



<p>I recently rebuilt the&nbsp;<a href="https://sundheim.group/" target="_blank" rel="noreferrer noopener">Sundheim Group</a>&nbsp;site by moving a nine-year-old WordPress site to a static Astro build hosted on Cloudflare.</p>



<figure class="wp-block-image size-large border"><img decoding="async" width="1168" height="613" src="https://www.billerickson.net/wp-content/uploads/2026/06/sundheim-astro-1168x613.jpg" alt="" class="wp-image-8692" srcset="https://www.billerickson.net/wp-content/uploads/2026/06/sundheim-astro-1168x613.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2026/06/sundheim-astro-768x403.jpg 768w, https://www.billerickson.net/wp-content/uploads/2026/06/sundheim-astro-1536x806.jpg 1536w, https://www.billerickson.net/wp-content/uploads/2026/06/sundheim-astro.jpg 1800w" sizes="(max-width: 1168px) 100vw, 1168px" /></figure>



<p><a href="https://hermes-agent.nousresearch.com/" target="_blank" rel="noreferrer noopener">My Hermes agent</a>&nbsp;did the first pass: creating an inventory of the old site&#8217;s content, creating a design brief from the existing WordPress site, and rebuilding the content with Astro components. The important work came next: reviewing what transferred well, fixing what did not, and rebuilding the pieces that WordPress plugins used to handle.</p>



<h2 class="wp-block-heading">When Astro is a good fit</h2>



<p>Astro is a good fit when the public site is mostly static and the editing workflow does not need to happen in a CMS.</p>



<p>That describes a lot of business websites. WordPress is often being used as a database for simple pages that only change a few times a year.</p>



<p>The Sundheim Group site was a good example. We built the original site in 2017. Since then, only minimal content changes had been made, and they were done by sending me Word docs. </p>



<p>Doug’s active writing and podcast work had moved to Substack. The WordPress site had become the front door: services, case studies, credibility, and contact.</p>



<p>Keeping WordPress live meant managed hosting, plugin updates, backups, security patches, and an admin backend that was rarely used.</p>



<p>Astro let us keep the public website and remove the ongoing CMS burden. The new site is hosted for free on Cloudflare.</p>



<h2 class="wp-block-heading">Start with an inventory</h2>



<p>The first step is not design. It is inventory.</p>



<p>Before rebuilding anything, I wanted to know exactly what existed on the old site:</p>



<ul class="wp-block-list">
<li>public URLs</li>



<li>content types</li>



<li>taxonomies</li>



<li>redirects</li>



<li>embedded forms and scripts</li>



<li>images and files</li>



<li>one-off templates</li>



<li>SEO metadata</li>
</ul>



<p>WordPress SEO plugin sitemaps are useful for this. They list posts, pages, custom post types, taxonomies, and last modified dates. I turned that into a&nbsp;<code>Migration Inventory.md</code>&nbsp;file that described the site structure and gave the agent a concrete migration target.</p>



<h2 class="wp-block-heading">Create the design brief</h2>



<p>The goal was not to redesign Sundheim Group from scratch.</p>



<p>The old site already had an established, professional visual identity. The rebuild needed to preserve that feel while replacing the implementation with cleaner, reusable pieces.</p>



<p>I created a&nbsp;<a href="https://github.com/google-labs-code/design.md" target="_blank" rel="noreferrer noopener">DESIGN.md file</a>&nbsp;from the existing website. It described the typography, color palette, page structure, spacing, components, and design guardrails.</p>



<p>Once the content inventory and design brief were in place, the agent had enough context to build the first Astro version.</p>



<h2 class="wp-block-heading">Don&#8217;t skip the QA process</h2>



<p>You can copy the obvious pages and still miss the behavior hiding in the content.</p>



<p>Old WordPress sites often contain newsletter embeds, Gravity Forms, survey scripts, YouTube shortcodes, tracking snippets, unexpected image paths, redirects, and one-off page templates. </p>



<p>On this project, the first Astro pass moved the visible content over but left broken functionality as static markup in the page. The cleanup pass found the important details:</p>



<ul class="wp-block-list">
<li>The contact page needed a real replacement form.</li>



<li>The newsletter page needed a working Substack signup form instead of stale Gravity Forms markup.</li>



<li>The assessment and speaking pages had third-party embeds that needed to be preserved deliberately.</li>



<li>A video page had an old YouTube shortcode that needed to become a responsive HTTPS iframe.</li>
</ul>



<p>I extracted the contact form into a reusable Astro component. Submissions post to a Cloudflare Worker, which verifies a Turnstile captcha server-side, normalizes the fields, and sends the notification through Cloudflare’s Email Service&nbsp;<code>send_email</code>&nbsp;binding.</p>



<p>The form does not need SMTP or a paid transactional email service. It uses a verified Cloudflare Email Routing destination address. In Cloudflare,&nbsp;<a href="https://developers.cloudflare.com/email-service/configuration/email-routing-addresses/#destination-addresses" target="_blank" rel="noreferrer noopener">destination addresses</a>&nbsp;are account-level verified recipients. The same verified address can receive direct sends from a Worker.</p>



<p>For the newsletter page, I inspected the live Substack form and rebuilt a simple static form that posts to Substack’s real endpoint.</p>



<p>For the assessment page, I kept the SurveyGizmo script because that page’s purpose was the external assessment. The difference is that it now lives in a route-specific component instead of being buried inside a large escaped content blob.</p>



<h2 class="wp-block-heading">SEO still matters</h2>



<p>Astro gives you a clean slate, which is both helpful and dangerous.</p>



<p>A static site can be very SEO-friendly, but not if every page hand-rolls its own title tag, canonical URL, Open Graph tags, and schema.</p>



<p>Joost&#8217;s&nbsp;<a href="https://joost.blog/astro-seo-complete-guide/" target="_blank" rel="noreferrer noopener">Astro SEO: the definitive guide</a>&nbsp;really helped me with this. He recommends centralizing SEO instead of sprinkling tags throughout the site.</p>



<p>On these Astro builds, that means:</p>



<ul class="wp-block-list">
<li>a shared layout-level SEO layer</li>



<li>canonical URLs generated from the configured production site URL</li>



<li>robots handling that can noindex staging while allowing production indexing</li>



<li><code>@astrojs/sitemap</code>&nbsp;for sitemap output</li>



<li>RSS feeds for blog or article content</li>



<li>JSON-LD structured data for the site, person or organization, pages, breadcrumbs, and blog surfaces</li>



<li>build-time checks for metadata length, H1s, image alt text, and internal links where possible</li>
</ul>



<p>SEO should be part of the site system, not something you remember to paste into individual pages.</p>



<h2 class="wp-block-heading">PageSpeed improves, but not automatically</h2>



<p>Astro starts with a big advantage: static HTML, minimal JavaScript, and assets that can be served from a CDN. That does not mean there&#8217;s no room for improvement.</p>



<p>On Sundheim Group, the old WordPress site scored 35 for mobile performance in PageSpeed Insights. After the initial Astro build, the score was around 85. I eventually reached 100 across Performance, Accessibility, Best Practices, and SEO by fixing the boring details:</p>



<ul class="wp-block-list">
<li>optimizing images and generating lighter WebP versions</li>



<li>adding responsive image variants for large hero images</li>



<li>removing unnecessary JavaScript from pages that did not need it</li>



<li>making embedded video responsive instead of layout-breaking</li>



<li>replacing custom fonts with a&nbsp;<a href="https://modernfontstacks.com/" target="_blank" rel="noreferrer noopener">modern font stack</a> (although the client pushed back and had me reverse this one)</li>
</ul>



<p>Astro makes a fast site easier to build, but PageSpeed still rewards the same practical work: image sizes, font loading, layout stability, and delay/defer scripts.</p>



<p>AI agents are great at optimizing Astro sites because Lighthouse gives them a tight feedback loop and they are in control of the entire stack. They can run the audit, identify the next bottleneck, make a focused change, and test again.</p>



<p>That is much harder on WordPress. I can hyper-optimize a WordPress theme, but a plugin or script added after launch can wipe out those gains.</p>



<h2 class="wp-block-heading" id="the-tradeoff-is-editing">The tradeoff is editing</h2>



<p>The tradeoff is editing convenience.</p>



<p>WordPress gives non-technical users an admin UI. Astro gives you a simpler, faster, safer public site, but content changes usually move into Git, Markdown, code review, and deployment.</p>



<p>For many business clients, that is not a problem. If they only make minor content changes a few times a year, emailing me directly can be easier than logging into WordPress. The cost savings on hosting and plugins can also exceed the cost of having me make those occasional updates.</p>



<p>But this is not a universal recommendation.</p>



<p>For active publishers, the lack of a full CMS is usually a non-starter. Their editorial workflow is built around WordPress, custom fields, roles, previews, revisions, recipe plugins, and team habits. An Astro-based CMS may be interesting, but it will not replace that workflow for most of them.</p>



<h2 class="wp-block-heading" id="my-current-rule">My current approach</h2>



<p>I still recommend WordPress when the site needs an editorial workflow, like most of my clients at <a href="https://cultivatewp.com">CultivateWP</a>.</p>



<p>I recommend Astro when the site is mostly static, the content can live in Markdown, and the owner values speed, security, low maintenance, and simple infrastructure more than a browser-based editing experience.</p>



<p>The key is to decide that up front. You should clearly communicate the pros and cons to the client, and let them make the decision.</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/migrating-from-wordpress-to-astro/">Migrating from WordPress to Astro</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>A starter theme for building Hybrid WordPress Themes</title>
		<link>https://www.billerickson.net/hybrid-wordpress-theme-starter/</link>
		
		<dc:creator><![CDATA[Bill Erickson]]></dc:creator>
		<pubDate>Mon, 29 May 2023 17:17:54 +0000</pubDate>
				<category><![CDATA[Advanced Custom Fields]]></category>
		<category><![CDATA[Gutenberg Block Editor]]></category>
		<category><![CDATA[WordPress Development]]></category>
		<guid isPermaLink="false">https://www.billerickson.net/?p=8661</guid>

					<description><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2023/05/be-starter-featured-image-1168x613.jpg" class="attachment-large size-large wp-image-8662 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2023/05/be-starter-featured-image-1168x613.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2023/05/be-starter-featured-image-768x403.jpg 768w, https://www.billerickson.net/wp-content/uploads/2023/05/be-starter-featured-image-1536x806.jpg 1536w, https://www.billerickson.net/wp-content/uploads/2023/05/be-starter-featured-image.jpg 1800w" sizes="(max-width: 768px) 100vw, 768px" /><p>A hybrid WordPress theme uses theme.json to define styles and customize the block editor while also using traditional PHP template files. Hybrid themes leverage the block editor for content but&#8230;</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/hybrid-wordpress-theme-starter/">A starter theme for building Hybrid WordPress Themes</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2023/05/be-starter-featured-image-1168x613.jpg" class="attachment-large size-large wp-image-8662 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2023/05/be-starter-featured-image-1168x613.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2023/05/be-starter-featured-image-768x403.jpg 768w, https://www.billerickson.net/wp-content/uploads/2023/05/be-starter-featured-image-1536x806.jpg 1536w, https://www.billerickson.net/wp-content/uploads/2023/05/be-starter-featured-image.jpg 1800w" sizes="(max-width: 768px) 100vw, 768px" />
<p>A <strong>hybrid WordPress theme</strong> uses theme.json to define styles and customize the block editor while also using traditional PHP template files.</p>



<p>Hybrid themes leverage the block editor for <em>content</em> but not for building the theme itself. <strong>Block themes</strong> use the new Site Editor for building and customizing the theme directly in the block editor.</p>



<p class="has-text-align-left"><a href="https://github.com/billerickson/BE-Starter">BE Starter</a> is a free starter theme to help you build hybrid WordPress themes.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><a href="https://github.com/billerickson/BE-Starter"><img decoding="async" width="1168" height="876" src="https://www.billerickson.net/wp-content/uploads/2023/05/theme-screenshot-1168x876.jpg" alt="" class="wp-image-8663" style="width:450px" srcset="https://www.billerickson.net/wp-content/uploads/2023/05/theme-screenshot-1168x876.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2023/05/theme-screenshot-768x576.jpg 768w, https://www.billerickson.net/wp-content/uploads/2023/05/theme-screenshot.jpg 1200w" sizes="(max-width: 1168px) 100vw, 1168px" /></a></figure></div>


<p>At <a href="https://cultivatewp.com">CultivateWP</a> we&#8217;ve built hybrid themes since before the block editor officially shipped in WordPress 5.0, and have built hundreds of hybrid themes for clients. We have an internal starter theme we use on all projects, which is continually updated with new features, improvements, and fixes with each major WP release. </p>



<p>BE Starter is a copy of our Cultivate Starter theme, but without all the food-blogger-specific functionality.</p>



<p>By the way, we’ll be growing our development team soon, so if you enjoy working on hybrid WordPress themes, <a href="https://www.billerickson.net/contact/">let me </a><a href="https://cultivatewp.com/careers/">k</a><a href="https://www.billerickson.net/contact/">now</a>!</p>



<h2 class="wp-block-heading">Starter theme features</h2>



<h3 class="wp-block-heading">Define colors and styles with theme.json</h3>



<p>We use a fully tokenized design process so every key in theme.json maps to a Figma design token. </p>



<p>The designers will customize the tokens to create a site&#8217;s style guide (ex: change all the brand colors, tweak the box shadows, adjust the font sizes). As they design the site, everything will be styled using these tokens. </p>



<p>When it&#8217;s time to develop the site, the developer customizes the theme.json file based on the settings in Figma Design Tokens. Then it&#8217;s simply a matter of styling elements with the appropriate tokens. For instance, h1 might use the &#8220;Colossal&#8221; font size while h2 uses &#8220;Huge&#8221;.</p>



<p>If custom font families are required, we&#8217;ll use a slug of &#8220;primary&#8221; and <a href="https://github.com/WordPress/twentytwentythree/blob/trunk/theme.json#L112-L154">register them like this</a>. This <a href="https://gwfh.mranftl.com/fonts/black-han-sans?subsets=latin">Google Webfonts Helper</a> is really useful for downloading the font files. We try to use primary/secondary/etc instead of overly-specific names for colors and font families so that when we redesign a site, we don&#8217;t end up with something like <code>.has-blue-background-color { color: red; }</code></p>



<p>We use a <a href="https://webaim.org/resources/contrastchecker/">contrast checker</a> to determine which brand colors should use &#8220;foreground&#8221; (a dark color) or white as the text color. Make sure you edit _blocks-core.scss and <a href="https://github.com/billerickson/BE-Starter/blob/master/assets/scss/partials/_blocks-core.scss#L278-L283">include any colors here</a> that should use white text.</p>



<p>Use the <code>settings.custom.layout</code> section of theme.json to set:</p>



<ul class="wp-block-list">
<li><code>content</code>&nbsp;is the content area width used for Content and Content Sidebar layouts. It&#8217;s also used for the content width inside of a group block</li>



<li><code>wide</code>&nbsp;is the site&#8217;s max width used in the site header, site footer, the content area for the &#8220;Full Width Content&#8221; layout, and the &#8220;wide&#8221; alignment option</li>



<li><code>sidebar</code>&nbsp;is the sidebar width</li>



<li><code>padding</code>&nbsp;is the left/right padding on the content area</li>



<li><code>block-gap</code>&nbsp;is the space between blocks (ex: paragraphs and headings)</li>



<li><code>block-gap-large</code>&nbsp;is the top/bottom padding on full width groups with a background color, top/bottom margin on separator block, top/bottom padding on site-inner, and in a few other places that need a larger space than block-gap.</li>
</ul>



<h3 class="wp-block-heading">Build custom blocks using ACF and block.json</h3>



<p>I have another article that goes into more detail about <a href="https://www.billerickson.net/building-acf-blocks-with-block-json/">building ACF blocks with block.json</a>.</p>



<p>The starter theme will make it even easier for you to create these blocks:</p>



<ul class="wp-block-list">
<li>It will automatically register every block in the /blocks folder</li>



<li>It will register the style <code>block-{block name}</code>&nbsp;if a style.css file exists in the block&#8217;s folder</li>



<li>It will include the init.php file if one exists</li>
</ul>



<p>Most of your blocks will only need a block.json file to register the block, a render.php file for the markup of the block, and a style.css file to style the block.</p>



<p>If you have additional code you need to load, you can create an init.php file which will be loaded on the init hook with a priority of 5. For instance, in the <a href="https://github.com/billerickson/BE-Starter/tree/master/blocks/social-links">social links block</a> I&#8217;ve included in the starter theme, the init.php file has a function for retrieving all of the site&#8217;s social links from Yoast SEO.</p>



<p>The blocks built in this method are totally self-contained. When it&#8217;s time to redesign a site, you can copy the entire /blocks directory to the new theme and everything will just work. </p>



<p>The blocks are styled with standard CSS (no SASS compiling) and CSS variables generated from the theme.json file. Ex:<code>background: var(--wp--preset--color--primary);</code></p>



<p>The block&#8217;s CSS file does not pass through WordPress&#8217; filter to auto-prepend them with <code>.editor-styles-wrapper</code>&nbsp;so you may need to add that class on your own for editor specific styling.</p>



<h3 class="wp-block-heading">Block Areas for additional editable content</h3>



<p>We use &#8220;block areas&#8221; to bring the block editor to global areas of the site (ex: sidebar, after post). </p>



<p>While you could use block-based widget areas in a similar way, I prefer a CPT because we add an admin body class of <code>.block-area-{block area name}</code>&nbsp;and can <a href="https://github.com/billerickson/BE-Starter/blob/master/assets/scss/editor-layout.scss#L6-L19">adjust the content width</a> to match how it will render on the frontend, so the &#8220;Sidebar&#8221; block area is 336px wide.</p>



<p>The theme includes a custom post type called &#8220;Block Areas&#8221; which appears under the Appearance section of the WP backend. It includes an ACF field group for linking a block area post to a theme-registered block area (sidebar, after-post, before-footer, 404).</p>



<p>To create the sidebar block area:</p>



<ul class="wp-block-list">
<li>Go to Appearance &gt; Block Areas.</li>



<li>Click &#8220;Add New&#8221; and create a block area with any name (ex: &#8220;Sidebar&#8221;). In the right column, under &#8220;Assigned To&#8221; select &#8220;Sidebar&#8221;.</li>



<li>Publish the post and reload the page. You&#8217;ll see the content area now matches the width of the actual sidebar in the theme ( <code>settings.custom.layout.sidebar</code>&nbsp;in theme.json).</li>
</ul>



<p>You can add/edit/remove the registered block areas in inc/block-areas.php. To display a block area in your theme, use <code>Block_Areas\show( $block_area_name )</code>.</p>



<h3 class="wp-block-heading">SASS compiled using Node.js</h3>



<p>Here&#8217;s a guide with more information on <a href="https://sprucecss.com/blog/the-simplest-sass-compile-setup/">the SASS Compile Setup</a>. </p>



<p>Once you have the theme set up in your local environment, run <code>npm install</code>&nbsp;to install the SASS package.  You can then run <code>npm run sass-dev</code>&nbsp;to automatically compile expanded CSS as changes are made to the SASS partials.</p>



<p>When you&#8217;re ready to commit your changes to a repo or push the theme live, you can run <code>npm run sass-prod</code>&nbsp;which compiles minified CSS.</p>



<h3 class="wp-block-heading">Use <code>be_icon()</code>&nbsp;for loading inline SVGs</h3>



<p>Include your SVG files in assets/icons/utility or create additional icon folders (ex: assets/icons/category). I recommend using <a href="https://github.com/svg/svgo">svgo</a> or <a href="https://jakearchibald.github.io/svgomg/">SVGOMG</a> to minify and clean them up.</p>



<p>Make sure the SVGs <strong>do not</strong> have a width and height attribute, but instead use the viewBox, and have the same width and height (ie: square artboard). Example: </p>



<pre class="wp-block-code"><code>&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;</code></pre>



<p>You can display these icons using the <code>be_icon()</code>&nbsp;function. For instance, if you want to display a 24&#215;24 facebook icon, use <code>be_icon( [ 'icon' =&gt; 'facebook', 'size' =&gt; 24 ] );</code></p>



<p>In addition to making it easy to load inline SVGs, this function includes smart caching to minimize the page load time. </p>



<p>Let&#8217;s say you have a heart icon that appears next to every post title on an archive page to save it. If you were to use an inline SVG, you&#8217;d load the full markup of that SVG every time that SVG appears. </p>



<p>Instead, when you call the &#8220;heart&#8221; icon with be_icon() it outputs <code>&lt;svg&gt;&lt;use href="#utility-heart"&gt;&lt;/use&gt;&lt;/svg&gt;</code>&nbsp;and then loads the heart icon once in the site footer with that ID.</p>



<p>If you have a complicated SVG that is having issues displaying with this smart caching (usually because it has its own IDs inside it) you can include <code>'force' =&gt; true</code>&nbsp;to skip this and load the full markup inline.</p>



<p><a href="https://github.com/billerickson/BE-Starter/blob/master/inc/helper-functions.php#L97-L126">See the be_icon() function</a> for all the available settings.</p>



<h3 class="wp-block-heading">Full Site Editing blocks have been removed</h3>



<p>Inside the <a href="https://github.com/billerickson/BE-Starter/blob/master/assets/js/editor.js">editor.js file</a> we&#8217;re unregistering a lot of core WP blocks that are related to full site editing. </p>



<h3 class="wp-block-heading">Login Logo</h3>



<p>Inside inc/login-logo.php you can customize the <code>$logo_path</code>, <code>$logo_width</code>, and <code>$logo_height</code>&nbsp;variables, and it will update the logo on the WP login page for you.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Summary</h2>



<p>The hybrid theme approach has radically improved our design and development process. Our new&nbsp;<a href="https://cultivatewp.com/cultivate-pro/">Cultivate Pro</a>&nbsp;service is only possible because of theme.json, Figma design tokens, and self-contained, reusable ACF blocks.</p>



<p>We’re able to build a fully custom WordPress theme in 8-12 hours rather than the 30-40hrs it used to take. Clients love how deeply integrated the block editor is in our themes. The color palettes use their brand colors, the font sizes and families use their style guide, and they’re able to edit all the content on the site – from simple posts, to the homepage, to the sidebar – using the block editor.</p>



<p>I’m excited to explore Full Site Editing and watch it mature over the next few years, but our team will be using hybrid WordPress themes for client work for the foreseeable future.</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/hybrid-wordpress-theme-starter/">A starter theme for building Hybrid WordPress Themes</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Building ACF blocks with block.json</title>
		<link>https://www.billerickson.net/building-acf-blocks-with-block-json/</link>
		
		<dc:creator><![CDATA[Bill Erickson]]></dc:creator>
		<pubDate>Thu, 25 Aug 2022 15:58:20 +0000</pubDate>
				<category><![CDATA[Advanced Custom Fields]]></category>
		<category><![CDATA[Gutenberg Block Editor]]></category>
		<category><![CDATA[WordPress Development]]></category>
		<guid isPermaLink="false">https://www.billerickson.net/?p=8641</guid>

					<description><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2022/08/block-json-1168x613.jpg" class="attachment-large size-large wp-image-8642 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2022/08/block-json-1168x613.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2022/08/block-json-768x403.jpg 768w, https://www.billerickson.net/wp-content/uploads/2022/08/block-json-1536x806.jpg 1536w, https://www.billerickson.net/wp-content/uploads/2022/08/block-json.jpg 1800w" sizes="(max-width: 768px) 100vw, 768px" /><p>ACF 6.0 includes a major improvement to the way blocks are built. It now supports using block.json, which aligns with WordPress core&#8217;s preferred method for block registration. Why is this&#8230;</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/building-acf-blocks-with-block-json/">Building ACF blocks with block.json</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2022/08/block-json-1168x613.jpg" class="attachment-large size-large wp-image-8642 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2022/08/block-json-1168x613.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2022/08/block-json-768x403.jpg 768w, https://www.billerickson.net/wp-content/uploads/2022/08/block-json-1536x806.jpg 1536w, https://www.billerickson.net/wp-content/uploads/2022/08/block-json.jpg 1800w" sizes="(max-width: 768px) 100vw, 768px" />
<p>ACF 6.0 includes a <em>major</em> improvement to the way blocks are built. It now supports using block.json, which aligns with WordPress core&#8217;s preferred method for block registration. </p>



<p><strong>Why is this important? </strong>As WordPress ships new features for blocks, you can start using them right away. Blocks are registered &#8220;the WordPress way&#8221; so support all WP core features. You don&#8217;t have to wait for ACF to add support for a new feature.</p>



<p><strong>What does this mean for my older blocks?</strong> <a href="https://www.billerickson.net/building-gutenberg-block-acf/">Blocks built using acf_register_block_type()</a> will continue working exactly as expected, and there&#8217;s no need to go back and update older code.  I recommend using the new method for all future blocks though.</p>



<p>To start using this today, you&#8217;ll need to log into your ACF account and download the latest release candidate. </p>



<p>If you&#8217;re having problems with your blocks not showing up, first make sure you&#8217;re on ACF 6.0, then run your block.json file through a JSON validator to see if there are any issues. Unfortunately there&#8217;s no error messages when you have a typo in your JSON file.</p>


<div class="block-toc"><p class="block-toc__header">Table of Contents</p><ol><li><a href="#">Create a block.json file</a></li><ol><li><a href="#">Custom prefixes in block name</a></li><li><a href="#script">Script</a></li><li><a href="#">Style</a></li><li><a href="#script">Script</a></li><li><a href="#">Icon</a></li><li><a href="#">ACF</a></li><li><a href="#">Styles</a></li><li><a href="#">Supports</a></li><li><a href="#">Attributes</a></li></ol><li><a href="#">Register your block</a></li><li><a href="#">Advanced Usage</a></li><li><a href="#">Need Help?</a></li><ol><li><a href="#">Additional information</a></li></ol></div>


<h2 class="wp-block-heading">Create a block.json file</h2>



<p>Each block will have a block.json file, so it works best to have a directory for each block. I recommend creating a /blocks/ directory in your theme or plugin to hold them.</p>



<p>Create a block.json file in your block-specific folder. Example: /blocks/tip/block.json</p>



<pre class="wp-block-code"><code>{
    "name": "cwp/tip",
    "title": "Recipe Tip",
    "description": "",
    "style": "file:./style.css",
    "script": "",
    "category": "cultivatewp",
    "icon": "carrot",
    "apiVersion": 2,
    "keywords": &#91;],
    "acf": {
        "mode": "preview",
	"renderTemplate": "render.php"
    },
    "styles": &#91;],
    "supports": {
        "align": false,
        "anchor": false,
        "alignContent": false,
        "color": {
            "text": false,
            "background": true,
            "link": false
        },
        "alignText": false,
        "fullHeight": false
    },
<meta charset="utf-8">    "attributes": {
<meta charset="utf-8">    }
}
</code></pre>



<p>Most of this will line up with <a href="https://www.advancedcustomfields.com/resources/acf_register_block_type/">settings for acf_register_block_type()</a>, but there are some important things to note:</p>



<h3 class="wp-block-heading">Custom prefixes in block name</h3>



<p>For the <code>name</code>&nbsp;you can now specify your own prefix, instead of all blocks being prefixed automatically with <code>acf</code>. If you don&#8217;t add a prefix, the ACF one will be used.</p>



<h3 class="wp-block-heading" id="script">Script</h3>



<p>If you need to load a JavaScript file along with your block, use the&nbsp;<code>script</code>&nbsp;parameter to pass the script handle:&nbsp;<code>"script": "block-tip"</code>.</p>



<p>Make sure you also register that script and specify any dependencies.</p>



<pre class="wp-block-code"><code>/**
 * Register block script
 */
function cwp_register_block_script() {
wp_register_script( 'block-tip', get_template_directory_uri() . '/blocks/tip/block-tip.js', &#91; 'jquery', 'acf' ] );
}
add_action( 'init', 'cwp_register_block_script' );</code></pre>



<p>If you are leveraging the ACF JS API ( ex:<code>window.acf.addAction</code>) you’ll need to include&nbsp;<code>acf</code>&nbsp;as a dependency.</p>



<p>If you use a namespace other than “acf/” you need to use the full block name in the callback, so:&nbsp;<code>render_block_preview/type=cwp/tip</code></p>



<h3 class="wp-block-heading">Style</h3>



<p>The <code>style</code>&nbsp;parameter lets you specify a stylesheet to include with this block. There are two ways this can be used.</p>



<pre class="wp-block-code"><code>"style": "file:./style.css"</code></pre>



<p>This will load the actual CSS directly in the document&#8217;s head. This means the CSS file is not loaded as a separate request, decreasing the initial page load time (pro), but also that the CSS file isn&#8217;t browser cached, slightly increasing the subsequent page load time (con).</p>



<p>Place your style.css file inside the block directory (ex: /wp-content/themes/my-theme/blocks/tip/style.css).</p>



<pre class="wp-block-code"><code>"style": "block-tip"</code></pre>



<p>This will run <code>wp_enqueue_style( 'block-tip' )</code>&nbsp;to load the CSS file normally, with the opposite pros/cons listed above. Elsewhere in your theme/plugin you should have <code>wp_register_style( 'block-tip', get_template_directory_uri() . '/blocks/tip/style.css' )</code></p>



<p><strong>Quick rant about styles:</strong></p>



<p>If you are using a FSE theme (&#8220;block theme&#8221;) then the style loading will work exactly as you expect. The CSS files and inline styles will only load if that page contains the block.</p>



<p>If you&#8217;re like me and building &#8220;classic&#8221; PHP based themes, WordPress loads every registered block style in the header, regardless of whether that block exists on the page. You can use the <code>should_load_separate_core_block_assets</code>&nbsp;filter to tell WP to only load the ones that are required, but it waits until <code>wp_footer</code>&nbsp;to load the CSS, causing big CLS issues which makes this feature useless.</p>



<p>The WP core argument (as I understand it) is that we don&#8217;t know exactly which blocks are on the page currently. They could be in the post content, or in reusable blocks, or in block-based widget areas, or other block-based features. While that is all true, I would&#8217;ve preferred loading the CSS files we <em>do</em> know are in the post content in the header and loading any missing ones in the footer.</p>



<p>I had built my own CSS loader that figured out which blocks appeared on the page, but now I opt for using WP Rocket to strip unused CSS from the page.</p>



<h3 class="wp-block-heading" id="script">Script</h3>



<p>If you need to load a JavaScript file along with your block, use the <code>script</code>&nbsp;parameter to pass the script handle: <code>"script": "block-tip"</code>.</p>



<p>Make sure you also register that script and specify any dependencies. </p>



<pre class="wp-block-code"><code>/**
 * Register block script
 */
function cwp_register_block_script() {
wp_register_script( 'block-tip', get_template_directory_uri() . '/blocks/tip/block-tip.js', &#91; 'jquery', 'acf' ] );
}
add_action( 'init', 'cwp_register_block_script' );</code></pre>



<p>If you are leveraging the ACF JS API ( ex:<code>window.acf.addAction</code>) you&#8217;ll need to include <code>acf</code> as a dependency.</p>



<p>If you use a namespace other than &#8220;acf/&#8221; you need to use the full block name in the callback, so: <code>render_block_preview/type=cwp/tip</code></p>



<h3 class="wp-block-heading">Icon</h3>



<p>You can specify a <a href="https://developer.wordpress.org/resource/dashicons/">Dashicons icon</a> to use, or include an actual SVG:</p>



<pre class="wp-block-code"><code>"icon": "&lt;svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22.5 22.5'&gt;&lt;defs&gt;&lt;style&gt;.a{fill:#222;}.b{fill:#1fa8af;}&lt;/style&gt;&lt;/defs&gt;&lt;path class='a' d='M20.17,4a10.17,10.17,0,0,0-7-3.5L11.91.38h-.18a.64.64,0,0,0-.47.13A.68.68,0,0,0,11,.93L10.6,3.79a.48.48,0,0,0,.12.43.54.54,0,0,0,.44.18h.11l1.44.17c3.94.45,6.12,2.69,5.84,6a8.37,8.37,0,0,1-2.49,5.12A8.14,8.14,0,0,1,10,18.06l-.65,0H9.15a.8.8,0,0,0-.5.17.68.68,0,0,0-.25.44L8,21.5a.49.49,0,0,0,.12.42.57.57,0,0,0,.45.18h.17l1,0h.34a11.61,11.61,0,0,0,8.21-3.39,12.76,12.76,0,0,0,3.77-7.92A9.49,9.49,0,0,0,20.17,4Z'/&gt;&lt;path class='b' d='M9.2,17h.15L10,17a7.61,7.61,0,0,0,3.64-.77L15,6.15a8.65,8.65,0,0,0-2.33-.57l-1-.12-1,7.35L9.23,7c-.11-.45-.33-.67-.65-.67H7.16c-.29,0-.5.22-.65.67L5.1,12.81,3.82,3a.55.55,0,0,0-.61-.55H.78a.36.36,0,0,0-.29.16A.6.6,0,0,0,.37,3a.5.5,0,0,0,0,.18L2.53,19.22a1.07,1.07,0,0,0,.23.6.64.64,0,0,0,.53.23H5.16c.37,0,.61-.21.73-.65l2-7.19Z'/&gt;&lt;/svg&gt;",</code></pre>



<h3 class="wp-block-heading">ACF</h3>



<p>ACF Specific settings will go in this array. </p>



<p>Use <code>mode</code>&nbsp;to specify how the block is rendered in the block editor.  The default is “auto” which renders the block to match the frontend until you select it, then it becomes an ACF field group editor. If set to “preview” it will always look like the frontend and you can edit ACF field group in the sidebar.</p>



<p>Use <code><meta charset="utf-8">renderTemplate</code>&nbsp;to specify what PHP file will render this block. I typically have a render.php file in each block directory for consistency.</p>



<p>Alternatively, you can use <code>renderCallback</code>&nbsp;to specify a PHP function that will output the block&#8217;s content.</p>



<h3 class="wp-block-heading">Styles</h3>



<p>Use <code>styles</code>&nbsp;to specify an array of block type styles. </p>



<pre class="wp-block-code"><code>"styles": &#91;
        { "name": "default", "label": "Default", "isDefault": true },
        { "name": "red", "label": "Red" },
        { "name": "green", "label": "Green" },
        { "name": "blue", "label": "Blue" }
    ],</code></pre>



<h3 class="wp-block-heading">Supports</h3>



<p>Use <code>supports</code>&nbsp;to specify what Gutenberg features this block supports. The default is <code>false</code>&nbsp;for all items so you don&#8217;t need to specify all the items it doesn&#8217;t support, but I typically leave them all in there marked <code>false</code>&nbsp;so I can easily toggle the ones I want to <code>true</code>. </p>



<p>In this example, the only feature this block supports is a background color:</p>



<pre class="wp-block-code"><code><meta charset="utf-8">"supports": {
        "align": false,
        "anchor": false,
        "alignContent": false,
        "color": {
            "text": false,
            "background": true,
            "link": false
        },
        "alignText": false,
        "fullHeight": false
    },</code></pre>



<h3 class="wp-block-heading">Attributes</h3>



<p>You can set the default attributes for the block features. For instance, if the block supports a background color, you can have that block use the <code>tertiary</code>&nbsp;color by default:</p>



<pre class="wp-block-code"><code>"attributes": {
	"backgroundColor": {
		"type": "string",
		"default": "tertiary"
	}
}
</code></pre>



<p>For more information, see the Block Editor Handbook article on <a href="https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/">Metadata in block.json</a>.</p>



<hr class="wp-block-separator has-css-opacity"/>



<h2 class="wp-block-heading">Register your block</h2>



<p>You should now have a folder in your theme/plugin with block.json, style.css, and render.php. The next step is to tell WordPress about your block using <code>register_block_type()</code>.</p>



<p>In your plugin or theme&#8217;s functions.php file, add:</p>



<pre class="wp-block-code"><code>/**
 * Load Blocks
 */
function cwp_load_blocks() {
<meta charset="utf-8">	register_block_type( get_template_directory() . '/blocks/tip/block.json' );

<meta charset="utf-8">	// Optional - register stylesheet if using Style Method 2 from above
<meta charset="utf-8">	wp_register_style( 'block-tip', get_template_directory_uri() . '/blocks/tip/style.css' );
}
add_action( 'init', 'cwp_load_blocks' );</code></pre>



<p>That&#8217;s it! Now your custom block should be accessible in the block editor.</p>



<h2 class="wp-block-heading">Advanced Usage</h2>



<p>While the above works as a basic example, there are some ways we can improve this:</p>



<ol class="wp-block-list">
<li>Register every block that exists in the /blocks directory</li>



<li>Cache the list of blocks so we aren&#8217;t traversing the file system on every pageload</li>



<li>Register a stylesheet for each block</li>



<li>Include any ACF field groups associated with that block</li>



<li>Include any additional PHP files required by the block</li>
</ol>



<p>Here&#8217;s the code I use in my themes, followed by a description of what it&#8217;s doing.</p>



<pre class="wp-block-code"><code>&lt;?php
/**
 * Blocks
 *
 * @package      CultivateClient
 * @author       CultivateWP
 * @since        1.0.0
 * @license      GPL-2.0+
 **/

namespace Cultivate\Blocks;

/**
 * Load Blocks
 */
function load_blocks() {
	$theme  = wp_get_theme();
	$blocks = get_blocks();
	foreach( $blocks as $block ) {
		if ( file_exists( get_template_directory() . '/blocks/' . $block . '/block.json' ) ) {
			register_block_type( get_template_directory() . '/blocks/' . $block . '/block.json' );
			wp_register_style( 'block-' . $block, get_template_directory_uri() . '/blocks/' . $block . '/style.css', null, $theme-&gt;get( 'Version' ) );

			if ( file_exists( get_template_directory() . '/blocks/' . $block . '/init.php' ) ) {
				include_once get_template_directory() . '/blocks/' . $block . '/init.php';
			}
		}
	}
}
add_action( 'init', __NAMESPACE__ . '\load_blocks', 5 );

/**
 * Load ACF field groups for blocks
 */
function load_acf_field_group( $paths ) {
	$blocks = get_blocks();
	foreach( $blocks as $block ) {
		$paths&#91;] = get_template_directory() . '/blocks/' . $block;
	}
	return $paths;
}
add_filter( 'acf/settings/load_json', __NAMESPACE__ . '\load_acf_field_group' );

/**
 * Get Blocks
 */
function get_blocks() {
	$theme   = wp_get_theme();
	$blocks  = get_option( 'cwp_blocks' );
	$version = get_option( 'cwp_blocks_version' );
	if ( empty( $blocks ) || version_compare( $theme-&gt;get( 'Version' ), $version ) || ( function_exists( 'wp_get_environment_type' ) &amp;&amp; 'production' !== wp_get_environment_type() ) ) {
		$blocks = scandir( get_template_directory() . '/blocks/' );
		$blocks = array_values( array_diff( $blocks, array( '..', '.', '.DS_Store', '_base-block' ) ) );

		update_option( 'cwp_blocks', $blocks );
		update_option( 'cwp_blocks_version', $theme-&gt;get( 'Version' ) );
	}
	return $blocks;
}

/**
 * Block categories
 *
 * @since 1.0.0
 */
function block_categories( $categories ) {

	// Check to see if we already have a CultivateWP category
	$include = true;
	foreach( $categories as $category ) {
		if( 'cultivatewp' === $category&#91;'slug'] ) {
			$include = false;
		}
	}

	if( $include ) {
		$categories = array_merge(
			$categories,
			&#91;
				&#91;
					'slug'  =&gt; 'cultivatewp',
					'title' =&gt; __( 'CultivateWP', 'cultivate_textdomain' ),
					'icon'  =&gt; \cwp_icon( &#91; 'icon' =&gt; 'cultivatewp', 'group' =&gt; 'color', 'force' =&gt; true ] )
				]
			]
		);
	}

	return $categories;
}
add_filter( 'block_categories_all', __NAMESPACE__ . '\block_categories' );
</code></pre>



<p>My <code><meta charset="utf-8">get_blocks()</code>&nbsp;function scans the /block directory and makes an array of all my blocks. I store this as an option so we only have to do this once, and use the current theme&#8217;s version to bust the cache. So when I add a new block, I also bump the version number in style.css</p>



<p>The code does the following for each block:</p>



<ul class="wp-block-list">
<li>Call <code>register_block_type()</code>&nbsp;using the block.json file</li>



<li>Call <code>wp_register_style()</code>&nbsp;to register the block&#8217;s stylesheet. This will only load if the block.json file specifies a <code>style</code>.</li>



<li>If there&#8217;s an <code>init.php</code>&nbsp;file in the block directory, load that too. This is what I use for any additional PHP code I want to run independent of the block&#8217;s rendering.</li>



<li>I&#8217;m doing all of this on the <code>init</code>&nbsp;hook with a priority of 5 so I can use the normal <code>init</code>&nbsp;(priority 10) inside my init.php file.</li>



<li>The <code>acf/settings/load_json</code>&nbsp;filter tells ACF to look in my block directories for ACF JSON field group files. </li>
</ul>



<hr class="wp-block-separator has-css-opacity"/>



<h2 class="wp-block-heading">Need Help?</h2>



<p>Ask ACF Support <img src="https://s.w.org/images/core/emoji/15.0.3/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" />. I&#8217;m closing comments on this post because I likely won&#8217;t have time to answer questions &amp; troubleshoot issues, but ACF&#8217;s support team is excellent and very responsive.</p>



<p>I&#8217;ll also update this post with more information and resources as I find them. </p>



<h3 class="wp-block-heading">Additional information</h3>



<ul class="wp-block-list">
<li>The GitHub ticket <a href="https://github.com/AdvancedCustomFields/acf/issues/654" target="_blank" rel="noreferrer noopener">Introducing the ACF PRO Block Versioning Developer Preview #654</a>.</li>
</ul>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/building-acf-blocks-with-block-json/">Building ACF blocks with block.json</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Color Palette with ACF custom blocks</title>
		<link>https://www.billerickson.net/color-palette-with-acf-custom-blocks/</link>
					<comments>https://www.billerickson.net/color-palette-with-acf-custom-blocks/#comments</comments>
		
		<dc:creator><![CDATA[Bill Erickson]]></dc:creator>
		<pubDate>Tue, 22 Feb 2022 14:23:52 +0000</pubDate>
				<category><![CDATA[WordPress Development]]></category>
		<guid isPermaLink="false">https://www.billerickson.net/?p=8629</guid>

					<description><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2-1168x613.jpg" class="attachment-large size-large wp-image-8632 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2-1168x613.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2-768x403.jpg 768w, https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2-1536x806.jpg 1536w, https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2.jpg 1800w" sizes="(max-width: 768px) 100vw, 768px" /><p>Your custom blocks can use the native Background Color and Text Color block features.</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/color-palette-with-acf-custom-blocks/">Color Palette with ACF custom blocks</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2-1168x613.jpg" class="attachment-large size-large wp-image-8632 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2-1168x613.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2-768x403.jpg 768w, https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2-1536x806.jpg 1536w, https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2.jpg 1800w" sizes="(max-width: 768px) 100vw, 768px" />
<figure class="wp-block-image alignwide size-full border"><img decoding="async" width="1800" height="945" src="https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2.jpg" alt="" class="wp-image-8632" srcset="https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2.jpg 1800w, https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2-768x403.jpg 768w, https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2-1168x613.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2022/02/color-palette2-1536x806.jpg 1536w" sizes="(max-width: 1800px) 100vw, 1800px" /></figure>



<p>The core WordPress blocks like button and group all share a <a href="https://www.billerickson.net/wordpress-color-palette-button-styling-gutenberg/">global color palette</a>. This saves a lot of time because you don&#8217;t have to style many variations of each block &#8211; every block with a class of <code>.has-secondary-background-color</code> will have the same background color.</p>



<p>If you&#8217;re using theme.json, this becomes even more powerful because you can customize the color palette on a per-block-type level.</p>



<p>At <a href="https://cultivatewp.com" target="_blank" rel="noreferrer noopener">CultivateWP</a> we build <em>a lot</em> of custom blocks with ACF. In the past, when we needed the global color palette we would either wrap our custom block in a group block that had the color styling, or use Matt Whiteley&#8217;s guide on <a href="https://whiteleydesigns.com/synchronizing-your-acf-color-picker-with-gutenberg-color-classes/" target="_blank" rel="noreferrer noopener">syncing ACF colors with the Gutenberg palette</a>. But Matt&#8217;s approach doesn&#8217;t work with theme.json. </p>



<p>A developer on our team (<a href="https://cultivatewp.com/about/chris-brailsford/" target="_blank" rel="noreferrer noopener">Chris Brailsford</a>) just found a better way.</p>



<h2 class="wp-block-heading">Add &#8216;color&#8217; support</h2>



<p>When registering a new block with <code>acf_register_block_type()</code>, you can use the &#8216;supports&#8217; array to specify which block editor features this block supports. The documentation lists a few examples, but the important part is <strong>All properties from the JavaScript <a href="https://developer.wordpress.org/block-editor/developers/block-api/block-supports/" target="_blank" rel="noreferrer noopener">block supports</a> documentation may be used. </strong></p>



<p>To add support for the color feature, add <code>'color' => true</code> to the supports array.</p>



<pre class="wp-block-code"><code>acf_register_block_type(
	&#91;
		'title'           => __( 'Hero', 'cwp2021' ),
		'name'            => 'hero',
		'render_template' => 'partials/blocks/hero.php',
		'category'        => 'cultivatewp',
		'mode'            => 'preview',
		'align'           => 'full',
		'supports'        => &#91;
			'align'           => &#91; 'full' ],
			'jsx'             => true,
			'color'           => true,
		],
	]
);
</code></pre>



<p>This adds support for both the Background Color and Text Color fields.</p>



<div class="wp-block-image border"><figure class="aligncenter size-full is-resized"><img decoding="async" src="https://www.billerickson.net/wp-content/uploads/2022/02/block-colors.jpeg" alt="" class="wp-image-8630" width="270" height="536" srcset="https://www.billerickson.net/wp-content/uploads/2022/02/block-colors.jpeg 540w, https://www.billerickson.net/wp-content/uploads/2022/02/block-colors-387x768.jpeg 387w" sizes="(max-width: 270px) 100vw, 270px" /></figure></div>



<p>For more control, you can pass an array to specify which of those two fields should be displayed.</p>



<pre class="wp-block-code"><code>acf_register_block_type(
	&#91;
		'title'           => __( 'Hero', 'cwp2021' ),
		'name'            => 'hero',
		'render_template' => 'partials/blocks/hero.php',
		'category'        => 'cultivatewp',
		'mode'            => 'preview',
		'align'           => 'full',
		'supports'        => &#91;
			'align'           => &#91; 'full' ],
			'jsx'             => true,
			'color'           => &#91;
				'background' => true,
<meta charset="utf-8">				'gradients'  => true,
				'text'       => false,
			],
		],
	]
);</code></pre>



<h2 class="wp-block-heading">Add the classes to your block</h2>



<p>Selecting a color from the palette adds a class to your block for styling. This automatically works in the backend because the block editor adds the class for you, but you&#8217;ll also need to update your block markup to add the class on the frontend.</p>



<p>Inside your block template file, check for <code>$block['backgroundColor']</code> and <code>$block['textColor']</code>. </p>



<pre class="wp-block-code"><code>$classes = &#91; 'block-hero' ];
if ( ! empty( $block&#91;'className'] ) ) {
	$classes = array_merge( $classes, explode( ' ', $block&#91;'className'] ) );
}
if ( ! empty( $block&#91;'align'] ) ) {
	$classes&#91;] = 'align' . $block&#91;'align'];
}
if ( ! empty( $block&#91;'backgroundColor'] ) ) {
	$classes&#91;] = 'has-background';
	$classes&#91;] = 'has-' . $block&#91;'backgroundColor'] . '-background-color';
}
if ( ! empty( $block&#91;'textColor'] ) ) {
	$classes&#91;] = 'has-text-color';
	$classes&#91;] = 'has-' . $block&#91;'textColor'] . '-color';
}
printf(
	'&lt;div class="%s"%s>',
	esc_attr( join( ' ', $classes ) ),
	! empty( $block&#91;'anchor'] ) ? ' id="' . esc_attr( sanitize_title( $block&#91;'anchor'] ) ) . '"' : '',
);
echo '&lt;div class="block-hero__image">' . wp_get_attachment_image( get_field( 'image' ), 'full' ) . '&lt;/div>';

echo '&lt;div class="block-hero__content">&lt;InnerBlocks />&lt;/div>';

echo '&lt;/div>';
</code></pre>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/color-palette-with-acf-custom-blocks/">Color Palette with ACF custom blocks</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.billerickson.net/color-palette-with-acf-custom-blocks/feed/</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		
			</item>
		<item>
		<title>Change favicon color for dark mode</title>
		<link>https://www.billerickson.net/favicon-dark-mode/</link>
					<comments>https://www.billerickson.net/favicon-dark-mode/#comments</comments>
		
		<dc:creator><![CDATA[Bill Erickson]]></dc:creator>
		<pubDate>Tue, 13 Jul 2021 22:17:32 +0000</pubDate>
				<category><![CDATA[WordPress Development]]></category>
		<guid isPermaLink="false">https://www.billerickson.net/?p=8613</guid>

					<description><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2021/07/dark-mode-1168x613.jpg" class="attachment-large size-large wp-image-8618 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2021/07/dark-mode-1168x613.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2021/07/dark-mode-768x403.jpg 768w, https://www.billerickson.net/wp-content/uploads/2021/07/dark-mode.jpg 1200w" sizes="(max-width: 768px) 100vw, 768px" /><p>Use an SVG to change the favicon color based on the browser's color scheme.</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/favicon-dark-mode/">Change favicon color for dark mode</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2021/07/dark-mode-1168x613.jpg" class="attachment-large size-large wp-image-8618 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2021/07/dark-mode-1168x613.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2021/07/dark-mode-768x403.jpg 768w, https://www.billerickson.net/wp-content/uploads/2021/07/dark-mode.jpg 1200w" sizes="(max-width: 768px) 100vw, 768px" />
<figure class="wp-block-image size-full"><img decoding="async" width="1200" height="630" src="https://www.billerickson.net/wp-content/uploads/2021/07/dark-mode.jpg" alt="" class="wp-image-8618" srcset="https://www.billerickson.net/wp-content/uploads/2021/07/dark-mode.jpg 1200w, https://www.billerickson.net/wp-content/uploads/2021/07/dark-mode-768x403.jpg 768w, https://www.billerickson.net/wp-content/uploads/2021/07/dark-mode-1168x613.jpg 1168w" sizes="(max-width: 1200px) 100vw, 1200px" /></figure>



<p>When you upload a favicon image in the WordPress customizer, it provides a helpful preview to see how your favicon will appear in browsers using light or dark mode.</p>



<div class="wp-block-image"><figure class="aligncenter size-large is-resized"><img decoding="async" src="https://www.billerickson.net/wp-content/uploads/2021/07/ee6fdc33-6408-40ad-93d4-37cf972a37c6.png" alt="" class="wp-image-8619" width="324" height="152"/></figure></div>



<p>When the favicon color doesn&#8217;t work well with dark mode, a common fix is to replace the transparent PNG with a JPG that has a white background, but then you end up with a white square in dark mode.</p>



<p>Alternatively, you can use an SVG for the favicon and modify the favicon styling based on the color scheme. </p>



<p>You can see this in use in the recent <a href="https://www.nerdpress.net" target="_blank" rel="noreferrer noopener">NerdPress</a> redesign we just launched.</p>



<h2 class="wp-block-heading">Create SVG favicon</h2>



<p>Create a square SVG with your desired icon. It will look something like this:</p>



<pre class="wp-block-code"><code>&lt;svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
	&lt;path fill="#0F145B" d="......" />
&lt;/svg></code></pre>



<p>Remove any styling from the shapes in the SVG (so the <code>fill</code>&nbsp;and <code>stroke</code>&nbsp;attributes) and add those styles with inline CSS.</p>



<p>You can use <code>@media ( prefers-color-scheme: dark )</code>&nbsp;to style the dark mode version differently. Here&#8217;s what my SVG now looks like:</p>



<pre class="wp-block-code"><code>&lt;svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
	&lt;style>
		path {
			fill: #0F145B;
		}
		@media ( prefers-color-scheme: dark ) {
			path {
				fill: #43C1C5;
			}
		}
	&lt;/style>
	&lt;path d="....." />
&lt;/svg></code></pre>



<h2 class="wp-block-heading">Add SVG favicon to your theme</h2>



<p>I added my favicon.svg to my theme&#8217;s <code>/assets/images/</code>&nbsp;directory, but you can add it anywhere in your theme. </p>



<p>Add the following code to your theme&#8217;s functions.php file to include the SVG favicon.</p>



<pre class="wp-block-code"><code>/**
 * SVG Favicon
 */
function be_svg_favicon() {
	echo '&lt;link rel="icon" href="' . esc_url( get_stylesheet_directory_uri() . '/assets/images/favicon.svg' ) . '" type="image/svg+xml">';
}
add_action( 'wp_head', 'be_svg_favicon', 100 );</code></pre>



<p>It seems that the SVG favicon is prioritized over the WP generated one regardless of whether it appears before or after it in the page markup, but I have the priority set to 100 so it will appear after, just in case.</p>



<p>Even with this approach, you should upload a JPG version of the favicon in the WordPress customizer. There are still <a href="https://caniuse.com/link-icon-svg">many browsers that don&#8217;t support SVG favicons</a> so you&#8217;ll want a fallback.</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/favicon-dark-mode/">Change favicon color for dark mode</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.billerickson.net/favicon-dark-mode/feed/</wfw:commentRss>
			<slash:comments>8</slash:comments>
		
		
			</item>
		<item>
		<title>InnerBlocks with ACF blocks</title>
		<link>https://www.billerickson.net/innerblocks-with-acf-blocks/</link>
					<comments>https://www.billerickson.net/innerblocks-with-acf-blocks/#comments</comments>
		
		<dc:creator><![CDATA[Bill Erickson]]></dc:creator>
		<pubDate>Tue, 08 Sep 2020 15:12:40 +0000</pubDate>
				<category><![CDATA[Advanced Custom Fields]]></category>
		<category><![CDATA[Gutenberg Block Editor]]></category>
		<category><![CDATA[WordPress Development]]></category>
		<guid isPermaLink="false">https://www.billerickson.net/?p=8408</guid>

					<description><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2020/09/innerblocks-acf-1168x613.jpg" class="attachment-large size-large wp-image-8418 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2020/09/innerblocks-acf-1168x613.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2020/09/innerblocks-acf-768x403.jpg 768w, https://www.billerickson.net/wp-content/uploads/2020/09/innerblocks-acf.jpg 1200w" sizes="(max-width: 768px) 100vw, 768px" /><p>Use InnerBlocks to insert any block (core or custom) inside your custom ACF block. Set the default content, limit which blocks can be used, and more.</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/innerblocks-with-acf-blocks/">InnerBlocks with ACF blocks</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="768" height="403" src="https://www.billerickson.net/wp-content/uploads/2020/09/innerblocks-acf-1168x613.jpg" class="attachment-large size-large wp-image-8418 wp-post-image" alt="" decoding="async" srcset="https://www.billerickson.net/wp-content/uploads/2020/09/innerblocks-acf-1168x613.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2020/09/innerblocks-acf-768x403.jpg 768w, https://www.billerickson.net/wp-content/uploads/2020/09/innerblocks-acf.jpg 1200w" sizes="(max-width: 768px) 100vw, 768px" />
<p>My favorite new feature in Advanced Custom Fields 5.9 is support for InnerBlocks. This allows you to insert <strong>any</strong> block (core or custom) inside your ACF block.</p>



<p>Rather than having to create your own fields for Title, Content, and Button in your custom block, you can simply insert <code>&lt;InnerBlocks /&gt;</code>&nbsp;and use the block editor to build the content inside the block.</p>


<div class="block-toc"><p class="block-toc__header">Table of Contents</p><ol><li><a href="#">How to use InnerBlocks</a></li><li><a href="#">Default value for InnerBlocks</a></li><li><a href="#placeholders">Placeholders instead of default content</a></li><li><a href="#">Limit the blocks available in InnerBlocks</a></li><li><a href="#">Template lock with InnerBlocks</a></li></ol></div>


<figure class="wp-block-video alignwide"><video controls src="https://p198.p4.n0.cdn.getcloudapp.com/items/RBuOkDy1/Screen%20Recording%202020-09-04%20at%2011.35.49%20AM.mp4?source=viewer"></video></figure>



<h2 class="wp-block-heading">How to use InnerBlocks</h2>



<p>When <a href="https://www.billerickson.net/building-gutenberg-block-acf/#register-block">registering your ACF block</a>, include <code>'jsx' =&gt; true</code>&nbsp;in the <code>supports</code>&nbsp;array. </p>



<pre class="wp-block-code"><code>acf_register_block_type( array(
	'title'			=> __( 'About', 'client_textdomain' ),
	'name'			=> 'about',
	'render_template'	=> 'partials/blocks/about.php',
	'mode'			=> 'preview',
	'supports'		=> &#91;
		'align'			=> false,
		'anchor'		=> true,
		'customClassName'	=> true,
		'jsx' 			=> true,
	]
));
</code></pre>



<p>In your template partial for the block, include <code>&lt;InnerBlocks /&gt;</code>&nbsp;where you would like the editable block area to appear. </p>



<pre class="wp-block-code"><code>$classes = &#91;'block-about'];
if( !empty( $block&#91;'className'] ) )
    $classes = array_merge( $classes, explode( ' ', $block&#91;'className'] ) );

$anchor = '';
if( !empty( $block&#91;'anchor'] ) )
	$anchor = ' id="' . sanitize_title( $block&#91;'anchor'] ) . '"';

echo '&lt;div class="' . join( ' ', $classes ) . '"' . $anchor . '>';
	echo '&lt;div class="block-about__inner">';
		echo '&lt;div class="block-about__content">';
			echo '&lt;InnerBlocks />';
		echo '&lt;/div>';
		echo '&lt;div class="block-about__image">';
			echo wp_get_attachment_image( get_field( 'image' ), 'be_thumbnail_l' );
		echo '&lt;/div>';
	echo '&lt;/div>';
echo '&lt;/div>';
</code></pre>



<h2 class="wp-block-heading">Default value for InnerBlocks</h2>



<p>It&#8217;s helpful to fill the InnerBlocks field with default content so the block looks correct when first inserted.</p>



<p>We&#8217;re building a new site for <a href="https://www.nicekicks.com/" target="_blank" rel="noreferrer noopener">Nice Kicks</a>, and they often need to highlight the release date and details for new sneakers. We built a Release Info block that uses InnerBlocks for the content area.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1168" height="678" src="https://www.billerickson.net/wp-content/uploads/2020/09/nicekicks-release-info-1168x678.jpg" alt="" class="wp-image-8414" srcset="https://www.billerickson.net/wp-content/uploads/2020/09/nicekicks-release-info-1168x678.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2020/09/nicekicks-release-info-768x446.jpg 768w, https://www.billerickson.net/wp-content/uploads/2020/09/nicekicks-release-info.jpg 1472w" sizes="(max-width: 1168px) 100vw, 1168px" /></figure>



<p>Rather than just having an empty white box when they first insert the block, we pre-populate it with default content using a <a href="https://www.billerickson.net/gutenberg-block-templates/">block template</a>.</p>



<p>Inside the block&#8217;s template file, create a <code>$template</code> array detailing which blocks should be added. Update <code>&lt;InnerBlocks /></code> to include the template.</p>



<p>You can find the available block attributes in a blocks.json file for each block in <a href="https://github.com/WordPress/WordPress/tree/master/wp-includes/blocks" target="_blank" rel="noreferrer noopener">wp-includes/blocks</a>.</p>



<pre class="wp-block-code"><code>$template = array(
	array('core/heading', array(
		'level' => 2,
		'content' => 'Title Goes Here',
	)),
    array( 'core/paragraph', array(
        'content' => '&lt;strong>Colorway:&lt;/strong> &lt;br />&lt;strong>Style Code:&lt;/strong>  &lt;br />&lt;strong>Release Date:&lt;/strong> &lt;br />&lt;strong>MSRP:&lt;/strong> ',
    ) )
);

echo '&lt;div class="' . join( ' ', $classes ) . '"' . $anchor . '>';
	echo '&lt;InnerBlocks template="' . esc_attr( wp_json_encode( $template ) ) . '" />';
	$form_id = get_option( 'options_be_release_info_form' );
	if( !empty( $form_id ) &amp;&amp; function_exists( 'wpforms_display' ) )
		wpforms_display( $form_id, true, true );
echo '&lt;/div>';
</code></pre>



<figure class="wp-block-image size-large"><img decoding="async" width="1168" height="678" src="https://www.billerickson.net/wp-content/uploads/2020/09/nicekicks-release-info-default-1168x678.jpg" alt="" class="wp-image-8415" srcset="https://www.billerickson.net/wp-content/uploads/2020/09/nicekicks-release-info-default-1168x678.jpg 1168w, https://www.billerickson.net/wp-content/uploads/2020/09/nicekicks-release-info-default-768x446.jpg 768w, https://www.billerickson.net/wp-content/uploads/2020/09/nicekicks-release-info-default.jpg 1472w" sizes="(max-width: 1168px) 100vw, 1168px" /></figure>



<h2 class="wp-block-heading" id="placeholders">Placeholders instead of default content</h2>



<p>In the above example we set the starting content for the block. If you were to publish the post without changing the text, the default content would appear in the blocks.</p>



<p>Alternatively, you can use the <code>placeholder</code>&nbsp;parameter to specify placeholder text. This will not be published, and when you select the field the placeholder text disappears.</p>



<p>I had two issues with placeholders, which is why I used default content instead:</p>



<ol class="wp-block-list"><li>When you insert the block, the first block inside InnerBlocks is selected so its placeholder text is not visible. You have to insert the block then click outside the block to see the placeholder text.</li><li>The placeholder field does not support HTML. In my use case, we used <code>&lt;strong&gt;</code>&nbsp;and <code>&lt;br /&gt;</code>&nbsp;to format the paragraph text, but that doesn&#8217;t work with the placeholder.</li></ol>



<p>To use placeholders with the above example, change the <code>$template</code>&nbsp;to </p>



<pre class="wp-block-code"><code>$template = array(
	array('core/heading', array(
		'level' => 2,
		'placeholder' => 'Title Goes Here',
	)),
	array( 'core/paragraph', array(
		'placeholder' => '&lt;strong>Colorway:&lt;/strong> &lt;br />&lt;strong>Style #:&lt;/strong>  &lt;br />&lt;strong>Release Date:&lt;/strong> &lt;br />&lt;strong>Price:&lt;/strong> ',
	) )
);</code></pre>



<p>And this was the result:</p>



<figure class="wp-block-image size-large border"><img decoding="async" width="1168" height="323" src="https://www.billerickson.net/wp-content/uploads/2020/09/block-template-placeholder-1168x323.png" alt="" class="wp-image-8419" srcset="https://www.billerickson.net/wp-content/uploads/2020/09/block-template-placeholder-1168x323.png 1168w, https://www.billerickson.net/wp-content/uploads/2020/09/block-template-placeholder-768x213.png 768w, https://www.billerickson.net/wp-content/uploads/2020/09/block-template-placeholder-1536x425.png 1536w, https://www.billerickson.net/wp-content/uploads/2020/09/block-template-placeholder.png 1546w" sizes="(max-width: 1168px) 100vw, 1168px" /></figure>



<h2 class="wp-block-heading">Limit the blocks available in InnerBlocks</h2>



<p>You can limit which blocks can be inserted into your InnerBlocks field using the <code>allowedBlocks</code>&nbsp;attribute. </p>



<p>Using the example above, I can limit the Release Info block to only include the heading and paragraph blocks:</p>



<pre class="wp-block-code"><code>$allowed_blocks = array( 'core/heading', 'core/paragraph' );

$template = array(
	array('core/heading', array(
		'level' => 2,
		'content' => 'Title Goes Here',
	)),
    array( 'core/paragraph', array(
        'content' => '&lt;strong>Colorway:&lt;/strong> &lt;br />&lt;strong>Style Code:&lt;/strong>  &lt;br />&lt;strong>Release Date:&lt;/strong> &lt;br />&lt;strong>MSRP:&lt;/strong> ',
    ) )
);

echo '&lt;div class="' . join( ' ', $classes ) . '"' . $anchor . '>';
	echo '&lt;InnerBlocks allowedBlocks="' . esc_attr( wp_json_encode( $allowed_blocks ) ) . '" template="' . esc_attr( wp_json_encode( $template ) ) . '" />';
	$form_id = get_option( 'options_be_release_info_form' );
	if( !empty( $form_id ) &amp;&amp; function_exists( 'wpforms_display' ) )
		wpforms_display( $form_id, true, true );
echo '&lt;/div>';
</code></pre>



<h2 class="wp-block-heading">Template lock with InnerBlocks</h2>



<p>You can also limit the flexibility by locking the template. </p>



<p>Adding <code>templateLock="all"</code>&nbsp;prevents inserting new blocks or removing/re-arranging current blocks</p>



<p>Adding <code>templateLock="insert"</code>&nbsp;prevents inserting new blocks or removing current blocks, but you can re-arrange the current blocks.</p>



<p>I recently built an Icon Heading block. The icon can be selected in the block settings sidebar using a <a href="https://www.billerickson.net/dynamic-dropdown-fields-in-acf/">dynamic dropdown field</a>.</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" src="https://www.billerickson.net/wp-content/uploads/2020/09/icon-heading.jpg" alt="" class="wp-image-8417" width="223" height="48" srcset="https://www.billerickson.net/wp-content/uploads/2020/09/icon-heading.jpg 446w, https://www.billerickson.net/wp-content/uploads/2020/09/icon-heading-440x96.jpg 440w" sizes="(max-width: 223px) 100vw, 223px" /></figure>



<p>I used InnerBlocks for the heading itself so it would have all the standard options for customizing the heading (change block style, change heading type to h3). I used <code>templateLock="all"</code>&nbsp;so only the heading from my block template could be used in this block.</p>



<pre class="wp-block-code"><code>$classes = &#91;'block-icon-heading'];
if( !empty( $block&#91;'className'] ) )
    $classes = array_merge( $classes, explode( ' ', $block&#91;'className'] ) );
if( !empty( $block&#91;'align'] ) )
    $classes&#91;] = 'align' . $block&#91;'align'];

$anchor = '';
if( !empty( $block&#91;'anchor'] ) )
	$anchor = ' id="' . sanitize_title( $block&#91;'anchor'] ) . '"';

$template = array(
	array('core/heading', array(
		'level' => 2,
		'content' => 'Heading',
	)),
);

echo '&lt;div class="' . join( ' ', $classes ) . '"' . $anchor . '>';
	$icon = get_field( 'dynamic_icon_category' );
	if( !empty( $icon ) )
		echo '&lt;div class="icon-heading-wrap">' . be_icon( &#91; 'icon' => $icon, 'group' => 'category', 'size' => 38 ] ) . '&lt;/div>';
	echo '&lt;InnerBlocks template="' . esc_attr( wp_json_encode( $template ) ) . '" templateLock="all" />';
echo '&lt;/div>';
</code></pre>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/innerblocks-with-acf-blocks/">InnerBlocks with ACF blocks</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.billerickson.net/innerblocks-with-acf-blocks/feed/</wfw:commentRss>
			<slash:comments>56</slash:comments>
		
		<enclosure length="2011730" type="video/mp4" url="https://p198.p4.n0.cdn.getcloudapp.com/items/RBuOkDy1/Screen%20Recording%202020-09-04%20at%2011.35.49%20AM.mp4?source=viewer"/>

			</item>
		<item>
		<title>How to remove core WordPress blocks</title>
		<link>https://www.billerickson.net/how-to-remove-core-wordpress-blocks/</link>
					<comments>https://www.billerickson.net/how-to-remove-core-wordpress-blocks/#comments</comments>
		
		<dc:creator><![CDATA[Bill Erickson]]></dc:creator>
		<pubDate>Thu, 25 Jun 2020 14:50:08 +0000</pubDate>
				<category><![CDATA[Gutenberg Block Editor]]></category>
		<category><![CDATA[WordPress Development]]></category>
		<guid isPermaLink="false">https://www.billerickson.net/?p=8365</guid>

					<description><![CDATA[<p>While I try to support all the core blocks in the themes I build, sometimes it makes sense to remove a few. Typically it&#8217;s because I built a custom block&#8230;</p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/how-to-remove-core-wordpress-blocks/">How to remove core WordPress blocks</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>While I try to support all the core blocks in the themes I build, sometimes it makes sense to remove a few.</p>



<p>Typically it&#8217;s because I <a href="https://www.billerickson.net/building-gutenberg-block-acf/">built a custom block</a> that&#8217;s similar to a core block while addressing the design and functional requirements of the theme. Most of my themes include a &#8220;Content and Image&#8221; block that&#8217;s similar to the &#8220;Media &amp; Text&#8221; block but it uses the theme&#8217;s grid layout.</p>



<p>Sometimes I&#8217;ll unregister the &#8220;Search&#8221; block and create my own that uses the <code>searchform.php</code>&nbsp;file in the theme, ensuring the Search block matches the design and functionality of the search form used everywhere else in the theme.</p>



<h2 class="wp-block-heading">Enqueue block editor assets</h2>



<p>You can use the <code>enqueue_block_editor_assets</code>&nbsp;hook to load scripts and styles into the block editor. My themes typically have an <code>editor.js</code>&nbsp;file that I use for <a href="https://www.billerickson.net/block-styles-in-gutenberg/">block styles</a> and unregistering block types. </p>



<p>I also enqueue any custom fonts used on the frontend so I can also use them in the editor styles.</p>



<pre class="wp-block-code"><code>/**
 * Gutenberg scripts and styles
 *
 */
function be_gutenberg_scripts() {
	wp_enqueue_style( 'theme-fonts', be_theme_fonts_url() );
	wp_enqueue_script( 'theme-editor', get_template_directory_uri() . '/assets/js/editor.js', array( 'wp-blocks', 'wp-dom' ), filemtime( get_template_directory() . '/assets/js/editor.js' ), true );
}
add_action( 'enqueue_block_editor_assets', 'be_gutenberg_scripts' );

/**
 * Theme Fonts URL
 *
 */
function be_theme_fonts_url() {
	return 'https://fonts.googleapis.com/css2?family=Roboto+Slab&amp;display=swap';
}
</code></pre>



<h2 class="wp-block-heading">Unregister block type</h2>



<p>Now that you&#8217;ve created an editor.js file and enqueued it into the block editor, you can use <code>wp.blocks.unregisterBlockType</code>&nbsp;to unregister block types.  </p>



<pre class="wp-block-code"><code>wp.domReady( () =&gt; {
	wp.blocks.unregisterBlockType( 'core/media-text' );
	wp.blocks.unregisterBlockType( 'core/search' );
} );</code></pre>



<p>Here&#8217;s a <a href="https://www.billerickson.net/block-styles-in-gutenberg/#block-names">list of all the core block types</a>.</p>



<h2 class="wp-block-heading">Unregister blocks everywhere</h2>



<p>The above code only unregisters the block from the &#8220;Edit Post&#8221; screen. If you&#8217;re trying to remove blocks from the Widgets screen, Full Site Editor, or Template Parts, you&#8217;ll need to adjust the dependencies.</p>



<p>Please refer to Jason Lemahieu&#8217;s article for more information: <a href="https://jasonlemahieu.com/2022/12/20/how-to-unregister-wordpress-blocks-from-everywhere/">How to Unregister WordPress Blocks (from everywhere!)</a></p>
<p>The post <a rel="nofollow" href="https://www.billerickson.net/how-to-remove-core-wordpress-blocks/">How to remove core WordPress blocks</a> appeared first on <a rel="nofollow" href="https://www.billerickson.net">Bill Erickson</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.billerickson.net/how-to-remove-core-wordpress-blocks/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		
			</item>
	</channel>
</rss>