Right, let’s talk about the beating heart of your site: Content Types. This isn’t just some abstract CMS concept; it’s the single most important organizational decision you’ll make. Think of a Content Type as a blueprint. You wouldn’t let a builder just throw rooms onto a house willy-nilly, right? You have a blueprint for a Kitchen: it has a sink, counters, an oven hookup. A blueprint for a Bedroom: it has a closet and windows. That’s what we’re doing here. We’re defining the blueprints for the different kinds of content our site serves.

A “Blog Post” blueprint is different from a “Team Member” blueprint, which is different from a “Product” blueprint. Each has its own set of fields, its own rules, and crucially, its own template for presentation. This structure is what stops your site from becoming a digital hoarder’s basement full of vaguely page-shaped objects.

Defining Your Blueprint: The Content Type

First, we define the blueprint itself. We tell Craft what fields this type of content has. This is done in the CMS admin, but since we’re developers and we like to keep our configuration in code (for version control and sanity), we use a Project Config approach. Let’s define a simple blogPost type.

We do this in config/craft.php by adding it to the $contentTypes array. Watch closely.

// config/craft.php
return [
    'contentTypes' => [
        'blogPost' => [
            'name' => 'Blog Post',
            'fields' => [
                'bodyContent' => 'craft\fields\PlainText',
                'postSummary' => 'craft\fields\PlainText',
                'heroImage' => 'craft\fields\Assets',
                'postCategory' => 'craft\fields\Categories',
            ],
        ],
        // ... other content types
    ],
    // ... other config
];

We’ve just told Craft that any entry using the blogPost type must have these four fields. No more, no less. This is your first line of defense against content chaos. Why is this so powerful? Because now, in your templates, you can call entry.heroImage with absolute confidence. It will always exist. No more {% if entry.someWeirdImageField %} guards all over your templates. You’ve enforced consistency.

The Template: Where Blueprints Become Reality

Now, a blueprint is useless without a builder. In our case, the builder is the Twig template. Craft follows a specific, logical pattern to find the template for an entry. It’s not magic, it’s just good convention.

The default lookup path is: templates/<sectionHandle>/<typeHandle>.twig

So, for a blog post entry that belongs to a Section called blog (its handle) and is of our blogPost type, Craft will look for: templates/blog/blogPost.twig.

Let’s build that template. Notice how we can use the fields we defined with certainty.

{# templates/blog/blogPost.twig #}
{% extends "_layout.twig" %}

{% block content %}
    <article class="prose max-w-none">
        {% if entry.heroImage|length %}
            {% set image = entry.heroImage.one() %}
            <img src="{{ image.url }}" alt="{{ image.title }}" class="w-full mb-6 rounded-lg">
        {% endif %}

        <h1>{{ entry.title }}</h1>

        <div class="text-lg text-gray-600 mb-8">
            {{ entry.postSummary }}
        </div>

        <div class="content">
            {{ entry.bodyContent }}
        </div>
    </article>
{% endblock %}

Clean, predictable, and safe. This is the payoff for setting up your Content Types correctly.

The URL Dilemma: Sections vs. Types

Here’s a common point of confusion that makes people curse at their screens: Content Types themselves do not have URLs. I’ll say it again. The URL structure is controlled by the Section the entry belongs to, not its Type.

A Section is a bucket for entries. It has settings for its URL format (e.g., blog/{slug}). All entries within that Section follow that rule. You can have a blog Section that contains entries of multiple types—blogPost, blogNews, blogReview—and they will all have URLs like blog/my-post-slug.

The Type dictates the fields and the template; the Section dictates the URL and the high-level grouping. This separation is actually brilliant once you get it. It means you can have a “News” Section and a “Blog” Section that both use the article Content Type, so they share the same field blueprint and template, but live at different URLs (/news/company-merger, /blog/my-great-post).

The Singleton: The Special Snowflake

Sometimes you have content that is truly one-of-a-kind. A “Homepage” entry. An “About Us” entry. These aren’t items in a list; they are unique pages. For this, we use the singleton Content Type. It’s a blueprint that can only be used once. You define it just like any other type, but when you create an entry, you assign it to a special Single Section. The URL for a singleton is defined by the section itself (e.g., the “Homepage” section just has a root URL /).

// config/craft.php
'contentTypes' => [
    'homepage' => [
        'name' => 'Homepage',
        'fields' => [
            'heroTitle' => 'craft\fields\PlainText',
            'heroButtonText' => 'craft\fields\PlainText',
            'featuredProducts' => 'craft\fields\Entries',
        ],
    ],
],

The gotcha? You have to remember that a Singleton is still just an entry. You retrieve it by its Section handle, not by its Type. You’ll almost always use craft.entries.section('homepage').one() to fetch it in your template. Trying to query by its type homepage would be missing the point entirely. It’s a one-of-a-kind entry in a one-of-a-kind section. That’s the whole deal.