Using content › TypeScript
Docs/Using content/TypeScript

TypeScript

What you'll learn

How document types come from your config with no build step, how to name them in your code, and how to generate a types file for apps that can't import the config.

Before this page: Local API.

Inferred from the config

Declare the config with defineConfig. It keeps the literal types (slugs, field names, select options), so the Local API is typed without any generation step:

const cms = await getEasyCMS(config) // useEasyCMS() in Nuxt
const { docs } = await cms.find('posts')
 
docs[0].title // string (required: true)
docs[0].excerpt // string | null | undefined (optional)
docs[0].tags // ('vue' | 'nuxt')[] (select with hasMany)
docs[0].author // number | User (a relationship: an id, or the document when populated)
docs[0].status // 'draft' | 'published' (drafts: true)
 
await cms.find('pots') // error: unknown collection
await cms.create('posts', { titel: 'Hi' }) // error: unknown field, and `title` is missing

What each field becomes:

FieldType
text, textarea, email, slug, datestring (date as ISO 8601)
numbernumber
booleanboolean
selectthe options as a union; an array of them with hasMany
relationship, uploadthe id, or the related document when populated; arrays with hasMany
groupan object of its fields
arrayan array of rows with id and the row's fields
blocksan array of rows, one type per block, told apart by blockType
richTextthe Tiptap document
jsonunknown

Fields without required: true are optional: null or missing. hidden fields are not in the type.

Blocks

Rows of a blocks field are a union; check blockType to narrow:

for (const section of post.sections ?? []) {
  if (section.blockType === 'quote') section.author // only quote rows have author
}

Fields that plugins add

Plugins tell the types what they add, so their fields are typed like yours:

plugins: [
  seoPlugin({ collections: ['posts'] }),
  nestedDocsPlugin({ collections: ['pages'] }),
  redirectsPlugin({ collections: ['pages'] }),
]
 
post.meta.title // string | null | undefined
page.breadcrumbs[0]?.url // string | null | undefined
await findByPath(cms, 'pages', '/about') // Page | null
await cms.find('redirects') // the collection the plugin adds

Your own plugins can do the same with definePlugin.

Naming the types

import type { CollectionDocument, CreateInput, GlobalDocument, UpdateInput } from '@easy-cms/core'
import type config from './easy-cms.config'
 
export type Post = CollectionDocument<typeof config, 'posts'>
export type NewPost = CreateInput<typeof config, 'posts'>
export type PostChanges = UpdateInput<typeof config, 'posts'>
export type Site = GlobalDocument<typeof config, 'site'>

import type keeps the config (and its database driver) out of browser bundles, so components can use Post too.

In Nuxt

Server routes return typed data, and useFetch passes the type on to the page:

// server/api/posts.get.ts
export default defineEventHandler(async () => (await useEasyCMS()).find('posts'))
<script setup lang="ts">
const { data } = await useFetch('/api/posts') // data.value.docs is Post[]
</script>

Generated types for other apps

For a frontend that can't import your config, such as a Vite app in another repository calling the REST API:

npm
npx easy-cms generate:types              # writes easy-cms-types.ts
npx easy-cms generate:types --out ../web/src/cms.ts
pnpm
pnpm exec easy-cms generate:types              # writes easy-cms-types.ts
pnpm exec easy-cms generate:types --out ../web/src/cms.ts
yarn
yarn easy-cms generate:types              # writes easy-cms-types.ts
yarn easy-cms generate:types --out ../web/src/cms.ts
bun
bunx easy-cms generate:types              # writes easy-cms-types.ts
bunx easy-cms generate:types --out ../web/src/cms.ts

The file has no imports: one interface per collection and global, plus Collections and Globals maps.

import type { Post } from './easy-cms-types'
 
const res = await fetch('/api/cms/posts?limit=10')
const { docs }: { docs: Post[] } = await res.json()

Run it again after changing the config, or in CI to check the file is up to date:

npm
npx easy-cms generate:types && git diff --exit-code easy-cms-types.ts
pnpm
pnpm exec easy-cms generate:types && git diff --exit-code easy-cms-types.ts
yarn
yarn easy-cms generate:types && git diff --exit-code easy-cms-types.ts
bun
bunx easy-cms generate:types && git diff --exit-code easy-cms-types.ts

Next steps

  • Local API: every function and its options.
  • REST API: the same operations over HTTP.