Alright, let’s get our hands dirty with Decap CMS. You might still know it as Netlify CMS, but they had to change the name—turns you can’t trademark “Netlify” if you’re not, you know, Netlify. It’s a classic open-source story: build something amazing that becomes synonymous with a platform, and then have to politely cough and say, “Actually, it’s a separate thing.” But the name is about all that changed. The concept remains gloriously, stupidly simple and effective.

Here’s the elevator pitch: Decap CMS gives you a React-based, single-page app admin panel to manage your content. The killer feature? It doesn’t talk to a database. Instead, it reads from and writes directly to your Git repository. You get a CMS experience without the headache of maintaining a server, a database, or any of the other plumbing that eventually springs a leak. It’s content management for people who would rather git commit than pray a WordPress update doesn’t break their site.

The Core Configuration: config.yml

Everything starts with a file called config.yml in your static/admin directory. Hugo serves this from your static folder, so it ends up at yoursite.com/admin/config.yml. This file is the brain, the boss, the entire rulebook for your Decap CMS setup. Let’s break down the non-negotiable parts.

backend:
  name: git-gateway
  branch: main # Your default branch: main, master, etc.

# This is crucial for Hugo. It tells Decap where you keep your content files.
local_backend: true # Allows local development with `npx decap-server`

media_folder: "static/img/uploads" # Where uploaded files are stored
public_folder: "/img/uploads" # The public path Hugo will use in HTML

collections:
  - name: "blog"
    label: "Blog Posts"
    folder: "content/blog"
    create: true
    slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
    editor:
      preview: false # Hugo's live reload is better for previews
    fields:
      - {label: "Title", name: "title", widget: "string"}
      - {label: "Publish Date", name: "date", widget: "datetime"}
      - {label: "Draft", name: "draft", widget: "boolean", default: true}
      - {label: "Body", name: "body", widget: "markdown"}

Why git-gateway? It handles the OAuth authentication with GitHub, GitLab, or Bitbucket for you, so you don’t have to set up your own auth server. It’s Netlify’s magic, but it works elsewhere too. The local_backend setting is your best friend for development. Run npx decap-server in your terminal, and you can use the CMS locally without pushing to a remote repo every five seconds.

The Authentication Gotcha

This is the part where the designers said, “Security is important!” and then designed a system that makes your first setup feel like you’re defusing a bomb. Decap CMS doesn’t have a username/password. It uses the authentication of your Git provider. This is great for security, but the setup is… finicky.

You must register Decap CMS as an OAuth application in your GitHub/GitLab settings. You need to provide that callback URL. For a site deployed on Netlify, it’s https://your-site.netlify.app/.netlify/identity/callback. If you’re not on Netlify, you might need to use the proxy setting in your config.yml to route authentication through a specific endpoint. It’s the biggest hurdle you’ll face. Read the identity documentation on Netlify’s site twice, have a coffee, and then do it. It’s worth it.

Content Modelling: Beyond Basic Blog Posts

Don’t just think in terms of blog posts. You can model anything. Let’s say you have a “Team Members” section. You’d use a collection for that. But if you have fixed pages like an “About” page, you’d use a file.

# A 'file' collection for a singleton, like a config page
- name: "pages"
  label: "Pages"
  files:
    - name: "about"
      label: "About Page"
      file: "content/about.md"
      fields:
        - {label: "Title", name: "title", widget: "string"}
        - {label: "Body", name: "body", widget: "markdown"}
        - label: "Team Members"
          name: "team"
          widget: "list"
          fields:
            - {label: "Name", name: "name", widget: "string"}
            - {label: "Role", name: "role", "widget": "string"}
            - {label: "Photo", name: "photo", widget: "image"}

See that? A list widget inside a file collection. This is how you give your content editors structured data without them having to write YAML by hand. The CMS generates the perfect front matter for Hugo to consume.

The Editorial Workflow (A.K.A. The Best Part)

Enable the editorial workflow. Just do it. In your config.yml, set publish_mode: editorial_workflow. This changes the game from “one big commit to main” to a proper Git-based content review process.

backend:
  name: git-gateway
  branch: main
  publish_mode: editorial_workflow # This right here. Turn it on.

It creates draft, in review, and ready states for your content. Behind the scenes, it’s creating pull requests for your entries. A content editor can write a post, submit it for review, and you, the developer, get a PR to review their changes. You can check the front matter, the markdown, everything, before merging it into your main branch and deploying. It’s version control for content, and it’s what makes this more than just a fancy web form.