{"id":19924392,"url":"https://github.com/kontent-ai/rich-text-resolver-js","last_synced_at":"2026-03-11T03:31:44.155Z","repository":{"id":37472258,"uuid":"505867543","full_name":"kontent-ai/rich-text-resolver-js","owner":"kontent-ai","description":"Rich text resolution tool based on Portable Text standard and a HTML transformer for rich text migration purposes in one.","archived":false,"fork":false,"pushed_at":"2026-01-29T11:03:30.000Z","size":2006,"stargazers_count":11,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-01-30T01:41:18.850Z","etag":null,"topics":["headless-cms","html","html-transformation","javascript","kontent-ai","migration","portable-text","portabletext","rich-text","rich-text-transformation","richtext","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@kontent-ai/rich-text-resolver","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kontent-ai.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-06-21T14:00:44.000Z","updated_at":"2026-01-29T11:03:04.000Z","dependencies_parsed_at":"2024-02-15T15:28:07.388Z","dependency_job_id":"db55dabe-ac3e-4e35-a3ba-285d8d39ab26","html_url":"https://github.com/kontent-ai/rich-text-resolver-js","commit_stats":null,"previous_names":["pokornyd/rich-text-resolver"],"tags_count":41,"template":false,"template_full_name":null,"purl":"pkg:github/kontent-ai/rich-text-resolver-js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kontent-ai%2Frich-text-resolver-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kontent-ai%2Frich-text-resolver-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kontent-ai%2Frich-text-resolver-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kontent-ai%2Frich-text-resolver-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kontent-ai","download_url":"https://codeload.github.com/kontent-ai/rich-text-resolver-js/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kontent-ai%2Frich-text-resolver-js/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30369379,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T21:41:54.280Z","status":"online","status_checked_at":"2026-03-11T02:00:07.027Z","response_time":84,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["headless-cms","html","html-transformation","javascript","kontent-ai","migration","portable-text","portabletext","rich-text","rich-text-transformation","richtext","typescript"],"created_at":"2024-11-12T22:17:17.058Z","updated_at":"2026-03-11T03:31:44.138Z","avatar_url":"https://github.com/kontent-ai.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![resolverlogo](https://github.com/kontent-ai/rich-text-resolver-js/assets/52500882/5cd40306-1b36-4d57-8731-f7718e61ebea)\n# Kontent.ai rich text resolver\n\n![Last modified][last-commit]\n[![Issues][issues-shield]][issues-url]\n[![Contributors][contributors-shield]][contributors-url]\n[![MIT License][license-shield]][license-url]\n[![codecov][codecov-shield]][codecov-url]\n[![Stack Overflow][stack-shield]](https://stackoverflow.com/tags/kontent-ai)\n[![Discord][discord-shield]](https://discord.gg/SKCxwPtevJ)\n\nThis package provides utilities for transforming Kontent.ai rich text into structured formats suitable for resolution and rendering in various environments.\n\n## Installation\n\nInstall the package via npm\n\n`npm i @kontent-ai/rich-text-resolver`\n\n---\n\n## Features\n\n### API Overview\n\n![Module API](media/resolver-api-overview.png)\n\n### Parsing rich text HTML to an array of simplified nodes\n\nThe tool provides environment-aware (browser or Node.js) `parseHTML` function to transform HTML into an array of `DomNode` trees. Any valid HTML is parsed, including all attributes. Together with built-in transformation methods, this tool is a suitable option for processing HTML and rich text from external sources, to make it compatible with Kontent.ai rich text format. See dedicated [HTML transformer docs](docs/index.md) for further information.\n\n### Portable text resolution\n\n[Portable Text](https://github.com/portabletext/portabletext) is a universal standard for rich text representation, with tools available for its transformation and rendering in majority of popular frameworks and languages:\n\n- React: [react-portabletext](https://github.com/portabletext/react-portabletext)\n- HTML: [to-html](https://github.com/portabletext/to-html)\n- Svelte: [svelte-portabletext](https://github.com/portabletext/svelte-portabletext)\n- Vue: [vue-portabletext](https://github.com/portabletext/vue-portabletext)\n- Astro: [astro-portabletext](https://github.com/theisel/astro-portabletext)\n\n\u003e [!TIP]\n\u003e This module re-exports modified `toHTML` function and `\u003cPortableText\u003e` component from `to-html` and `react-portabletext` packages respectively. These modified helpers provide default resolution for tags which are either unsupported or only partially supported in the original packages (`sub` and `sup` tags, images, tables and links).\n\u003e\n\u003e Make sure to use these re-exports if you want to take advantage of the default resolution. You can still provide your own custom resolutions for the above tags even when using these helpers. \n\nThe tool provides `transformToPortableText` function to convert rich text content into an array of Portable Text blocks, with custom blocks defined for Kontent.ai-specific objects.\n\nCombined with a suitable package for the framework of your choice, this makes for an optimal solution for resolving rich text.\n\n\u003e [!IMPORTANT]\n\u003e The provided Portable Text transformation functions expect a valid Kontent.ai rich text content, otherwise you risk errors or invalid blocks in the resulting array.\n\n#### Custom portable text blocks\n\nBesides default blocks for common elements, Portable Text supports custom blocks, which can represent other entities. Each custom block should extend `ArbitraryTypedObject` to ensure `_key` and `_type` properties are present. Key should be a unique identifier (e.g. guid), while type should indicate what the block represents. Value of `_type` property is used for mapping purposes in subsequent resolution.\n\n**This package comes with built-in custom block definitions for representing Kontent.ai rich text entities:**\n\n##### Component/linked item – **PortableTextComponentOrItem**\n\nhttps://github.com/kontent-ai/rich-text-resolver-js/blob/83ef999b3deb89f3dd0a6be5f5a61c3fbe5331ee/showcase/showcase.ts#L3-L12\n\n##### Image – **PortableTextImage**\n\nhttps://github.com/kontent-ai/rich-text-resolver-js/blob/83ef999b3deb89f3dd0a6be5f5a61c3fbe5331ee/showcase/showcase.ts#L14-L24\n\n##### Item link – **PortableTextItemLink**\n\nhttps://github.com/kontent-ai/rich-text-resolver-js/blob/83ef999b3deb89f3dd0a6be5f5a61c3fbe5331ee/showcase/showcase.ts#L26-L34\n\n##### Table – **PortableTextTable**\n\nhttps://github.com/kontent-ai/rich-text-resolver-js/blob/83ef999b3deb89f3dd0a6be5f5a61c3fbe5331ee/showcase/showcase.ts#L36-L62\n\n## Examples\n\n### Plain HTML resolution\n\nHTML resolution using a slightly modified version of `toHTML` function from `@portabletext/to-html` package.\n\n```ts\nimport {\n  transformToPortableText,\n  resolveTable,\n  resolveImage,\n  PortableTextHtmlResolvers,\n  toHTML\n} from \"@kontent-ai/rich-text-resolver\";\n\nconst richTextValue = \"\u003crich text html\u003e\";\nconst linkedItems = [\"\u003carray of linked items\u003e\"]; // e.g. from SDK\nconst portableText = transformToPortableText(richTextValue);\n\nconst resolvers: PortableTextHtmlResolvers = {\n  components: {\n    types: {\n      image: ({ value }) =\u003e {\n        // helper method for resolving images\n        return resolveImage(value); \n      },\n      componentOrItem: ({ value }) =\u003e {\n        const linkedItem = linkedItems.find(\n          (item) =\u003e item.system.codename === value.componentOrItem._ref\n        );\n        switch (linkedItem?.system.type) {\n          case \"component_type_codename\": {\n            return `\u003cp\u003eresolved value of text_element: ${linkedItem?.elements.text_element.value}\u003c/p\u003e`;\n          }\n          default: {\n            return `Resolver for type ${linkedItem?.system.type} not implemented.`;\n          }\n        };\n      },\n      table: ({ value }) =\u003e {\n        // helper method for resolving tables\n        const tableHtml = resolveTable(value, toHTML);\n        return tableHtml;\n      },\n    },\n    marks: {\n      contentItemLink: ({ children, value }) =\u003e {\n        return `\u003ca href=\"https://website.com/${value.contentItemLink._ref}\"\u003e${children}\u003c/a\u003e`;\n      },\n      link: ({ children, value }) =\u003e {\n        return `\u003ca href=${value?.href} data-new-window=${value?.[\"data-new-window\"]}\u003e${children}\u003c/a\u003e`;\n      },\n    },\n  },\n};\n\nconst resolvedHtml = toHTML(portableText, resolvers);\n```\n\n### React resolution\n\nReact, using a slightly modified version of `PortableText` component from `@portabletext/react` package.\n\n```tsx\nimport {\n  PortableTextReactResolvers,\n  PortableText,\n  TableComponent,\n  ImageComponent,\n} from \"@kontent-ai/rich-text-resolver/utils/react\";\nimport {\n  transformToPortableText,\n} from \"@kontent-ai/rich-text-resolver\";\n\n// assumes richTextElement from SDK\n\nconst resolvers: PortableTextReactResolvers = {\n  types: {\n    componentOrItem: ({ value }) =\u003e {\n      const item = richTextElement.linkedItems.find(item =\u003e item.system.codename === value?.componentOrItem._ref);\n      return \u003cdiv\u003e{item?.elements.text_element.value}\u003c/div\u003e;\n    },\n    // Image and Table components are used as a default fallback if a resolver isn't explicitly specified\n    table: ({ value }) =\u003e \u003cTableComponent {...value} /\u003e,\n    image: ({ value }) =\u003e \u003cImageComponent {...value} /\u003e,\n  },\n  marks: {\n    link: ({ value, children }) =\u003e {\n      return (\n        \u003ca href={value?.href} rel={value?.rel} title={value?.title} data-new-window={value?.['data-new-window']}\u003e\n          {children}\n        \u003c/a\u003e\n      )\n    },\n    contentItemLink: ({ value, children }) =\u003e {\n      const item = richTextElement.linkedItems.find(item =\u003e item.system.id === value?.contentItemLink._ref);\n      return (\n        \u003ca href={\"https://website.xyz/\" + item?.system.codename}\u003e\n          {children}\n        \u003c/a\u003e\n      )\n    }\n  }\n}\n\nconst MyComponent = ({ props }) =\u003e {\n  // https://github.com/portabletext/react-portabletext#customizing-components\n  const portableText = transformToPortableText(props.element.value);\n\n  return (\n    \u003cPortableText value={portableText} components={resolvers} /\u003e\n  );\n};\n```\n\n### Vue resolution\nUsing `@portabletext/vue` package\n\n```ts\n\u003cscript setup\u003e\nimport {\n  PortableText,\n  PortableTextComponentProps,\n  PortableTextComponents,\n  toPlainText,\n} from \"@portabletext/vue\";\nimport {\n  resolveTableVue as resolveTable,\n  resolveImageVue as resolveImage,\n  toVueImageDefault,\n} from \"@kontent-ai/rich-text-resolver\";\n\n\nconst components: PortableTextComponents = {\n  types: {\n    image: ({ value }: PortableTextComponentProps\u003cPortableTextImage\u003e) =\u003e\n      resolveImage(value, h, toVueImageDefault),\n    table: ({ value }: PortableTextComponentProps\u003cPortableTextTable\u003e) =\u003e\n      resolveTable(value, h, toPlainText),\n  },\n  // marks etc.\n};\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cPortableText :value=\"props.value\" :components=\"components\" /\u003e\n\u003c/template\u003e\n```\n\n### Modifying portable text nodes\n\nPackage exports a `traversePortableText` method, which accepts an array of `PortableTextObject` and a callback function. The method recursively traverses all nodes and their subnodes, optionally modifying them with the provided callback:\n\n```ts\n    import {\n      PortableTextObject,\n      transformToPortableText,\n      traversePortableText,\n    } from \"@kontent-ai/rich-text-resolver\";\n\n    const input = `\u003cfigure data-asset-id=\"guid\" data-image-id=\"guid\"\u003e\u003cimg src=\"https://asseturl.xyz\" data-asset-id=\"guid\" data-image-id=\"guid\" alt=\"\"\u003e\u003c/figure\u003e`;\n\n    // Adds height parameter to asset reference and changes _type.  \n    const processBlocks = (block: PortableTextObject) =\u003e {\n      if (block._type === \"image\") {\n        const modifiedReference = {\n          ...block.asset,\n          height: 300\n        }\n  \n        return {\n          ...block,\n          asset: modifiedReference,\n          _type: \"modifiedImage\"\n        }\n      }\n\n      // logic for modifying other object types...\n\n        // return original block if no modifications required\n        return block;\n    }\n\n    const portableText = transformToPortableText(input);\n    const modifiedPortableText = traversePortableText(portableText, processBlocks);\n```\n\n\n### MAPI transformation\n\n`toManagementApiFormat` is a custom transformation method built upon `toHTML` package, allowing you to restore portable text previously created from management API rich text back into MAPI supported format.\n\n```ts\n  const richTextContent =\n    `\u003cp\u003eHere is an \u003ca data-item-id=\"12345\"\u003e\u003cstrong\u003einternal link\u003c/strong\u003e\u003c/a\u003e in some text.\u003c/p\u003e`;\n\n  const portableText = transformToPortableText(richTextContent);\n  \n  // your logic to modify the portable text\n\n  const validManagementApiFormat = toManagementApiFormat(portableText);\n```\n\n\u003e [!IMPORTANT]  \n\u003e MAPI transformation logic expects Portable Text that had been previously created from management API rich text and performs only minimal validation. It doesn't provide implicit transformation capabilities from other formats (such as delivery API).\n\u003e\n\u003e If you're interested in transforming external HTML or rich text to a MAPI compatible format, see [HTML transformer docs](docs/index.md) instead.\n\n[last-commit]: https://img.shields.io/github/last-commit/kontent-ai/rich-text-resolver-js?style=for-the-badge\n[contributors-shield]: https://img.shields.io/github/contributors/kontent-ai/rich-text-resolver-js?style=for-the-badge\n[contributors-url]: https://github.com/kontent-ai/rich-text-resolver-js/graphs/contributors\n[issues-shield]: https://img.shields.io/github/issues/kontent-ai/rich-text-resolver-js.svg?style=for-the-badge\n[issues-url]: https://github.com/kontent-ai/rich-text-resolver-js/issues\n[license-shield]: https://img.shields.io/github/license/kontent-ai/rich-text-resolver-js?label=license\u0026style=for-the-badge\n[license-url]: https://github.com/kontent-ai/rich-text-resolver-js/blob/main/LICENSE\n[stack-shield]: https://img.shields.io/badge/Stack%20Overflow-ASK%20NOW-FE7A16.svg?logo=stackoverflow\u0026logoColor=white\u0026style=for-the-badge\n[discord-shield]: https://img.shields.io/discord/821885171984891914?label=Discord\u0026logo=Discord\u0026logoColor=white\u0026style=for-the-badge\n[codecov-shield]: https://img.shields.io/codecov/c/github/kontent-ai/rich-text-resolver-js/main.svg?style=for-the-badge\n[codecov-url]: https://app.codecov.io/github/kontent-ai/rich-text-resolver-js\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkontent-ai%2Frich-text-resolver-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkontent-ai%2Frich-text-resolver-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkontent-ai%2Frich-text-resolver-js/lists"}