Why Craft CMS Deserves a Look

Craft CMS, developed by Pixel & Tonic, has become a serious contender in the CMS space. With an active community — its Discord server topped 5,000 members within its first year — and a client roster that includes Netflix, Craft is steadily gaining ground. For teams coming from WordPress, the admin interface feels familiar, which eases the transition. What sets Craft apart is its flexible field system, letting you shape content structure to fit each project rather than forcing content into predefined templates.

There’s no subscription lock-in either: a one-off license fee covers Pro and eCommerce features, and the plugin store continues to grow. Add in an actively maintained codebase that keeps pace with modern web development, and it’s a solid choice for everything from marketing sites to custom builds.

Setting Up a Local Environment

Craft ships with Craft Nitro, a tool that automates local environment setup. Nitro relies on Multipass, which runs an Ubuntu virtual machine behind the scenes — you never need to interact with Ubuntu directly. Start by downloading and running the Multipass installer for your operating system from its official site.

Installing Nitro on macOS and Linux

Open a terminal and run the Nitro installer script. Follow the on-screen prompts to create your virtual machine using the default presets, which meet Craft’s minimum requirements. Tuning is possible, but defaults work fine for most projects.

bash <(curl -sLS https://installer.getnitro.sh)

Installing Nitro on Windows 10 Pro

Windows setup requires Hyper-V, so this will not work on the Home edition. With Hyper-V enabled, follow these steps:

  1. Download nitro_windows_x86_64.zip from the latest release.
  2. Create a Nitro folder in your home directory if one doesn’t already exist, e.g. C:\Users\<username>\Nitro.
  3. Extract the zip and place nitro.exe into that folder.
  4. On first install, add Nitro to your global path from the command line:
setx path "%PATH%;%USERPROFILE%\Nitro"

Finally, launch cmd.exe with administrator rights and run Nitro, following the prompts to create your first machine. The default settings Nitro suggests are sufficient.

Creating a New Craft Project

With Nitro and your virtual machine ready, the next step is downloading Craft and mounting your project files so local edits reflect instantly in the Ubuntu VM. You’ll also set up a local test domain along the way.

  1. Create a folder for your project.
  2. Download Craft CMS from the latest release, either via Composer or as a zip.
  3. Extract the files into your project folder.
  4. Open a terminal and navigate there: cd /path/to/project.
  5. Run nitro add and follow the prompts. Defaults are generally fine.

If you run into “Not Readable” errors on macOS, Multipass needs full disk access. Enable it under System Preferences → Security & Privacy → Privacy → Full Disk Access by checking multipassd.

Configuring the Database

With your test domain and mounted files in place, open the project’s .env file in the root folder and update the database connection details as follows:

DB_USER="nitro"
    DB_PASSWORD="nitro"
    # 'nitro' is the default database
    DB_DATABASE="nitro"

To connect your preferred SQL client to the database, run nitro info and use the IP address shown under “IPV4” along with the username, password, and port you selected during setup.

Running the Installer

Everything is now in place to install Craft itself. Navigate to your test domain followed by /admin — e.g., testdomainyouset.test/admin — and Craft’s install screen should appear. Follow the on-screen instructions, and you’ll land in the admin panel once complete.

If you skipped Composer during installation, you may be asked for a security key. Generate one with a password manager like 1Password or LastPass — there’s no length limit. Open .env, find SECURITY_KEY="", and paste your key inside the quotes.

(Large preview)

You now have a working Craft CMS local environment, ready for anything from migrating a WordPress blog to building a custom eCommerce store. When you’re done developing, shut the server down with nitro stop.

A few useful Nitro commands during development:

  • nitro start — starts the development server
  • nitro stop — stops the development server
  • nitro context — shows info about installed environments
  • nitro info — details about the current environment, including PHP version

Building the Content Model

With Craft installed, the next step is to define the structure of our cat blog. Craft uses a graphical interface for content modeling, so there is no need to write configuration code. For this project, we need two content types: a channel for individual cat posts, which we’ll call "Cats," and a single page for the homepage.

Creating the Sections

Sections are the containers for your content. A "Channel" is suited for repeated content like blog posts, while a "Single" is for one-off pages such as a homepage. To set up the Cats channel:

  1. Go to Settings in the left-hand menu.
  2. Click Sections and then New Section.
  3. Set the Name to Cats and choose the Section Type of Channel.
  4. In the Entry URI Format field, enter /cats/{slug}.
  5. For the Template, enter cat.
  6. Save the section.

This configuration tells Craft to generate URLs like ourtestdomain.test/cats/fluffy and use the cat.twig template for rendering.

Next, create the homepage as a single page:

  1. Within Sections, click New Section.
  2. Set the Name to Homepage and choose Single as the Section Type.
  3. Tick the Homepage checkbox.
  4. In the Template field, enter index.
  5. Save the section.

Defining Custom Fields

Craft starts with a blank slate, allowing us to build our own field structure. First, we need to create the individual fields for our cat posts. Navigate to SettingsFields and click New Field for each of the following:

  • Cat's Name: Choose the Plain Text field type.
  • Cat's Description: Use the Plain Text field type and set a Field Limit of 2000 characters.

For the cat photos, we must first tell Craft where to store uploaded files. Head to SettingsAssetsNew Volume.

  • Set the Name to Cat's Photo.
  • Enable Assets in this volume have public URLs.
  • Set Base URL to @web/uploads/.
  • Set File System Path to @webroot/uploads/.
  • Save the volume.

Now we can create the photo field itself. Click New Field again, name it Cat's Photo, and select the Assets field type.

(Large preview)

Linking Fields to Sections

With our fields and sections created, we need to associate them. This is done by dragging and dropping.

  1. Go to SettingsSections.
  2. Click Edit Section Type on the "Cats" section.
  3. Select the Fields tab.
  4. Drag the "Cats" fields we created into the content pane.
  5. Save the section.
(Large preview)

Tip: You can disable the default title field by toggling off the "Show the Title Field" option, in case you prefer to use the cat's name as the entry title.

Creating and Listing Cat Entries

With the model in place, we can start generating content. In the admin panel, click EntriesNew Entry, select the "Cats" section from the dropdown, and fill in the details for your feline post.

To display these entries on the site, open your Craft project folder in a code editor and create the index.twig file in the templates directory. If a default index file exists, clear its contents.

<!DOCTYPE html>
<html xmlns="https://www.w3.org/1999/xhtml" lang="en-US">
<head>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
</head>

<body>
{# Create an entry query with the 'section' and 'limit' parameters #}
{% set myEntryQuery = craft.entries()
    .section('cats')
    .limit(10) %}

{# Fetch the entries #}
{% set entries = myEntryQuery.all() %}

{# Display the entries #}

<div class="container">
  <div class="row">
    {% for entry in entries %}
      <div class="col">
          <article class="card">

              {% set catImage = entry.catsPhoto.one() %}
              {% if catImage %}
                <img src="{{ rel.url }}" class="card-img-top" alt="...">
              {% endif %}

              <div class="card-body">
                <h1><a href="{{ entry.url }}">{{ entry.title }}</a></h1>
                <h2>{{ entry.catsName }}</h2>
                <p>{{ entry.catsDescription }}</p>
                <a class="btn btn-primary" href="{{ entry.url }}">View {{ entry.catsName }}</a>
              </div>

          </article>
      </div>
    {% endfor %}
  </div>
</div>

</body>
</html>

This template queries the "Cats" section, limits the output to 10 entries, and loops through them. Because images in Craft are stored as arrays, an inner loop is required to iterate over and display each image.

After saving, visit your development URL (ensure the server is running with nitro start). Your index page should now show the cat posts. Clicking through to an individual post will currently result in a 404 error until we create the cat.twig template.

Building the Individual Post Page

To fix the 404, create a new file named cat.twig in the templates folder. Inside the entry context, there is no need to query for the entry again, as Craft provides it directly.

<html>
  <body>
    {% block content %}
  
      {% set catImage = entry.catsPhoto.one() %}
      {% if catImage %}
        <img src="{{ rel.url }}" class="card-img-top" alt="...">
      {% endif %}
  
      {{ entry.title }}
      {{ entry.catsName }}
      {{ entry.catsDescription }}
      
    {% endblock %}
  </body>
</html>

After saving, refresh the site and navigate to one of your cat entries. The individual post page should now be accessible.

Going Headless with GraphQL

Craft CMS's Pro package enables headless usage by adding user accounts and a GraphQL API. The Pro version has a free trial, allowing for thorough testing before purchase.

Starting the Pro Trial

From the admin panel, go to the Plugin Store and select Upgrade. Click Trial on the "Pro" section and follow the process. Once activated, a new GraphQL option will appear in the sidebar.

After your content models are set up, Craft automatically generates the GraphQL schemas for them—there is no need to define types manually.

Creating the API Endpoint

Craft doesn't expose GraphQL at a default URL. We need to add a route rule to make the endpoint available. Open the routes.php file in your project's config directory and add the rule shown below. This makes the endpoint accessible at https://yourprojecturl.test/api.

return [
    'api' => 'graphql/api',
    // ...
];

After saving, you can test the endpoint to ensure it's active.

curl -H "Content-Type: application/graphql" -d '{ping}' https://yourprojecturl.test/api

A successful response will be "pong".

Enabling Content for the API

To control which sections are available via GraphQL, go to the GraphQL section in the admin sidebar, click Public Schema, enable the "Cats" section, and save. This allows public access to that content without any authentication. For more restrictive access, you can create private schemas, though that is beyond the scope here.

(Large preview)

Testing with GraphiQL

To verify the API works, use the built-in GraphiQL client found in the GraphQL dropdown menu.

  1. Open GraphiQL.
  2. Change the schema from "Full Schema" to "Public Schema".
  3. Enter a GraphQL query in the left-hand pane to fetch your cats.
  4. Click the Play button to run the query.
query ($section: [String], $orderBy: String) {
  entries(section: $section, orderBy: $orderBy) {
    title
    slug
    id
  }
}

This confirms your headless endpoint is functioning and returns the desired cat data.