Search documentation

Search documentation

Getting Started

Create your first project

Build a small content flow from an editor to your frontend.

Build one Article from editor to browser#

This walkthrough creates a small but realistic content flow. At the end, an editor will publish an Article in Contoprix and a Next.js route will read it by slug.

Keep the first model intentionally small. A working title and slug is more useful than a large schema that nobody has delivered to the frontend yet.

What you will create#

First project flow
Website + English language

Article content type

One Article entry: hello-contoprix

Published delivery API response

/blog/hello-contoprix in your Next.js app

1. Choose the website and language#

In Contoprix Admin, switch to the tenant and website that this frontend will use. A website is important because delivery credentials, languages, content entries, and media are all scoped to it.

Make sure the website has an enabled default language. For this guide, use:

SettingValue
Language nameEnglish
Language codeen
DefaultYes

If the website already has English as its default language, keep it. Do not create a second English entry just for this tutorial.

2. Create an Article content type#

Open Content Types and create a type with these values:

SettingValueWhy
NameArticleClear label for editors
CodearticleStable value used in SDK calls
KindCollectionAllows many articles per website and language
DraftsEnabledLets editors save work before publishing
VersioningEnabledKeeps an edit history
LocalizationEnable only if this content will be translatedLets entries be created outside the website's default language

The code is part of the integration contract. Your application will call client.content.getBySlug("article", ...), so keep the code stable after frontend work starts.

Add these fields in this order:

Field nameField codeField typeRequiredExample
TitletitleTextYesHello, Contoprix
SlugslugSlugYeshello-contoprix
SummarysummaryText AreaNoOur first published article.
BodybodyRich TextNoArticle body copy

Important

Use the exact field codes shown in your frontend mapper. The API puts them inside entry.data, so a field code change is a frontend change too.

3. Create the first entry#

Open Content Entries, choose the Article type, the website, and English. Create an entry with this content:

Example Article
Title: Hello, Contoprix
Slug: hello-contoprix
Summary: Our first published article.
Body: This is the first Article delivered to our website.

Save the entry, review it, and publish it. The slug is normalized for delivery, so Hello-Contoprix is stored and looked up as hello-contoprix.

If publishing is blocked, check the required fields first. If a workflow is configured for the entry, it may need approval before a user without workflow-bypass permission can publish it.

4. Create a delivery credential#

In the website's API-client or credential settings, create a delivery credential that has delivery read access for this website. Copy the delivery key once and place it in a local environment file.

.env.local
CONTOPRIX_BASE_URL=https://cms.example.com
CONTOPRIX_DELIVERY_KEY=replace-with-your-delivery-key

Use the CMS API origin for CONTOPRIX_BASE_URL; do not add /api. Keep this file out of version control and do not use a NEXT_PUBLIC_ prefix for the key.

5. Create one shared client#

Create a small server-side client module:

src/lib/contoprix/client.ts
import { ContoprixClient } from "@contoprix/client";

export const client = new ContoprixClient({
  baseUrl: process.env.CONTOPRIX_BASE_URL!,
  auth: {
    type: "deliveryKey",
    deliveryKey: process.env.CONTOPRIX_DELIVERY_KEY!,
  },
  languageCode: "en",
});

The delivery key identifies the website. You do not manually pass a tenant ID or website ID in every SDK request.

6. Fetch the Article by slug#

The SDK response has metadata on the entry and your CMS fields under data. Map it before rendering:

src/lib/contoprix/articles.ts
import { client } from "./client";

type ArticleData = {
  title?: string;
  summary?: string;
  body?: unknown;
};

export async function getArticle(slug: string) {
  const entry = await client.content.getBySlug("article", slug, {
    languageCode: "en",
  });

  const data = entry.data as ArticleData;

  if (!data.title) {
    throw new Error(`Article ${entry.id} is missing its required title.`);
  }

  return {
    id: entry.id,
    slug: entry.slug ?? slug,
    title: data.title,
    summary: data.summary ?? "",
    body: data.body,
    publishedAt: entry.publishedAt,
  };
}

The cast belongs in this data module, not inside each React component. Later, replace this small local type with generated types if your team uses the CLI.

7. Render it in a Next.js route#

src/app/blog/[slug]/page.tsx
import { getArticle } from "@/lib/contoprix/articles";

export default async function ArticlePage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const article = await getArticle(slug);

  return (
    <article>
      <h1>{article.title}</h1>
      {article.summary ? <p>{article.summary}</p> : null}
    </article>
  );
}

Open /blog/hello-contoprix. If the page renders the title and summary, the entire first delivery flow is working.

Troubleshoot the first request#

What you seeLikely causeWhat to do
401 or 403Missing, incorrect, or wrong-website delivery keyCheck the environment values and delivery-read access.
404Wrong content-type code, slug, language, or unpublished entryConfirm article, hello-contoprix, en, and the entry's published state.
entry.data is missing a fieldField is optional, has a different code, or was not filled inCheck the Content Type field code and entry values.
The browser shows a secretThe client module was imported into browser codeKeep the SDK client and delivery key in server-only modules.

Continue building safely#

Add only one feature at a time:

  1. Add a hero image using a media field.
  2. Add an author content type and link it with a relation.
  3. Add another language and create a separate Article entry for that language.
  4. Add a list route with client.content.list().
  5. Configure preview or Visual Builder only after published delivery works.

Read Content Types before changing the Article schema, and Content Entries for draft, version, and publication behavior.