# Map collections to Sanity document types

https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/map-collections-to-schemas

Convert your Webflow collection audit into TypeScript schemas using defineType and defineField, then extract shared field groups into reusable types.

---

## 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:** [Initialize Sanity and install the migration toolkit](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/initialize-sanity-migration-toolkit) · **Next:** [Build a page builder with a sections array](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/page-builder-sections-array)

---

## 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** *(current)*
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](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/internal-links-portable-text)
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)

---

In Sanity, your content model is TypeScript: version-controlled, reviewable, and testable. Each Webflow collection that passes the two-question test from the previous lesson becomes a Sanity document type. AI generates the first pass from your actual API data; your job is to review and correct it.



This lesson runs two prompts in sequence. The first generates one schema file per collection. The second reviews that output, finds field groups that repeat across schemas, and extracts them into shared reusable types. Running them separately, with a review step in between, gives you a chance to catch mistakes before they compound.



### Folder conventions



Before running the prompts, set up this folder structure inside `sanity/schemaTypes/`:



```text
sanity/schemaTypes/
  documents/       — document types (page, blogPost, author, etc.)
  objects/         — reusable embedded types (seo, address, etc.)
  sections/        — page builder section types (heroSection, featureGrid, etc.)
  portableText/    — blockContent definition and embedded PT types (codeBlock, figureImage, etc.)
  index.ts         — imports and re-exports everything
```

Create the folders now:



```bash
mkdir -p sanity/schemaTypes/documents sanity/schemaTypes/objects sanity/schemaTypes/sections sanity/schemaTypes/portableText
```

The separation makes it immediately clear what a file is without opening it, and keeps the AI from dumping everything into a flat list where documents and shared utility types become indistinguishable. The prompts below save files to the correct subdirectory. If the AI ignores the path instructions, move the files manually before continuing.



### Naming map: title, name, and SEO



Webflow uses different names for "the thing you see" depending on context, and conflating them is the single most common source of wrong-field bugs. Map them before generating any schema.



The key rule: CMS collections use `name` in `fieldData`, not `title`. Keep it as `name` in your Sanity schema. Renaming it `title` for aesthetic reasons breaks the import transform, a direct 1:1 mapping becomes a rename, and creates confusion when page singletons use `title` for a different concept.



For page singletons, document title and SEO title are different fields with different sources:



```typescript
// Page singleton — two separate concerns
{
  title: pageTitleBySlug['home'],       // from pages.json display name
  seo: {
    metaTitle: extractedFromHead.title, // from HTML <title> tag
    metaDescription: extractedFromHead.description,
  }
}
```

That distinction, page name is not SEO title, saves a round trip if you establish it before schema generation.



### Step 1: Generate schemas



Open your AI editor (Cursor or Claude Code) and run this prompt against the files you fetched in Chapter 2:



```text
@webflow/api/components.json @webflow/api/collections.json @webflow/export/[page-name].html Identify the distinct section patterns across these files. For each collection that has its own page or could appear standalone, write a Sanity document type schema in TypeScript using defineType and defineField. For collections that only ever appear inside another document, write them as inline object arrays instead. Use camelCase field names, type: 'blockContent' for rich text, type: 'reference' for links to other document types, and type: 'image' for image fields. Save each document type to /sanity/schemaTypes/documents/ and update index.ts to import and re-export them all.
```

Important registration rules:



- Every type passed to `defineType` must be exported from its file and added to the `schemaTypes` array in `index.ts`.

- Any custom object type used inside a Portable Text `of` array (such as `codeBlock`, `htmlEmbed`, or `figureImage`) must be defined with its own `defineType` call, exported, and registered in `index.ts`. Defining it inline inside `blockContent.ts` and referencing it by name is not sufficient. Studio will show "Unknown type" for any document containing that block type.


The AI reads your actual collection field names and relationships, not a generic example, so the output maps directly to your site.



### What to look for when reviewing the output



For each generated schema, check:



- Document vs. object — any collection the AI modeled as a document type should have its own page in your site. If it doesn't, it should be an inline array instead. A `post` document type is correct; a `faqItem` document type probably isn't.

- Slug field — the AI may add `source: 'title'` to slug fields. Leave it out for now — imported content sets `slug.current` explicitly from your Webflow export. You can add source back post-migration for editorial UX (see the slug two-phase note below). Also check: CMS types should use `source: 'name'`, not `source: 'title'`, since Webflow CMS items use `name` as the primary field.

- Field names on CMS types — confirm the AI used `name` (not `title`) as the primary label field for CMS collection schemas. If it generated `title`, rename it to `name` before continuing. This keeps the import transform a direct 1:1 mapping and avoids confusion with page singletons.

- Reference fields — confirm the `to` array lists the correct target types. The AI infers these from field names, which is usually right but worth checking.

- SEO fields — these are rarely in Webflow's API schema but you'll want them in Sanity. Add an `seo` object field to every document type that has a public URL if the AI didn't include one.

- File locations — confirm document types landed in `documents/`, not the root. Move any misplaced files before running Step 2.


Once you're satisfied with the individual schemas, move to Step 2.



### Step 2: Extract shared types



The first prompt generates schemas independently, so it inlines any repeated field groups, SEO fields, image and alt combinations, address blocks, into every schema that needs them. That's valid, but it creates maintenance overhead: change the SEO structure and you're editing five files instead of one. Run this follow-up prompt to extract repeated groups into named reusable types:



```text
@sanity/schemaTypes/documents/ Review all the schema files in this directory. Identify any field groups that appear in three or more schemas — for example, SEO fields (metaTitle, metaDescription, ogImage, noIndex), or any other repeated cluster of fields. For each repeated group: create a named object type in its own file saved to /sanity/schemaTypes/objects/ (e.g. seo.ts), replace the inlined fields in each schema with a single defineField({ name: '...', type: '...' }) reference to the new type, and update index.ts to import and register the new types. Output all modified and new files.
```

The output should be a small number of new shared type files in `objects/` and updated references in each document schema. Common shared types on most sites: `seo`, sometimes `openGraph`, sometimes an address or contact object.



After running this prompt, confirm Studio still compiles without errors before moving on — a mismatched type name between the new shared type and its reference in a schema is the most common mistake here.



A correctly generated post document type looks like this:



```typescript:sanity/schemaTypes/documents/post.ts
// sanity/schemaTypes/documents/post.ts
import { defineType, defineField } from 'sanity'

export const post = defineType({
  name: 'post',
  title: 'Post',
  type: 'document',
  fields: [
    defineField({ name: 'title', type: 'string' }),
    defineField({
      name: 'slug',
      type: 'slug',
      // No `source` — slug is set from Webflow export, not derived from title
    }),
    defineField({
      name: 'author',
      type: 'reference',
      to: [{ type: 'author' }]
    }),
    defineField({ name: 'body', type: 'blockContent' }),
    defineField({ name: 'seo', type: 'seo' }),
  ]
})
```

And a child collection correctly converted to an inline array looks like this:



```typescript
defineField({
  name: 'faqs',
  title: 'FAQs',
  type: 'array',
  of: [{
    type: 'object',
    fields: [
      defineField({ name: 'question', type: 'string' }),
      defineField({ name: 'answer', type: 'text' }),
    ]
  }]
})
```

Once you've reviewed and corrected the output, confirm all files are in the right subdirectory (`documents/` for document types, `objects/` for shared types) and that `index.ts` imports from those paths. Confirm Studio compiles without errors before moving on.



**Gotcha: type name alignment. **The `name` field in each schema definition is the `_type` string that every imported document must carry. If your schema file has `name: 'blog'` but your import script writes `_type: 'blogPost'`, Studio renders each document as "Unknown type" — very difficult to diagnose after thousands of documents exist. The import.js prompt in Chapter 5 has the AI read `name` values directly from your schema files rather than guessing, so this shouldn't come up if you follow it. If you do see "Unknown type" after import, compare `_type` in a few imported documents (via the API or `sanity groq '*[0]{_type}'`) against the `name` field in the corresponding schema file — they must match exactly. Don't rename types after import has run unless you're prepared to also migrate every existing document's `_type` field.



**Gotcha: blockContent object types need separate registration. **Any custom object type used inside a Portable Text array — `codeBlock`, `htmlEmbed`, `figureImage` — must be both defined as a named type and registered in the global `schemaTypes` array. Defining them inline inside `blockContent.ts` and referencing them by name in the `of` array isn't enough. The schema generation prompt above includes this requirement explicitly, so a correctly generated schema won't have this problem. If you see "Unknown type: codeBlock" (or similar) in Studio, check that the type is exported from its file and present in the `schemaTypes` array in `index.ts`.



**Slug source: two-phase approach. **During migration, leave `source` off slug fields entirely — import scripts set `slug.current` explicitly from `fieldData.slug`, and the Generate button never fires during import. After migration, add `source` back for editorial UX so editors get the Generate button on new documents. Use `source: 'name'` for CMS collection types and `source: 'title'` for page singletons — they use different primary fields. Migrated slugs stay untouched until an editor clicks Generate, so there's no risk of accidentally changing live URLs.



```typescript
// CMS collection type (e.g. blog, faqItem)
defineField({ name: 'slug', type: 'slug', options: { source: 'name', maxLength: 96 } })
```

```typescript
// Page singleton (e.g. page, homePage)
defineField({ name: 'slug', type: 'slug', options: { source: 'title', maxLength: 96 } })
```

Next you’ll design the page builder schema so editors can compose pages from section types.



---

## 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/map-collections-to-schemas.md",
  "feedback": "Description of the issue"
}
```

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

</AgentInstructions>
