How to get WordPress REST API self-requests working in a local development environment, i.e. Docker

I’ve only just started using Docker to run a local development environment for testing my WordPress plugin development. (Yeah, don’t get started. I’ve been working successfully for nearly 3 decades by testing my code on a remote development server.)

Using Docker is great! It’s so much faster to be able to just save my code and hit refresh to see the changes immediately, without having to sync the changes to the server… and local load times are lightning-fast!

But there’s a problem. ICS Calendar Pro‘s main functionality is to retrieve and process data from external URLs (specifically iCalendar subscription feeds), using the wp_remote_get() function. Once I added the ability to create and manage similar data locally, the obvious solution to me was to still retrieve and process that local data as if it were an external source, so ICS Calendar Pro generates ICS feeds for the internal event data, and then it makes wp_remote_get() requests to itself.

This was not working in Docker, and it took me several hours of wading through a sea of AI slop on the WordPress “how-to” websites that have spread across the web like a film of toxic algae, to finally find an answer.

Port forwarding.

I access the local Docker site at the URL http://localhost:8888 but it didn’t occur to me immediately that since Docker is running a virtualized Linux system inside a container, it’s Docker that is listening on port 8888 of my Mac system, but then Docker is routing those requests to Apache listening on port 80 inside the container.

Since the plugin’s REST API requests are coming from inside the container, Apache inside the container also needs to be listening on the port Docker is using outside the container.

This involved going into the file system of the container within Docker and editing the /etc/apache2/ports.conf file, to add the second line shown below:

Listen 80
Listen 8888

I restarted the container and boom! Everything was working.

Well… I think that’s because I had already taken another step that was probably essential for this. Since the WP REST API is using URLs with the wp-json/ path, WordPress’s URL routing needs to be activated. By default, the WordPress installation in the Docker container had the Permalink structure set to “Plain.” Changing that to anything else (on the Settings → Permalinks page) makes the REST URLs work.

Building a custom WordPress block without a React/JSX build process

I spent two solid days trying to make this work. And it’s not purely because I’m too stubborn to learn React/JSX. That’s part of it.

It’s more that I’m too stubborn to introduce an arbitrary build process into my development workflow. I have my own idiosyncratic ways of doing things, and I’m coding solo, so I generally don’t need to conform to someone else’s standard practices. As long as what I’m doing has its own internal consistency and safeguards, and produces a reliable product, who cares how I get there?

I actually have introduced a few “build” processes into my own workflow, using Git (out of necessity) for version control of my WordPress Plugin Directory plugins, my own custom versioning process for non-repository plugins, and “minifying” all of my JavaScript and CSS (using online minifiers).

But I am not doing any significant amount of block development. Certainly not enough to learn JSX and implement the arbitrary build process into my workflow, adding thousands of unnecessary dependency files to my development codebase, etc.

For the latest version of ICS Calendar Pro (version 6), currently in development, I just needed to add a simple custom block that lets users insert a calendar (a Custom Post Type) by selecting one from a dropdown list. It’s a more user-friendly option than what they’ve had to do up to this point: copying a small code snippet from the CPT’s admin page and pasting it into a Shortcode block.

Ideally my new block would render a preview of the calendar in the Block Editor, but that’s secondary to just having a way to insert the block itself and pick from the list of saved calendars.

I found the official list of block development examples, and made some progress with the “no build” example. I also found a great article from Cello Expressions (maker of the Sheet Music Library plugin), called Dynamic Blocks with Block.json, Vanilla JS, and No Build Process. I really thought that was going to get me exactly what I needed… until I realized that their particular needs did not involve having any kind of configuration options in the Block Editor itself.

Then I found DahmaniAdame/block.js on GitHub Gist. It was exactly what I needed. A straightforward, no-build example, using Vanilla JavaScript, of a simple block with a dropdown list of posts in the inspector sidebar.

The only problem: it worked perfectly if I stuck with built-in post types, but it would cause a vaguely defined block error when I switched it to using my CPT.

Eventually I realized the problem was here:

const title = post.meta.some_meta1 && post.meta.some_meta1 !== ''
		? post.meta.some_meta1
		: post.title.rendered;

For an as-yet undetermined reason, I’m not able to retrieve meta data about my CPT. (Yes, I changed some_meta1 to the actual key of my meta data field, view.) I decided I didn’t really need to retrieve that meta data, so I just removed the conditional, i.e.:

const title = post.title.rendered;

With that change (and a few others to change the post type I’m querying and remove a few other things I didn’t really need), my block was working perfectly! So much so, in fact, that I decided to try to push it to do more things. There, I ran into some trouble, because my block does a lot of JavaScript stuff when it renders, and I haven’t (yet) figured out how to get that JavaScript to run when the content renders in the Block Editor, so the HTML output of the block does get inserted into the Block Editor when the user selects from the dropdown, but the JavaScript that actually reveals it on the front-end page isn’t running. (Yes, I used enqueue_block_editor_assets to load my JavaScript and CSS files. And I also added logic to try to force my plugin’s JavaScript initialization event to trigger, but something more complicated is going on.)

Anyway… it’s still not 100% perfect, but I really wish I had found that Gist before I spent two full days banging my head against the wall over this!

How to get Safari not to display the disclosure triangle on an HTML5 <details> <summary> tag in 2025

I hate blog posts with “in (insert current year here)” in the title, but it seems important here, because none of the stuff I’m finding in the usual locations, even StackOverflow, CSS-Tricks, or the Mozilla Developer Network really seems to be getting this right.

So, you’re using the HTML5 <details> tag, and you want to customize the disclosure toggle element on the <summary> tag? Most browsers play nicely with summary::marker in CSS, but not Safari.

Forget this (the old rule everyone has been recommending but that doesn’t seem to do anything, at least, not anymore):

details summary::-webkit-details-marker { display: none; }

Fortunately, the solution (at least, what seems at this very moment to be working for me) is even easier, once you find it:

details summary { list-style: none; }

(OK I did actually find this solution on the MDN link above, but it also includes the bit that I said doesn’t seem to do anything.)

A practical example

Let’s say you want to get rid of the triangle and replace it with a plus sign (+) that changes to a minus sign (-) when toggled open. You can certainly improve the aesthetics of this with color, or images, or some margin/padding tweaks, but this is the essence. Confirmed to be all you need in both Safari and Firefox (the only browsers I currently have installed on my Mac), but it should work in Chrome too.

details summary {
    list-style: none;
}
details summary::before {
    content: "+";
}
details[open] summary::before {
    content: "−";
}

How to replace line break tags (<br>) with spaces in CSS

You can’t. This post on TimonWeb with the same title as mine is wrong. As is this one from Gallagher Website Design, which of course it is, because it’s exactly the same.

And now that all of the search engines (even my beloved DuckDuckGo) are using AI to gather answers, the LLMs are permanently ensconcing this incorrect answer to the question.

It doesn’t work.

I get why it might seem like it works. In fact it’s kind of not really doing anything… at least in terms of adding the space. The only reason there’s a space between the words where the <br> tag got removed is because there’s a space there in the HTML.

HTML honors spaces. Well, one space. If there’s a line break, that’s the space. If you get rid of the line break and there’s still space in the HTML, then HTML renders a space. But if removing the <br> tag pushes two words together, they’re really going to get pushed together.

The only way (at least, the only way I’ve been able to find, and I’ve spent more time on this than it’s worth) to get there to be a space when you remove the <br> tag, is to actually have a space in the HTML.

Since I’m dealing with WordPress, which is the reason it’s a problem, I have also come up with a WordPress-specific solution. The reason it’s a problem in WordPress, at least in the Block Editor (a.k.a. Gutenberg) is that if you insert a line break in your text, i.e. by typing Shift-Enter, it inserts a <br> tag without any space between the words.

I guess Gutenberg is trying to be efficient by not adding an unnecessary space. You know, just like how it makes efficient use of CSS class names and inline styles like this:

<div class="wp-block-columns are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-7d06e0e1 wp-block-columns-is-layout-flex" style="margin-top:var(--wp--preset--spacing--80);margin-bottom:var(--wp--preset--spacing--80)">

I’d insert the eyeroll emoji here, but I use my No Nonsense plugin to prevent WordPress from unnecessarily… uh, I mean efficiently, loading a bunch of emoji-related JavaScript on every page of every one of the 42% of the websites in the world it powers.

Let’s see, now where was I? You can use the the_content filter to force WordPress to add a space after <br> tags in page content:

function my_the_content($content) {
    $content = str_replace('<br>', '<br> ', $content);
    return $content;
}
add_filter('the_content', 'my_the_content', 11, 1);

(Yes, this might be adding an extra space in places where there’s already a space after the <br> but it doesn’t matter… as I said, HTML honors the space, but only one space.)

Once you’ve got that in place, then you can write some CSS to selectively remove <br> tags from display. In my case, I want to remove them in Cover blocks on mobile devices, and my mobile breakpoint is 1024 pixels. So here’s the CSS I wrote:

@media screen and (max-width: 1024px) {
    .wp-block-cover__inner-container br { display: none; }
}

Note here that I’m just hiding the <br> tags altogether, which is all you really can do. But as long as that <br> tag is followed by an actual space (or an actual line break) in the HTML code itself, then you’ll get a space between the words.

No one planned for this

I have a client whose website I’ve been managing for over a decade.

Long before I was working with them, the client registered their domain with a company called NameSecure. As you can see by clicking that link, their website still exists, but they were long ago purchased by Network Solutions (one of the worst companies in the world). NameSecure’s zombie, circa 2002 website lives on though. I can still log into the client’s account. I’ll come back to that in a minute.

When I started working with the client, my preferred hosting company was Media Temple. As you can see by clicking that link, their website doesn’t exist anymore, after they were acquired and dismantled several years ago by GoDaddy. The company whose name, I still believe, was supposed to be “God Addy,” as in “the god of addresses,” when some nascent techbro registered it in 1999, before marketing stepped in and retconned it to the slightly less awful name that the world has known ever since they kicked off their sexist, years-long Super Bowl ad campaign of the early 2000s. (And yes, I’ve read the contrary history on Wikipedia.)

GoDaddy mostly absorbed everything Media Temple-related, and the client set me up with “Delegate Access” so I can get into their GoDaddy account. That’s where we made our DNS changes a few years ago to point to the Digital Ocean server I was running their site on until Digital Ocean became a disaster. And it’s where I went again last year when I moved their site to WP Engine. (Don’t even get me started.)

Well they’ve had it with WP Engine’s ludicrous overage charges (and so have I), so now we’re moving on to the hosting-company-that-only-mostly-sucks-rather-than-completely-sucks du jour, Hostinger. So far the worst I can say about Hostinger is that sometimes their web control panel just stops responding. But I blame React (and therefore, indirectly, Facebook/Meta) for that.

I’ve got their site ready to go on Hostinger, so now it’s just down to the DNS update.

I started by logging into GoDaddy, and was surprised to see there are no domains listed for their account. Then I pieced together what must have happened. We had canceled their Media Temple hosting several years ago, but there was still a (no longer necessary) annual SSL certificate auto-renewal that kept happening. The client finally reached out to me in time to stop the auto-renewal this year, so we canceled it, effectively ending all of the client’s paid services with GoDaddy. But their domain continued to use the Media Temple name servers.

I’m just guessing here, but it seems that canceling the last paid GoDaddy service also shut off access to the domain management tools. So, their undead DNS zone file endures on Media Temple’s zombie name servers, but with no way to make any edits.

No worries, I thought. Let’s just jump back to NameSecure. It’s ancient, but it is where their domain is actually registered, and they do let you set your domain to use their own name servers. (I always prefer to keep name servers at the registrar, whenever possible, anyway.)

But… oh dear. NameSecure’s zone file editor only lets you create A, MX, and CNAME records. No TXT records. Seriously… no TXT records??? In 2025, those are essential for a variety of reasons. So, we can’t do that.

OK… then I guess our only alternative is to… ugh… move the name servers over to Hostinger. That is obviously what Hostinger wants us to do. That’s what hosting companies always want you to do, and the main reason I don’t want to do it. It’s a lot more of a pain to switch hosting companies when your current host is handling your DNS. It’s not truly lock-in, but it might as well be.

Anyway, that’s where we are. No one back in the late ’90s and early 2000s planned for the 2025 domain registrar zombie apocalypse.

And now, I’m just sitting here, waiting for the name server change to propagate, so I can actually edit the client’s zone file in Hostinger. This is the next-worst thing I can say about Hostinger: if you host domains, let them edit the DNS zone file even before they’re using your name servers! It would be so, so nice if I could plug in all of the client’s MX records while I’m waiting for propagation. As it is, there’s going to be some indeterminate amount of time when their email absolutely will stop working, between when the name server change propagates, making Hostinger allow me to actually edit the damn zone file, and when I actually get those records plugged in and they propagate.

Ideally, this should only take a few minutes. But name server changes can take anywhere from a few minutes to a day or more to propagate. Do I just need to sit here on the edge of my seat that entire time?

I guess so.

Well, that has me all caught up on this stupid situation. I don’t have much else to say about it… I just have to wait. But all of this just feels like another nail in the coffin of this stupid industry that never planned ahead and also never seems to learn from past mistakes.