Skip to content

Getting started with PirateCMS

PirateCMS is a markdown-first CMS for marketing pages, managed by your AI agent. Every page is one file that humans read like a document and machines read like data. This guide gets you from install to a shipped page, then covers the config basics.

One command. The wizard asks where (a new folder, or . for the one you’re in), which framework (Astro or Next.js), and which package manager, then builds the ship and verifies every step:

Terminal window
npx -y @piratecms/create my-site # or run it with '.' inside your folder
| | |
)_) )_) )_)
)___))___))___)\
)____)____)_____)\\
_____|____|____|____\\\__
---------\ P I R A T E /---------
^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^ ^^^^ ^^^
^^^^ ^^^
a markdown-first CMS, crewed by yer AI agent
AARRR! Let's get ye a ship. Three questions, then I do the work.
▐█▙ Ship's inspection
✓ Node 24.3.0
✓ git aboard
▐█▙ The shipyard
✓ template built from the latest PirateCMS → my-site/
standalone: no ties to the source repo
✓ hull: Astro — Next.js adapter left ashore
▐█▙ Loading provisions
✓ provisions loaded (npm install)
▐█▙ The crew
✓ crew aboard in .claude/skills — /page-new, /post-new,
/seo-audit, /ship
▐█▙ The ship's log
✓ fresh git history with a first commit — this ship is yers alone
Yer ship is seaworthy.
? Set sail now? (↑/↓ + enter)
❯ Aye — launch the dev server
Not yet

Choose Aye and your site opens at localhost:4321 (Astro) or :3000 (Next) — the wizard shows you the command it ran so you know it next time. The dev server is your preview: edit any file, the browser updates instantly.

Everything is already aboard — nothing to install. One prerequisite: start Claude Code inside your site folder (cd my-site && claude) — the skills load from the repo’s .claude/skills/. Then it’s one command:

/page-new yourproduct.com # or: /page-new — a landing page for my time-tracking app

The agent reads your site (or your description), asks nine short positioning questions — pre-filled, you mostly confirm — and writes your homepage as real, schema-validated sections:

{/* src/content/pages/index.mdx — generated, draft-first */}
---
title: Track time without thinking about it
description: Tracklee watches git and your calendar so invoices write themselves.
draft: true
sections:
- type: hero
variant: with-proof
headline: Track time without thinking about it
primaryCTA: { text: Start tracking free, href: /signup }
- type: features-grid
features:
- { title: "", description: "" }
---

Three things just happened:

  • Your positioning was saved to src/data/positioning.yaml — no future page, post, or SEO pass re-asks; the voice stays consistent site-wide.
  • The page was born a draft: visible at http://localhost:4321 in dev, invisible to production builds until you say “ship it”.
  • Every prop was validated against the section schemas — a generated page can’t be an invalid page.

From here on, changes are conversations: “make the headline more direct”, “swap testimonials for a how-it-works”, “add a plan”.

One command in Claude Code — the publishing counterpart to the install wizard:

/ship

The first run sets up your harbor. The agent checks what’s connected — GitHub repo, Vercel or Netlify — and walks you through whatever’s missing (logins included). One-time.

Every run after that just ships. The agent takes what you’ve been working on — including pages still marked draft: true (it asks, flips them, and publishes) — opens a PR, and hands you a preview URL. Look at it, say “make it live”, and production updates:

you: ship it
agent: AARRR — yer new pricing page is on the preview: https://my-site-git-ship….vercel.app
say "make it live" and I'll merge it
you: make it live
agent: She's live, captain: https://my-site.vercel.app/pricing

Underneath it’s plain git + static hosting: every push builds and deploys on your host, every PR gets a preview URL — you never build anything yourself.

Prefer doing it by hand?
Terminal window
gh repo create my-site --private --source . --push
# then either:
npx vercel link && npx vercel git connect # Vercel
npx netlify-cli init # Netlify
# vercel.json + netlify.toml ship in the repo; `pirate use` keeps them matched

Ask the agent (/page-new — a features page for my time tracker) or create a file yourself. File path = URL, always:

src/content/pages/
└── features.mdx → /features
---
title: Features
description: What the app does.
layout: composed # composed (default) · prose · docs
sections:
- type: features-grid
columns: 3
features:
- title: Track time
description: One keystroke to start a timer.
- title: Bill clients
description: Turn tracked hours into invoices.
---

A page is MDX with a sections: list in frontmatter — each item is a type: plus that section’s props, pure data. Markdown below the frontmatter renders after the sections.

The navbar and footer are site chrome — rendered on every page from pirate.config.yaml, never written into page content. Putting the new page in the menu is one entry in that same file (nav.links: [{ label: Features, href: /features }]) — or just say “add Features to the nav”.

layout: picks the shell that wraps your content:

LayoutWhat it isUse for
composedSections + markdown, full-widthMarketing pages
prosePure markdown, branded typographyAbout, legal, posts
docsProse + a sidebarDocumentation

Two conventions worth knowing: draft: true hides a page from production builds — dev still shows it; /ship (or npx pirate publish) flips it. And Pascal-cased JSX section tags in the body (<Hero … />) still work as a hand-authoring escape hatch — but sections: frontmatter is the canonical form, and the only one agents write.

The single source of truth is one file, pirate.config.yaml: site name and brand, navigation, footer, SEO defaults, colors, fonts, framework. Change the nav there (or say “add Pricing to the menu”) and every page updates — pages never carry their own chrome.

Content itself lives next to it, one file per page:

my-site/
├── pirate.config.yaml settings: brand · nav · footer · SEO · framework
└── src/
└── content/pages/*.mdx the content — one file = one page (data lives inline)

Structured data — pricing plans, testimonials, feature lists — lives inline in the page, as section props in the frontmatter:

---
title: Pricing
sections:
- type: pricing
plans:
- name: Starter
price: $9
features: [10 projects, Email support]
- name: Pro
price: $29
features: [Unlimited projects, Priority support]
---

Need the same list on several pages (testimonials, logos, plans)? Move it to src/data/<name>.yaml and reference it as plans: { $data: plans } — the engine inlines the file at build and validates it with the section’s own schema. Inline stays the default; $data exists to stop copying one list across pages.

Branding is that same file: brand name, nav, footer, SEO defaults, fonts, and a brand color ramp (50–900) every section uses. Colors mirror into Tailwind’s @theme block in src/styles/global.css — or just ask the agent: “change our brand color to emerald” edits both.

No lock-in, ever: everything is plain files you own — MDX, YAML, git. Change frameworks later with npx pirate use next (or use astro) — it switches config, dependencies, and deploy settings in one command. Leave entirely and your content travels: it’s markdown in your repo.