GutenbergKit's native REST URL builder assumes a path-based site API root, so it can't address a self-hosted WordPress site that uses plain permalinks — where the only REST root is the query form https://site/?rest_route=/.
Summary
WordPressRESTURL.namespaced (iOS) and RestUrlBuilder.namespaced (Android) build every native REST URL by appending the endpoint to the path (apiRoot.appending(path:) / string concat).
- A plain-permalink site has no path-based REST root:
/wp-json/ depends on the rewrite rules that plain permalinks disable, so WordPress advertises ?rest_route=/ as the site's REST root.
- Result: for such a site, every native REST URL is malformed — not just media uploads.
- WP.com sites are unaffected — they're reached through the path-based
public-api.wordpress.com/ proxy (SitePreparationView.swift / SitePreparationViewModel.kt set siteApiRoot = https://public-api.wordpress.com/), which abstracts the underlying site's permalinks away.
Root cause
namespaced does apiRoot.appending(path:). Given siteApiRoot = https://site/?rest_route=/:
namespaced(root, "/wp-block-editor/v1/settings") → https://site/wp-block-editor/v1/settings?rest_route=/ → 404
namespaced(root, "/wp/v2/media") → https://site/wp/v2/media?rest_route=/ → 404
The /wp/v2/… lands on the URL path (which doesn't route on a plain-permalink site) with a stray rest_route=/ query, instead of https://site/?rest_route=/wp/v2/media.
Impact
Every endpoint routed through namespaced — in RESTAPIRepository alone: editor settings, active theme, site settings, post types — plus the native media-upload endpoint. namespaced has no rest_route handling and its unit tests (both platforms) cover only path-based roots.
This is pre-existing — the logic was extracted from the prior buildNamespacedURL and carried the assumption forward. It surfaced while reviewing the native media-upload work (#561), whose mediaEndpointURL relies on namespaced.
Proposed fix
Teach namespaced (both platforms) to detect a rest_route query on the root and append the endpoint — and the site namespace — to the rest_route value rather than the URL path:
root = https://site/?rest_route=/
build /wp/v2/media → https://site/?rest_route=/wp/v2/media ✓
Path-based roots keep today's behavior. Add the query-based-root tests the builders never had. This fixes all native endpoints at once, and the media upload's ?_embed=wp:featuredmedia query then merges correctly for free (there's a latent iOS/Android divergence in mediaEndpointURL's query append — percentEncodedQuery overwrite vs + concat — that's moot until the base is correct).
Open question
Reachability depends on whether the host apps (WordPress-iOS / Jetpack) actually connect to self-hosted plain-permalink sites and hand GutenbergKit a ?rest_route= root. Worth confirming before prioritizing — though a plain-permalink site has nothing else it could hand over.
GutenbergKit's native REST URL builder assumes a path-based site API root, so it can't address a self-hosted WordPress site that uses plain permalinks — where the only REST root is the query form
https://site/?rest_route=/.Summary
WordPressRESTURL.namespaced(iOS) andRestUrlBuilder.namespaced(Android) build every native REST URL by appending the endpoint to the path (apiRoot.appending(path:)/ string concat)./wp-json/depends on the rewrite rules that plain permalinks disable, so WordPress advertises?rest_route=/as the site's REST root.public-api.wordpress.com/proxy (SitePreparationView.swift/SitePreparationViewModel.ktsetsiteApiRoot = https://public-api.wordpress.com/), which abstracts the underlying site's permalinks away.Root cause
namespaceddoesapiRoot.appending(path:). GivensiteApiRoot = https://site/?rest_route=/:The
/wp/v2/…lands on the URL path (which doesn't route on a plain-permalink site) with a strayrest_route=/query, instead ofhttps://site/?rest_route=/wp/v2/media.Impact
Every endpoint routed through
namespaced— inRESTAPIRepositoryalone: editor settings, active theme, site settings, post types — plus the native media-upload endpoint.namespacedhas norest_routehandling and its unit tests (both platforms) cover only path-based roots.This is pre-existing — the logic was extracted from the prior
buildNamespacedURLand carried the assumption forward. It surfaced while reviewing the native media-upload work (#561), whosemediaEndpointURLrelies onnamespaced.Proposed fix
Teach
namespaced(both platforms) to detect arest_routequery on the root and append the endpoint — and the site namespace — to therest_routevalue rather than the URL path:Path-based roots keep today's behavior. Add the query-based-root tests the builders never had. This fixes all native endpoints at once, and the media upload's
?_embed=wp:featuredmediaquery then merges correctly for free (there's a latent iOS/Android divergence inmediaEndpointURL's query append —percentEncodedQueryoverwrite vs+concat — that's moot until the base is correct).Open question
Reachability depends on whether the host apps (WordPress-iOS / Jetpack) actually connect to self-hosted plain-permalink sites and hand GutenbergKit a
?rest_route=root. Worth confirming before prioritizing — though a plain-permalink site has nothing else it could hand over.