Skip to content
qino

Type-safe content pipeline

Markdown in.
TypeScript out.

Point qino at your content folder and hand it a schema. You - and your agents - get typed, validated content. Nothing to deploy!

What developers say

I thought using Markdown for content would be easy. Then I saw how much wiring and formatting it takes.... qino just handles all of that, amazing tool!

Jeremy Marcillaud

Jeremy Marcillaud

Fullstack Engineer, Rainbow

Why I built qino, and why now?

At my last company, we ran several websites on Markdown content: blogs, event listings, and landing pages. Markdown is great until you need non-developers to publish updates. Then we developers become the bottleneck.

We were looking for CMS tools that plug into our existing sites, instead of forcing our code into their framework.

We first tried Decap CMS. It's free and open-source, but we had to maintain a config.yml that grew with every content type, and still fetch and validate the Markdown ourselves, with no type safety.

Then we gave TinaCMS a try. It handled fetching and typing, but made us query our own files through GraphQL, and its git workflow confused developers and editors alike.

Two tools, same result: more time spent on the CMS than on the content. I just wanted it to be simpler.

I still believed in Markdown as a content source, and the timing made the case even stronger. AI agents now work alongside you in the codebase, and they're only as useful as the content they can read. A hosted CMS keeps that content on someone else's servers, behind APIs that can rate limit you, go down, or charge for access.

The best way to give agents your content is to keep it local, in your repo, right next to your code. Owning your content layer has never mattered more.

So I built qino, and it comes with:

  1. Type-safe content. Define your content with Zod schemas. Types hold from end to end.
  2. No config file to maintain. qino generates it from your schemas and settings.
  3. No framework lock-in. qino plugs into the site you already have.
  4. Any content shape. Collections, standalone pages, or full documentation sites.
  5. A cloud editor, soon. Non-developers edit content without touching code.
  6. A CLI for agents. qino check validates every edit against your schemas.

You can use @qino/cms as a standalone content pipeline for your files, or pair it with the cloud UI for a full CMS experience once it ships.

Give qino a try! Read the docs or let your agent set it up.

Charly Martin

Charly Martin

Creator of qino

qino is dead simple to set up

qino works with what you already have: Markdown files in your repo, a TypeScript config, and your framework of choice.

01 — Markdown on disk

.
├── content
│   ├── posts
│   │   ├── hello-world.md
│   │   └── typed-content.md
│   └── authors
│       ├── camille-laurent.json
│       └── jonas-weiss.json
├── qino
│   ├── collections
│   │   ├── posts.ts
│   │   └── authors.ts
│   └── index.ts
└── src
    └── ...

02 — qino

const postsCollection = qino.defineCollection({
  directory: "/posts",
  extension: ".md",
  schema: PostSchema,
  relations: {
    author: authorsCollection,
  },
  views: (view) => ({
    default: view({ resolveRelations: 1 }),
  }),
})

03 — In your app

await postsCollection.getEntries();

// [
//   {
//     title: "Hello world",
//     author: { name: "Camille Laurent" },
//     _meta: { slug: "hello-world" },
//   },
//   {
//     title: "Typed content",
//     author: { name: "Jonas Weiss" },
//     _meta: { slug: "typed-content" },
//   },
// ]

Supported file types

Reads the files
you already have

Markdown

MDX

JSON

Three ways to model content

Collection

Unordered list of of entries

A flat folder of similarly shaped entries such as posts/*.md. Great for blog posts, events, team members, etc. Views filter, sort and paginate.

Read more about collections
Tree

Hierarchy and order, first-class

Nested folders with anchor files and _order.json. Great for docs, guides or a knowlegde base. Build a sidebar without validating a single body.

Read more about trees
Item

One file with its own role

A single file, such as pages/home.md, with a unique schema. Great for pages or navigation.

Read more about items

Relations

qino makes your content a relational database.

Declare a relation and a frontmatter string becomes a typed foreign key. Any primitive can point at any other; the view decides how many hops resolve, up to 6.

qino/posts.ts
const postCollection = qino.defineCollection({
  directory: "/posts",
  extension: ".md",
  schema: z.object({
    title: z.string(),
    author: z.string(),
    categories: z.array(z.string()),
    tags: z.array(z.string()),
  }),
  relations: {
    author: authorCollection,
    "categories[*]": categoryCollection,
    "tags[*]": tagCollection,
  },
  views: (view) => ({
    default: view({ resolveRelations: 1 }),
  }),
});
posts.author
authors
posts.categories[*]
categories
posts.tags[*]
tags

A broken relation should fail the build, not the page.

When a view resolves a relation, qino checks that the linked entry exists. If it doesn't, the build fails, so broken links never ship.

One command between your content and a broken build.

The CLI runs before your framework does, so content errors surface in the terminal instead of at request time.

qino build

Generate types

Checks every definition, validates every content file against its schema, and writes qino/_generated/types.d.ts. Add it to your prebuild script in package.json and every deploy is gated on valid content.

qino check

Validate content only

Fast feedback in CI or on save. Relation fields are treated as ordinary strings and are not followed.

qino lint

Validate definitions only

Checks that content and media folders exist, that no two primitives own overlapping paths, and that relations target the same instance.

Let your agent set it up.

Install the qino skill, then ask your coding agent to set up qino. It models the content you already have into collections, trees, and items, wires qino build into your build, and keeps going until it passes.