---
title: "Academy Article"
canonical_url: "https://academy.laioutr.com/en/artikel/declare-content-collections-in-code"
locale: en
meta:
  "og:title": "Academy Article"
---

[Go to home](https://academy.laioutr.com/en)

- [For developers](https://academy.laioutr.com/en/bereiche/for-developers)

- [Developer start](https://academy.laioutr.com/en/leitfaeden/developer-start)

- [Declare content collections in code](https://academy.laioutr.com/en/artikel/declare-content-collections-in-code)

# Declare content collections in code

The project defines which content types the Laioutr CMS manages in the collections option of @laioutr-app/cms. After the next deployment, the types appear in Cockpit under Content, and their entries answer Orchestr queries like any other backend.

## Before you start

<dl>

<dt>Plan</dt>
<dd>Any plan with the Laioutr CMS integration enabled</dd>

<dt>Role</dt>
<dd>Developer with access to the code and the Cockpit integrations</dd>

<dt>Duration</dt>
<dd>15 min</dd></dl>

### Prerequisites

- Laioutr CMS enabled under Integrations
- Local development environment or access to laioutrrc.json

Last reviewed on October 15, 2026

The Laioutr CMS stores **entries** of entity types the storefront already knows, such as `BlogPost`, `Recipe` or `Author`. Editors create, translate and publish them in Cockpit under **Content**. The storefront reads them via `@laioutr-app/cms` from the content delivery API.

For every declared type, the app registers query handlers, component resolvers, link handlers and page indexes. The CMS defines no tokens of its own: it answers the existing tokens of the type, so a Section built for canonical `BlogPost` data also works with CMS entries.

Types come from three sources: canonical types such as `BlogPost`, `Product` or `Category`, types an installed app offers, and custom types from a token file. Visitors see published entries, content preview shows drafts. Reads are not cached, so a publish is visible on the next request.

## Step by step

1. 1 ### Step 1: Enable Laioutr CMS

   Open **Integrations** in Cockpit, enable **Laioutr CMS** and redeploy the storefront. This sets up the bucket, the CDN key and the `@laioutr-app/cms` app.
2. 2 ### Step 2: Declare collections

   Add `collections: ['BlogPost', 'Recipe']` under `'@laioutr-app/cms'` in `nuxt.config.ts`. Hosted projects keep the same list in the app config inside `laioutrrc.json`; maintain each type in one place only.
3. 3 ### Step 3: Define custom types

   For a custom type, create a token file with `defineEntityComponentToken` and a Zod schema and add it as `{ entityType: 'Author', tokens: './cms/author.tokens' }`. Hosted projects publish the token file as a package.
4. 4 ### Step 4: Enable queries

   Use the `queries` option to set how the CMS answers, for example `'acme/author/by-slug': 'by-slug'`. Canonical types come with default queries, `false` switches single ones off.
5. 5 ### Step 5: Deploy and check entries

   Deploy the **main** environment, because the list is read at build time. Then create entries under **Content** and check drafts by adding `?preview_token=…` to the storefront URL.

## Check the result

The type appears in Cockpit under **Content**, the build log contains no `[@laioutr-app/cms] … note(s) on the content collections` warning, and a published entry is visible on its page with the next request.

## Frequently asked questions

Why is a type missing in Cockpit?

Cockpit reads the list from the storefront of the **main** environment. A type appears once that deployment declares it. Types with entries that are no longer declared are listed as **Not declared**.

How is a field made language-independent?

With `.meta({ cms: { localized: false } })` on the field. By default, text, rich text, media and links are maintained per language, numbers, booleans, enums and dates are not.

Why is an unpublished entry still in the sitemap?

The sitemap of the SEO Essentials app is rebuilt periodically, so an entry can stay there for up to about a day. The page itself is gone immediately.

## Related

- [Manage content in Content Collections](https://academy.laioutr.com/en/artikel/manage-content-in-content-collections)6 min

  Content Collections are the place for structured content such as blog posts, recipes or authors in Cockpit. Under Content › Collections, entries are created, maintained per language, linked and published without a deployment.
- [Orchestr: entities, queries, actions and resolvers](https://academy.laioutr.com/en/artikel/orchestr-entities-queries-actions-and-resolvers)8 min

  Orchestr is Laioutr's data layer and runs server-side in the Nuxt server. Queries return entity IDs, component resolvers load the data for them, actions perform changes, and tokens form the contract between frontend, Orchestr and apps.
- [Delivery API, Management API and Assets API](https://academy.laioutr.com/en/artikel/delivery-management-and-assets-api)8 min

  The Delivery API serves published Laioutr content to frontends and other channels, the Management API creates and changes content and assets programmatically. The assets part runs through the storefront's /api/public/media/\* routes and is authorised with a project-bound restricted key.
- [Start and check a deployment](https://academy.laioutr.com/en/artikel/start-and-check-a-deployment)5 min

  A deployment rebuilds the storefront of an environment and puts changes to apps, versions and configuration live. It is started in Cockpit under Deployments with Deploy; the status and build log show whether it worked.

## Learn it in a lesson

- [Content collections in code](https://academy.laioutr.com/en/lektionen/developer-content-collections-in-code)10 min

## [Developer start](https://academy.laioutr.com/en/leitfaeden/developer-start)

Search this guide

1. [Set up a local development environment](https://academy.laioutr.com/en/artikel/set-up-a-local-development-environment)15 min
2. [The Laioutr CLI: commands and authentication](https://academy.laioutr.com/en/artikel/laioutr-cli-commands-and-authentication)6 min
3. [Build and release a first custom app](https://academy.laioutr.com/en/artikel/build-and-release-a-first-custom-app)20 min
4. [Define custom Sections and Blocks for Studio](https://academy.laioutr.com/en/artikel/define-custom-sections-and-blocks-for-studio)15 min
5. [Orchestr: entities, queries, actions and resolvers](https://academy.laioutr.com/en/artikel/orchestr-entities-queries-actions-and-resolvers)8 min
6. [Declare content collections in code](https://academy.laioutr.com/en/artikel/declare-content-collections-in-code)15 min
7. [Performance and technical SEO in code](https://academy.laioutr.com/en/artikel/performance-and-technical-seo-in-code)10 min

## Structured Data

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@id": "https://academy.laioutr.com/#website",
      "@type": "WebSite",
      "inLanguage": "en",
      "name": "Default",
      "url": "https://academy.laioutr.com/",
      "translationOfWork": {
        "@id": "https://academy.laioutr.com/#website"
      }
    },
    {
      "@id": "https://academy.laioutr.com/en/artikel/declare-content-collections-in-code#webpage",
      "name": "Academy Article",
      "url": "https://academy.laioutr.com/en/artikel/declare-content-collections-in-code",
      "@type": [
        "WebPage",
        "FAQPage"
      ],
      "breadcrumb": {
        "@id": "https://academy.laioutr.com/en/artikel/declare-content-collections-in-code#breadcrumb"
      },
      "isPartOf": {
        "@id": "https://academy.laioutr.com/#website"
      },
      "mainEntity": [
        {
          "@id": "https://academy.laioutr.com/en/artikel/declare-content-collections-in-code#/schema/question/1"
        },
        {
          "@id": "https://academy.laioutr.com/en/artikel/declare-content-collections-in-code#/schema/question/2"
        },
        {
          "@id": "https://academy.laioutr.com/en/artikel/declare-content-collections-in-code#/schema/question/3"
        }
      ],
      "potentialAction": [
        {
          "@type": "ReadAction",
          "target": [
            "https://academy.laioutr.com/en/artikel/declare-content-collections-in-code"
          ]
        }
      ]
    },
    {
      "@id": "https://academy.laioutr.com/en/artikel/declare-content-collections-in-code#breadcrumb",
      "@type": "BreadcrumbList",
      "itemListElement": [
        {
          "@type": "ListItem",
          "name": "Home",
          "item": "https://academy.laioutr.com/en",
          "position": 1
        },
        {
          "@type": "ListItem",
          "name": "For developers",
          "item": "https://academy.laioutr.com/en/bereiche/for-developers",
          "position": 2
        },
        {
          "@type": "ListItem",
          "name": "Developer start",
          "item": "https://academy.laioutr.com/en/leitfaeden/developer-start",
          "position": 3
        },
        {
          "@type": "ListItem",
          "name": "Declare content collections in code",
          "item": "https://academy.laioutr.com/en/artikel/declare-content-collections-in-code",
          "position": 4
        }
      ]
    },
    {
      "@id": "https://academy.laioutr.com/en/artikel/declare-content-collections-in-code#/schema/question/1",
      "@type": "Question",
      "inLanguage": "en",
      "name": "Why is a type missing in Cockpit?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "<p>Cockpit reads the list from the storefront of the <strong>main</strong> environment. A type appears once that deployment declares it. Types with entries that are no longer declared are listed as <strong>Not declared</strong>.</p>"
      }
    },
    {
      "@id": "https://academy.laioutr.com/en/artikel/declare-content-collections-in-code#/schema/question/2",
      "@type": "Question",
      "inLanguage": "en",
      "name": "How is a field made language-independent?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "<p>With <code>.meta({ cms: { localized: false } })</code> on the field. By default, text, rich text, media and links are maintained per language, numbers, booleans, enums and dates are not.</p>"
      }
    },
    {
      "@id": "https://academy.laioutr.com/en/artikel/declare-content-collections-in-code#/schema/question/3",
      "@type": "Question",
      "inLanguage": "en",
      "name": "Why is an unpublished entry still in the sitemap?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "<p>The sitemap of the SEO Essentials app is rebuilt periodically, so an entry can stay there for up to about a day. The page itself is gone immediately.</p>"
      }
    }
  ]
}
```
