Fix: Content and Data Cannot Be in Same Collection in Astro

Error message:
Content and data cannot be in same collection.
Content Collections 2025-01-25

What Causes This Error?

This error occurs when a collection contains both content files (Markdown/MDX with body content) and data files (JSON/YAML without body content). Each collection must be either type: 'content' or type: 'data', not both.

The Problem

src/content/blog/
├── post.md       # Content file (has body)
├── config.json   # Data file (no body)
└── meta.yaml     # Data file (no body)
// Trying to mix types in one collection
const blog = defineCollection({
  type: 'content',  // But folder has .json files too
  schema: z.object({ /* ... */ }),
});

The Fix

Separate Into Different Collections

src/content/
├── blog/           # Content collection
│   ├── post-1.md
│   └── post-2.md
└── blog-meta/      # Data collection
    ├── config.json
    └── authors.yaml
// src/content/config.ts
import { defineCollection, z } from 'astro:content';

// Content collection for blog posts
const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    date: z.date(),
  }),
});

// Data collection for metadata
const blogMeta = defineCollection({
  type: 'data',
  schema: z.object({
    siteName: z.string(),
    postsPerPage: z.number(),
  }),
});

export const collections = { blog, 'blog-meta': blogMeta };

Common Scenarios

Authors Data Separate from Posts

src/content/
├── posts/          # type: 'content'
│   ├── hello.md
│   └── world.md
└── authors/        # type: 'data'
    ├── john.json
    └── jane.json
const posts = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    author: z.string(),  // Reference to author ID
  }),
});

const authors = defineCollection({
  type: 'data',
  schema: z.object({
    name: z.string(),
    email: z.string(),
    bio: z.string(),
  }),
});

Settings/Config Data

src/content/
├── pages/          # type: 'content'
│   └── about.md
└── settings/       # type: 'data'
    └── navigation.json
const pages = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
  }),
});

const settings = defineCollection({
  type: 'data',
  schema: z.object({
    items: z.array(z.object({
      label: z.string(),
      url: z.string(),
    })),
  }),
});

Using Both Collections Together

---
import { getCollection, getEntry } from 'astro:content';

// Get content
const posts = await getCollection('posts');

// Get related data
const settings = await getEntry('settings', 'navigation');

// Combine them
const postsWithAuthors = await Promise.all(
  posts.map(async (post) => {
    const author = await getEntry('authors', post.data.author);
    return { ...post, author: author.data };
  })
);
---

If You Need Both in One Place

---
// Keep collections separate but use together
import { getCollection } from 'astro:content';

const blogPosts = await getCollection('blog');
const blogConfig = await getEntry('blog-settings', 'config');

const { postsPerPage } = blogConfig.data;
const paginatedPosts = blogPosts.slice(0, postsPerPage);
---

Collection Type Reference

TypeFile ExtensionsHas BodyExample
content.md, .mdxYesBlog posts, docs
data.json, .yaml, .tomlNoConfig, authors

Migration: Splitting Mixed Collection

# Before
src/content/blog/
├── post.md
└── settings.json

# After - move data files
mkdir -p src/content/blog-settings
mv src/content/blog/*.json src/content/blog-settings/

Quick Checklist

  • Each collection is either content or data
  • Markdown/MDX files → type: 'content'
  • JSON/YAML config → type: 'data'
  • Split mixed folders into separate collections
  • Reference between collections using IDs