From content files
to typed queries.
Turn Markdown and structured data into validated collections, connected queries, and rendered articles. Bring your own framework.
From source to data
Start with a content file, define its schema, and query the fields your application needs.
---
title: Hello, world
draft: false
---
Content starts here.export const collections = {
posts: defineCollection({
source: "./content/posts",
schema: schema.object({
title: schema.string(),
draft: schema.boolean(),
}),
}),
};const posts = await content.select("title", "draft").from("posts")
.all();[
{
"title": "Hello, world",
"draft": false
}
]Flexible type-safe query syntax
Compare fluent chains and object-based queries. The same controls update both examples and their shared result.
| title | draft |
|---|---|
| Hello, world | false |
| What's next | true |
| Working with content | false |
| Writing with Markdown | false |
const posts = await content
.select("title")
.from("posts");const posts = await content.query.posts.findMany({
columns: { title: true },
});[
{
"title": "Hello, world"
},
{
"title": "What's next"
},
{
"title": "Working with content"
},
{
"title": "Writing with Markdown"
}
]Typed queries
Select the fields you need. Catch misspelled fields before running your code.
| title | draft |
|---|---|
| Hello, world | false |
| What's next | true |
| Working with content | false |
| Writing with Markdown | false |
const posts = await content
.select("title")
.from("posts");[
{
"title": "Hello, world"
},
{
"title": "What's next"
},
{
"title": "Working with content"
},
{
"title": "Writing with Markdown"
}
]Relationships
Connect posts to their authors. Select related fields in one query, with authored references checked during preparation.
posts
- Hello, worldauthorId:
ada - What's nextauthorId:
grace - Working with contentauthorId:
ada
authors
- Ada Lovelaceid:
ada - Grace Hopperid:
grace
relationships: {
author: {
collection: "authors",
type: "one",
from: "authorId",
to: "id",
referenceSide: "from",
},
}const posts = await content.select("title").from("posts")
.where((fields) => eq(fields.authorId, "ada"))
.all();[
{
"title": "Hello, world"
},
{
"title": "Working with content"
}
]Flexible sources
Markdown, JSON, YAML, or TOML. Different formats, the same query API. Use Astro-compatible or custom loaders for other sources.
---
title: Hello, world
draft: false
authorId: ada
---
Content starts here.source: {
type: "markdown",
directory: "./content/posts",
}{
"id": "hello-world",
"title": "Hello, world",
"draft": false,
"authorId": "ada"
}Typed collections
Validate content with Zod or another Standard Schema library. Query results follow your schema.
---
title: Hello, world
draft: false
authorId: ada
---defineCollection({
source: "./content/posts",
schema: schema.object({
title: schema.string(),
draft: schema.boolean(),
authorId: schema.string(),
}),
});- File
content/posts/hello-world.md
Computed fields
Use built-in reading time, word count, and excerpt helpers, or write custom computed fields. Query them alongside authored metadata.
Hello, world
3 min read
import { excerpt, readingTime, wordCount } from "contentgraph/computed";
computed: {
readingMinutes: readingTime(),
wordCount: wordCount(),
excerpt: excerpt({ maxLength: 160 }),
}Query the computed field
const post = await content.select("title", "readingMinutes").from("posts")
.first();Markdown rendering
Get HTML, syntax highlighting, and headings for your table of contents. Article output stays separate from metadata queries.
## Hello, world
Content starts here.
```ts
const greeting = "Hello, world";
```Hello, world
Content starts here.
const greeting = "Hello, world";
Render API
const article = await content.render(
"examples",
"hello-world",
);Vite, Next.js, Angular, or standalone
Keep content fresh during development, or build portable artifacts with the CLI. Your framework owns routes and rendering.
- Ready to edit
- Edit the title
- Save the file
- Page data refreshed
---
title: Hello, world
draft: false
authorId: ada
---Content starts here.
Make your first collection.
Start with a Markdown file and a schema.