We have two customer-account UI extensions targeting the same extension point, customer-account.profile.block.render, both added to the Profile page’s Main section via the checkout & accounts editor. One works correctly the other doesn’t. The other’s extension-point wrapper is created in the DOM, but nothing ever mounts inside it — no iframe, no content, and our extension’s own code (confirmed via debug logging) never even runs. This reproduces regardless of where in the section the block is placed, and regardless of whether the other, working block is present or removed entirely.
Setup
api_version = "2026-07"for both extensions.- Both extensions declare the identical target in
shopify.extension.toml:
[[extensions.targeting]]
module = "./src/Block.jsx"
target = "customer-account.profile.block.render"
[extensions.capabilities]
network_access = true
- Both use the same entry-point shape:
import "@shopify/ui-extensions/preact";
import { render } from "preact";
export default async () => {
render(<Extension />, document.body);
};
- “Loyalty Birthday” (
handle: customer-account-birthday) — works correctly, renders every time. - “Store Credit Tier” (
handle: customer-account-store-credit-tier) — never renders, in the editor preview or on the live storefront account page, for any test customer.
What we’ve tested
-
Confirmed the backend/data side is fine. Store Credit Tier’s extension calls our own app backend over
network_accessto read a value and display it. We added logging on our backend and watched it live while reloading the account page — zero requests ever arrive, even though the working extension’s equivalent backend call fires normally. -
Confirmed the extension script itself loads. The Network tab shows the compiled extension bundle (
.js) being fetched successfully (200, served from Shopify’s extension CDN), so the extension is registered and its code is being served. -
Added an unconditional debug render — an
<s-text>with no dependency on any fetched data, rendered synchronously on mount regardless of state. It never appears anywhere on the page, in the editor or live. This rules out a data/timing bug in our own render logic; the component’s render function never appears to execute at all, or has nowhere to render into. -
Ruled out placement/positioning in the editor UI. We tried every position within the same section (above, below, every reorder), including removing the working “Loyalty Birthday” block entirely so “Store Credit Tier” was the only block in that section. No change in behavior.
-
Inspected the registered extension config directly via the page’s
extensions-dataserialized script tag. This is where we found the actual difference — both extensions declare the sametarget, but theplacementReferenceassigned by the editor is consistently different, every time, regardless of position or what else is present:
// Loyalty Birthday (renders correctly)
"targets": [{
"target": "customer-account.profile.block.render",
"placementReference": "PROFILE1",
"position": 0
}]
// Store Credit Tier (never renders)
"targets": [{
"target": "customer-account.profile.block.render",
"placementReference": "PAGE_TITLE",
"position": 0
}]
- Inspected the actual rendered DOM for both. Birthday’s wrapper is fully populated with real rendered content:
<div data-testid="ui-extension-point-wrapper">
<div ...>
<h2>Birthday</h2>
<!-- full rendered form content -->
</div>
</div>
Store Credit Tier’s wrapper (with Birthday removed, so this is the only block in the section) is created, but completely empty — no iframe, no shadow content, nothing:
<div data-testid="ui-extension-point-wrapper">
<div data-inspector-id="[redacted]"></div>
</div>
- Checked the official docs ( Customer account UI extensions ) to confirm we weren’t missing a separate/more specific target string for a different region of the Profile page. There’s only one documented target for the whole page (
customer-account.profile.block.render), with sub-placement described as fully merchant-controlled via the editor — i.e. this is documented as something that should just work for any block dropped anywhere on the page.
What we think is going on
Given the wrapper element is created (so the anchor point itself is recognized) but never gets an iframe or any content mounted into it — even though our extension’s script loads successfully and is confirmed active in extensions-data — this looks like the account-web runtime doesn’t actually mount a third-party app block for the PAGE_TITLE placement on the Profile page, while it does for PROFILE1. Since the editor consistently assigns our block to PAGE_TITLE no matter where we drop it (even as the only block in the section), we don’t have a way to force PROFILE1 from our side — this isn’t something controllable from shopify.extension.toml or the extension source, since both extensions declare the identical target.
What we’re hoping to learn
- Is
PAGE_TITLEa currently-unsupported/reserved placement for third-party app blocks oncustomer-account.profile.block.render, and is there any way to avoid the editor assigning it? - Is this a known issue, and if so is there a workaround or fix timeline?