Skip to content

fix: build REST URLs for sites using plain permalinks - #573

Draft
dcalhoun wants to merge 2 commits into
build/bump-wordpress-rs-0.6.0from
fix/support-plain-permalinks
Draft

fix: build REST URLs for sites using plain permalinks#573
dcalhoun wants to merge 2 commits into
build/bump-wordpress-rs-0.6.0from
fix/support-plain-permalinks

Conversation

@dcalhoun

Copy link
Copy Markdown
Member

What?

Teaches the native REST URL builders to address sites that use plain permalinks, where the only REST root is the query form https://site/?rest_route=/.

Stacks on #572 (the wordpress-rs 0.6.0 bump), so it targets build/bump-wordpress-rs-0.6.0 rather than trunk.

Fixes #563.

Why?

A site with plain permalinks has no path-based REST root — /wp-json/ depends on the rewrite rules that plain permalinks disable, so WordPress advertises ?rest_route=/ instead. Both hosts hand that root straight to GutenbergKit (EditorConfiguration+Blog.swift on iOS, GutenbergKitSettingsBuilder.kt on Android) after discovering it from the site's https://api.w.org/ Link header, so it reaches every native URL builder verbatim.

Those builders appended the endpoint to the path, which produced malformed URLs for every native endpoint — editor settings, active theme, site settings, post types, the post itself, and editor assets.

Worth noting the iOS failure was silent, not a 404. Verified against a live plain-permalink site:

GET https://site/wp/v2/settings?rest_route=/   -> 200 {"name":…,"namespaces":[…]}   # the REST API index
GET https://site/?rest_route=/wp/v2/settings   -> 200 {"title":…,"date_format":…}   # the actual settings

The trailing ?rest_route=/ routes to the API root and the path is ignored, so the editor received the wrong resource and failed later at decode time with no indication the URL was wrong. On Android the two query-carrying endpoints did 404 outright.

WP.com sites are unaffected — they use the path-based public-api.wordpress.com/ proxy.

How?

Both platforms mirror the algorithm in @wordpress/api-fetch's root URL middleware, which the web layer already uses (and which is why JS requests work today on these sites): when the root carries a query, append the endpoint to it and merge the path's own query string with &.

  • iOSURL.appending(rawPath:) (Foundation+Extensions.swift), the primitive every iOS REST URL bottoms out in. EditorAssetLibrary used stock URL.appending(path:), which had the same defect, so it now routes through the same primitive.
  • Android — new String.appendingRestPath (StringExtensions.kt), replacing the trimEnd('/')-plus-concat in RESTAPIRepository and both asset libraries. Namespace insertion is split into namespacedPath() so the path transform and the root join are separate concerns.

The fix lives in the join primitive rather than in namespace insertion deliberately: two of the three Android call sites (and the iOS asset library) build their URLs without going through namespacing, so they would otherwise stay broken.

Also aligns a pre-existing platform divergence in namespace insertion — iOS namespaced two-segment paths (components.count >= 2) where Android bailed (parts.size < 3); Android now matches iOS.

Note for #357 / #561

#357 introduces WordPressRESTURL.namespaced (iOS) and RestUrlBuilder.namespaced (Android). This branch and that one merge without textual conflict, but two things need coordinating — details in a comment on #357:

  • iOS composes cleanly (WordPressRESTURL.namespaced already calls appending(rawPath:) on every path), but Android's RestUrlBuilder.namespaced needs to delegate its join to appendingRestPath, or a clean merge silently reverts the Android fix.
  • mediaEndpointURL needs & merging on both platforms before it works on a query root.

Testing Instructions

Requires a self-hosted site set to Settings ▸ Permalinks ▸ Plain. Confirm it advertises the query-form root first:

curl -sI https://your-site.com/ | grep -i '^link'

The rel="https://api.w.org/" value should contain ?rest_route=/ rather than /wp-json/.

  1. Build the demo app on either platform and add the site using an application password.
  2. Open the Editor Configuration screen and confirm the API Root row shows the ?rest_route= form.
  3. Open a post in the editor.

Check that:

  • The post's existing content loads (/wp/v2/posts/{id}).
  • The editor picks up theme styles — colors and fonts match the site's theme rather than defaults (/wp-block-editor/v1/settings and /wp/v2/themes). This is the highest-value check: the themes endpoint is the only one carrying its own query string, so it's what exercises the ?& merge.
  • The block inserter is populated (/wp/v2/types).
  • Saving and publishing work.

Enable Network Logging in the site-preparation screen to inspect the request URLs directly — they should read ?rest_route=/wp/v2/…, not /wp/v2/…?rest_route=/.

Also confirm no regression on a pretty-permalink site and a WP.com site.

Accessibility Testing Instructions

N/A — no user interface changes.

dcalhoun and others added 2 commits July 31, 2026 16:38
Sites with plain permalinks have no path-based REST root, so WordPress
advertises the query form `https://site/?rest_route=/` instead. Appending
an endpoint to that root landed the path before the query, malforming
every native REST URL.

Teach `URL.appending(rawPath:)` to append the endpoint to the query value
when the root carries one, merging the path's own query string with `&`.
This mirrors `@wordpress/api-fetch`'s root URL middleware, which the web
layer already uses, so native and web requests resolve identically.

Also route `editorAssetsUrl` through the same primitive instead of
`URL.appending(path:)`, which had the same defect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sites with plain permalinks have no path-based REST root, so WordPress
advertises the query form `https://site/?rest_route=/` instead. The root
was normalized with `trimEnd('/')` and concatenated with the endpoint,
which mangled the route value and emitted a second `?` for endpoints that
carry their own query string.

Add `String.appendingRestPath`, which appends the endpoint to the query
value when the root carries one and merges the path's query string with
`&`. This mirrors `@wordpress/api-fetch`'s root URL middleware, which the
web layer already uses, so native and web requests resolve identically.

Route the repository and both asset library URL builders through it, and
align namespace insertion with iOS, which also namespaces two-segment
paths.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the [Type] Bug An existing feature does not function as intended label Jul 31, 2026
@linear linear Bot mentioned this pull request Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant