# Write in Markdown

> YAML frontmatter format for hand-written posts uploaded or published via the CLI.

HTML version: https://blogizi.com/docs/markdown-frontmatter
Markdown version: https://blogizi.com/docs/markdown-frontmatter.md

Write a `.md` file locally with YAML frontmatter, then save it as a draft with `blogizi draft` or publish live with `blogizi publish`. AI coding agents can write the file for you — the CLI only pushes it to Blogizi.

## When to use this

- You already write in your editor (VS Code, Obsidian, etc.)
- You version posts in git alongside your repo
- An AI coding agent drafts the markdown and you push via CLI
- Prefer Obsidian? See the [Obsidian plugin](/docs/obsidian) guide

## Frontmatter template

Every post file starts with YAML frontmatter between `---` delimiters. The CLI parses these fields and sends them to Blogizi when you publish.

```markdown
---
title: "How we built our auth flow"
description: "A walkthrough of the auth decisions we made — sessions, OAuth, and what we'd do differently."
keyword: "indie app authentication"
slug: "how-we-built-our-auth-flow"
status: "draft"
date: "2026-07-21"
readingTime: 0
wordCount: 0
---

Your markdown content starts here. Use `##` and `###` for sections — the page already shows the title, so don't open with a heading that just repeats it.
```

## Field reference

| Field | Required | Notes |
| --- | --- | --- |
| `title` | Yes | Page title and H1 on your blog |
| `description` | Yes | Meta description for SEO and social cards (~150–160 chars) |
| `keyword` | Yes | Primary SEO keyword for the post |
| `slug` | Yes | URL path — lowercase letters, numbers, and hyphens only |
| `status` | No | `draft` or `published` — `blogizi draft` always sets `draft`; `blogizi publish` always sets `published`; `blogizi update` only changes status when set in the file |
| `date` | No | `YYYY-MM-DD` — informational / CLI display; does **not** rewrite `publishedAt` on already-published posts |
| `scheduledAt` | No | Future publish time. ISO with `Z`/offset is absolute; naive `YYYY-MM-DDTHH:mm` uses the **project timezone** (Settings → General). Future → keeps `draft` and publishes at that time; omit to leave schedule unchanged; empty/null clears it. CLI: `--scheduled-at` / `--unschedule`. `blogizi publish` always publishes immediately and cancels any schedule |
| `readingTime` | No | Leave as `0` — recalculated from content on publish |
| `wordCount` | No | Leave as `0` — recalculated from content on publish |
| `ctaTitle` | No | Per-post CTA title — overrides the project branding CTA when `ctaTitle`, `ctaButtonLabel`, and `ctaButtonUrl` are all set |
| `ctaSubtitle` | No | Per-post CTA subtitle (optional even when CTA is shown) |
| `ctaButtonLabel` | No | Per-post CTA button label |
| `ctaButtonUrl` | No | Per-post CTA button URL |
| `sidebarCtaTitle` | No | Per-post sidebar CTA title (sticky card beside the post; a bar under the header on mobile) — overrides the project sidebar CTA when `sidebarCtaTitle`, `sidebarCtaButtonLabel`, and `sidebarCtaButtonUrl` are all set, even if the project's sidebar CTA is off |
| `sidebarCtaText` | No | Per-post sidebar CTA paragraph |
| `sidebarCtaBullets` | No | YAML list of bullet points (max 6); start one with an emoji to use it as the icon |
| `sidebarCtaButtonLabel` | No | Per-post sidebar CTA button label |
| `sidebarCtaButtonUrl` | No | Per-post sidebar CTA button URL |
| `coverImageUrl` | No | Absolute HTTPS cover URL (hero + listing cards) |
| `coverImage` | No | Same as `coverImageUrl`, or a local relative path for the CLI to upload |

## Markdown body

Everything after the closing `---` is the article body. Standard Markdown works: headings, lists, links, fenced code blocks with language tags, blockquotes, and images.

```markdown
![Architecture diagram](https://media.blogizi.com/content/<postId>/<timestamp>.png)
```

Upload via the dashboard editor, [`blogizi upload`](/docs/cli-publishing), [`POST /api/upload`](/docs/api-upload-image), or MCP [`upload_post_image`](/docs/mcp-upload-post-image), then put the returned URL in the body. The CLI also auto-uploads relative local paths like `![Alt](./images/diagram.png)` when you `draft` / `update` / `publish`.

> **Title is shown separately:** Your blog page renders the title from frontmatter. Avoid opening the body with a heading that simply repeats the title — start with an introduction paragraph or a meaningful section heading instead.

## Upload or publish from the CLI

Authenticate once and select your project (see [CLI publishing](/docs/cli-publishing)), then push any local file:

```sh
blogizi draft path/to/my-post.md
blogizi update path/to/my-post.md
blogizi publish path/to/my-post.md
```

Both `draft` and `publish` create a post; `update` revises an existing post by slug. They recalculate word count and reading time. `blogizi draft` saves a draft; `blogizi publish` makes it live; `blogizi update` keeps visibility unless the file sets `status`.

## Where to store files

Put files anywhere in your repo — `blogizi draft`, `blogizi update`, and `blogizi publish` take an explicit path. A `blogizi-posts/` (or `posts/`) folder in the project root is a convenient convention.

## Not in frontmatter

These fields live on the post record but aren't part of the YAML block — set them in the dashboard after publishing if you need them:

- **Tags** — filtering on your blog homepage and related posts
- **CTA style** — card / banner / minimal / split / accent is project-level (Settings → CTA); only the CTA copy fields above are per-post

Cover images use frontmatter `coverImage` / `coverImageUrl`, the CLI upload flow, or the dashboard. Without a cover, the auto-generated OG image is used on listing cards.

> **Warning:** `blogizi draft` and `blogizi publish` each create a **new** post. If a post with the same slug already exists, the API returns a conflict error. To revise an existing article, run `blogizi update` on the same file (or edit it in the dashboard).
