Querying
How a page reads documents, and the calls that do it.
Pages read documents through cms.query. The key is the model type: models/blog.ts is cms.query.blog. Fetching & routes is how that call becomes a URL.
The examples below use a model whose file is content/blog/{slug}.mdx.
import { defineModel, field } from "@backmatter/next";
export default defineModel({
type: "blog",
path: "content/blog/{slug}.mdx",
route: "/blog/{slug}",
fields: {
title: field.text({ required: true }),
featured: field.boolean(),
authors: field.relation("people"),
},
});
One document
get loads one file. The argument is the slug. Import cms from backmatter in any file.
import { cms } from "backmatter";
const post = await cms.query.blog.get(slug);
if (!post) notFound();
post.frontmatter.title;
content/blog/hello.mdx is get("hello"). get returns null when that file is missing.
A singleton is one file, so get takes no slug. cms.query.site.get() reads it. The slug inside that file is index when the path omits {slug}.
Frontmatter is typed from the model. title: field.text({ required: true }) makes post.frontmatter.title a string.
A list
findMany lists every document of that type.
import { cms } from "backmatter";
const posts = await cms.query.blog.findMany({
where: { featured: true },
include: { authors: true },
});
posts[0]?.authors?.frontmatter.name;
Call it with no options to list them all.
where
where keeps documents where a field equals a value. It matches slug and scalar fields: text, number, and boolean.
await cms.query.blog.findMany({ where: { slug: "hello" } });
await cms.query.blog.findMany({ where: { featured: true } });
include
A relation field stores the other document's slug. authors: "mads" points at content/people/mads.mdx.
include loads that document, one level deep. The slug stays on frontmatter. The loaded document sits next to it, as post.authors. A missing file is null. The read does not throw.
const posts = await cms.query.blog.findMany({ include: { authors: true } });
posts[0]?.frontmatter.authors; // "mads"
posts[0]?.authors; // the people document, or null
What the types follow
Renaming a field in models/blog.ts is a type error wherever you use cms.query.blog. Adding models/faq.ts makes cms.query.faq exist. You do not register the query anywhere else.
cms.query trusts the file. Run backmatter validate in CI for files in the repo. Publish checks documents saved in the CMS. A content file fails that check when a required field is missing.