# Handle internal links in Portable Text

https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/internal-links-portable-text

Choose between raw URL strings and document references for internal links, then build the URL map your extraction scripts will use.

---

## Navigation

**Course:** [Migrating from Webflow to Sanity](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity) · [View as markdown](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity.md)

**Previous:** [Create a siteSettings singleton](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/site-settings-singleton) · **Next:** [Fetch your collection items from the API](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/fetch-collection-items-api)

---

## Course Contents

1. [Introduction](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/introduction-to-webflow-migration)
2. [Audit your Webflow content](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/audit-webflow-content)
3. [Recognize Webflow's content modeling patterns](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/recognize-content-modeling-patterns)
4. [Export from Webflow and connect the Data API](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/export-webflow-connect-api)
5. [Initialize Sanity and install the migration toolkit](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/initialize-sanity-migration-toolkit)
6. [Map collections to Sanity document types](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/map-collections-to-schemas)
7. [Build a page builder with a sections array](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/page-builder-sections-array)
8. [Model custom-template pages as singletons](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/custom-template-singletons)
9. [Model navigation in Sanity](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/model-navigation)
10. [Create a siteSettings singleton](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/site-settings-singleton)
11. **Handle internal links in Portable Text** *(current)*
12. [Fetch your collection items from the API](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/fetch-collection-items-api)
13. [Run the asset pre-upload pass](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/asset-pre-upload)
14. [Resolve reference fields from API data](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/resolve-reference-fields)
15. [Convert rich text to Portable Text](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/convert-rich-text-portable-text)
16. [Extract static page content using the page DOM API](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/extract-static-page-content)
17. [Import content with the two-pass pattern](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/two-pass-import)
18. [Validate your import in Studio](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/validate-import-studio)
19. [Set up forms, redirects, and sitemap](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/forms-redirects-sitemap)
20. [Set up draft mode and visual preview](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/draft-mode-visual-preview)
21. [Validate your import without a frontend](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/validate-migration)
22. [Launch checklist and DNS transfer](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/launch-checklist-dns)
23. [AI prompt library and resources](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/appendix-prompt-library-resources)

---

Before writing any extraction scripts, decide how internal links inside rich text become links in Sanity. There are two options.



### Option 1: Raw URL strings



The link stays as a URL path (`/blog/some-post`). Simple to implement, but fragile when slugs change.



### Option 2: Document references (more structured)



Define a custom `internalLink` mark type that stores a reference to a Sanity document. The frontend resolves the reference to the current URL at render time. This is the better long-term choice. Editors get autocomplete when inserting links, and renamed slugs don't silently break.



### Ask AI to generate the internalLink mark type



```text
@sanity/schemaTypes/blockContent.ts @sanity/schemaTypes/index.ts I want to add an internalLink annotation to my Portable Text schema that lets editors link to internal documents by reference. Looking at the document types registered in index.ts, add an internalLink mark type to blockContent.ts that references all document types that have a public-facing URL (i.e. have a slug field). Output the updated blockContent.ts file.
```

The AI reads your actual registered document types and generates the to array from them — so it references caseStudy and solution if those are your types, not a generic page and post. The output should look something like this, with your own type names:



```typescript
// sanity/schemaTypes/blockContent.ts — internalLink added to annotations
{
  name: 'internalLink',
  type: 'object',
  title: 'Internal Link',
  fields: [
    {
      name: 'reference',
      type: 'reference',
      to: [
        // Your actual document types with slug fields go here
        { type: 'page' },
        { type: 'caseStudy' },
      ]
    }
  ]
}
```

### About url-map.json



`url-map.json` is a file your import script reads at runtime. When `rich-text.js` processes an `<a href="/blog/some-post">` tag, it checks `url-map.json` to find the matching Sanity document `_id` and emits an `internalLink` reference annotation instead of a plain external link. Without it, every internal link in your rich text is imported as a plain URL.



This is different from your redirect list. Redirects handle external traffic after DNS transfer. `url-map.json` is used purely during migration to wire up internal references.



You'll generate this file in Chapter 5 after running import pass 1. The step-by-step instructions are there, in the right order.



### Alternative: post-import conversion pass



If you already imported with plain link annotations and want to convert them after the fact, a targeted patch script is often simpler than re-running the full import. The pattern: fetch all documents containing link `markDefs` from Sanity, build a slug-to-`_id` map from your imported documents, iterate over every block's `markDefs`, and for each link whose `href` resolves to a known document `_id`, replace the `markDef` with an `internalLink` reference and update `_type` on any child spans that reference that key. Use `patch.set({ content: updatedBlocks })` to write the result back. This is idempotent, running it again after re-imports keeps everything in sync.



Next, you’ll fetch your collection items from the API.



---

## Related Resources

- [Full course as markdown](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity.md)
- [All courses and lessons](https://www.sanity.io/learn/sitemap.md)
- [Complete content for LLMs](https://www.sanity.io/learn/llms-full.txt)

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://www.sanity.io/learn/resource/feedback

```json
{
  "path": "/learn/course/migrating-content-from-webflow-to-sanity/internal-links-portable-text.md",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>
