Skip to content
qino

Collections

A flat folder of similarly shaped entries with one schema.

A collection is a flat directory of files that share one schema, such as blog posts or authors. It is the primitive for "many of the same thing".

qino/collections/posts.ts
import { z } from "zod";

import qino from "../";

export const postCollection = qino.defineCollection({
  directory: "/posts",
  extension: ".md",
  schema: z.object({
    title: z.string(),
    draft: z.boolean(),
    markdown: z.string(),
  }),
});

Entries and slugs

Every file in directory with the configured extension is an entry. The slug is the filename minus that extension, so posts/hello-world.md has the slug hello-world. Subfolders, hidden files, and other extensions are ignored. See Content folder.

Each entry is the validated schema output plus _meta:

src/app/posts/[slug]/page.tsx
const post = await postCollection.getEntry("hello-world");

post._meta.slug; // "hello-world"
post._meta.fileName; // "hello-world.md"
post._meta.filePath; // absolute path

Getters

GetterReads contentApplies viewReturns
getEntries(options?)all filesyesArray<Entry>
getEntry(slug, options?)one fileyesEntry
getAllSlugs()nonoArray<string> sorted

getEntries returns entries in discovery order unless the selected view sorts them. getAllSlugs is the cheap way to build routes, since it never parses or validates anything.

Filtering and sorting

Collections are the only primitive with filter and sort. Both live inside a view, never on the getter call:

qino/collections/posts.ts
views: (view) => ({
  default: view({
    filter: (post) => !post.draft,
    sort: (a, b) => a.title.localeCompare(b.title),
  }),
  all: view({}),
}),

Filtering runs after relation resolution and augmentation. Sorting runs after filtering, for getEntries only. getEntry throws when the selected view's filter excludes the entry. See Views.

Because getAllSlugs ignores views, it can list slugs that a filtering default view excludes. When the default view filters, derive route slugs from the viewed set instead:

src/app/posts/[slug]/page.tsx
const slugs = (await postCollection.getEntries()).map((post) => post._meta.slug);

When to reach for something else

  • One well-known file, such as a home page: use an item.
  • Nested, ordered content, such as docs: use a tree.