Background
weaverse-builder#2208 added native Portable Text output to the Content API:
GET /pages/:type/:handle?format=portable-text
GET /theme-settings?format=portable-text
Accept: application/portable-text+json
(Implementation: Weaverse/builder#2236)
This issue tracks the SDK-side convenience helper, originally Phase 3 of the plan. It was deferred from the Builder PR because it lives in this repo.
Scope
Add a small helper to @weaverse/hydrogen that lets developers convert an existing weaverse-format page payload to Portable Text on the client/edge — useful for codebases that haven't switched their server-side fetch to ?format=portable-text yet, or that need both formats at once.
import { toPortableText } from '@weaverse/hydrogen/portable-text'
const ptContent = toPortableText(page.items, { meta: true })
The conversion logic is identical to the server-side serializer in builder/app/backend/content-api/portable-text/ — we should port (or vendor) the pure module, not duplicate it.
Deliverables
- New subpath export:
@weaverse/hydrogen/portable-text exposing:
toPortableText(items, options?)
htmlToPortableText(html)
richtextValuesToPortableText(value) (for theme settings)
- Type exports:
PTBlock, PTSpan, PTMarkDef, etc.
- Same options surface as Builder:
{ meta?: boolean; keyLength?: number }.
- Tree-shakeable — pure module, no React or Hydrogen imports.
- Tests mirroring the Builder unit tests.
- Docs example (in the existing Hydrogen docs) showing rendering with
@portabletext/react against either:
- server-fetched PT (
?format=portable-text), or
- client-converted PT (using this helper).
Implementation notes
- The Builder serializer uses
node-html-parser (works in both Node and edge runtimes — no DOM dependency). Should work as-is for Hydrogen / Oxygen / Cloudflare Workers.
- Builder source to port:
builder/app/backend/content-api/portable-text/ (6 files, ~670 lines incl. types).
- Builder spec with full design + decisions:
builder/.specs/2026-05-02--portable-text-content-api/.
- Follows the fixed-version group release rhythm (core → react → hydrogen) — this would land as a hydrogen-only minor.
Non-goals
- No HTML output format — the issue explicitly deferred that.
- No schema-driven richtext detection — the same heuristic (
isHtml) used in the Builder is fine for v1.
- No round-trip helper (PT → weaverse) — not requested; the
?meta=true _weaverse.id is enough for consumers who need to map back.
Priority
Low — the server-side endpoint already returns PT directly via ?format=portable-text. This is a convenience for adoption, not a blocker. File when there's user demand.
Background
weaverse-builder#2208added native Portable Text output to the Content API:GET /pages/:type/:handle?format=portable-textGET /theme-settings?format=portable-textAccept: application/portable-text+json(Implementation: Weaverse/builder#2236)
This issue tracks the SDK-side convenience helper, originally Phase 3 of the plan. It was deferred from the Builder PR because it lives in this repo.
Scope
Add a small helper to
@weaverse/hydrogenthat lets developers convert an existingweaverse-format page payload to Portable Text on the client/edge — useful for codebases that haven't switched their server-side fetch to?format=portable-textyet, or that need both formats at once.The conversion logic is identical to the server-side serializer in
builder/app/backend/content-api/portable-text/— we should port (or vendor) the pure module, not duplicate it.Deliverables
@weaverse/hydrogen/portable-textexposing:toPortableText(items, options?)htmlToPortableText(html)richtextValuesToPortableText(value)(for theme settings)PTBlock,PTSpan,PTMarkDef, etc.{ meta?: boolean; keyLength?: number }.@portabletext/reactagainst either:?format=portable-text), orImplementation notes
node-html-parser(works in both Node and edge runtimes — no DOM dependency). Should work as-is for Hydrogen / Oxygen / Cloudflare Workers.builder/app/backend/content-api/portable-text/(6 files, ~670 lines incl. types).builder/.specs/2026-05-02--portable-text-content-api/.Non-goals
isHtml) used in the Builder is fine for v1.?meta=true_weaverse.idis enough for consumers who need to map back.Priority
Low — the server-side endpoint already returns PT directly via
?format=portable-text. This is a convenience for adoption, not a blocker. File when there's user demand.