Cloudsteading
Product image still needed. This listing has source documentation, but no reviewed screenshot yet.

vibecms

Self-hosted Markdown blog publishing with media and scoped agent access

vibecms is a self-hosted Ghost/WordPress alternative built on Cloudflare (D1, Images, R2, Workers). Paid services required. Inspect the source and license in the linked repository.

Source & license

Upstream license: AGPL-3.0

License TL;DR

You can use and change it, even commercially. If people use your modified version over a network, offer them its corresponding source under the AGPL. Sharing copies has source-sharing duties too. Sharing source code is different from sharing users’ content.

Explain AGPL v3 in plain English →

Summary of the main license. Separate packages and assets can have different terms.

Inspect repository ↗Read this project’s actual license ↗

Repository owner

@moinulmoin

See the upstream repository for the original creator and contributors.

Maintain this project? Maintainer verification →

Cloudflare hosting

Paid services required

A conservative small self-hosted installation uses Workers Paid from $5 USD/account/month, covering dynamic rendering and arbitrary-recipient Email Service when enabled. D1/R2 overages, enabled email sending and image transformations beyond 5,000 unique transformations/month are additional. SELF_HOSTED=true makes Polar optional; hosted public Analytics Engine configuration is not the core self-host topology.

Hosting requirements
  • Use the root self-host configuration, create your own D1/R2 resources and deploy the separate public renderer sharing those resources.
  • Provide BETTER_AUTH_SECRET and TOKEN_PEPPER. Email verification/sending needs current Email Service entitlement; no blanket free-runtime claim is made.
  • Source and configuration review establishes a deployment path and conditional costs; this candidate was not executed or load-tested.
Check current pricing ↗
Sources checked 01/10/2026

Repository snapshot: efe7ae7. Hosting eligibility reflects the deployment documentation and listed assumptions.

  • ghost ↗

    Database migration SQL lives in `packages/db/drizzle/` and is shared by both Workers. Local API development, Astro rendering, migrations, and seed commands share the root

  • wordpress ↗

    Database migration SQL lives in `packages/db/drizzle/` and is shared by both Workers. Local API development, Astro rendering, migrations, and seed commands share the root

  • workers ↗

    y // from wrangler.public.jsonc and shares D1/R2 with this Worker. "$schema": "node_modules/wrangler/config-schema.json", "name": "vibecms", "main": "apps/api/src/index.ts", "compatibility_date": "2026-07-13", "compatibility_flags": ["nodejs_compat"], "upload_source_maps": true, "workers_dev": true, "preview_urls": false, "observability": { "enabled": true, "logs": { "enabled": true, "invocation_logs": true }, "traces": { "enabled": true, "

  • d1 ↗

    "head_sampling_rate": 0.1 } }, "version_metadata": { "binding": "CF_VERSION_METADATA" }, "d1_databases": [ { "binding": "DB", "database_name": "vibecms", "database_id": "00000000-0000-0000-0000-000000000000", "migrations_dir": "packages/db/drizzle" } ], "r2_buckets": [ { "binding": "ASSETS_BUCKET", "bucket_name": "vibecms-media" } ], "send_email": [{ "name": "EMAIL" }], "assets": { "directory": "apps/dashboard/dis

  • r2 ↗

    abase_id": "00000000-0000-0000-0000-000000000000", "migrations_dir": "packages/db/drizzle" } ], "r2_buckets": [ { "binding": "ASSETS_BUCKET", "bucket_name": "vibecms-media" } ], "send_email": [{ "name": "EMAIL" }], "assets": { "directory": "apps/dashboard/dist", "binding": "ASSETS", "not_found_handling": "single-page-application", "run_worker_first": ["/api/*", "/mcp", "/mcp/*", "/webhooks/*", "/media-assets/*", "/internal/*"] }, "vars": { "APP_

  • images ↗

    "binding": "ASSETS_BUCKET", "bucket_name": "vibecms-media" } ], "images": { "binding": "IMAGES" }, "cache": { "enabled": true }, "services": [ { "binding": "API", "service": "vibecms

  • paid ↗

    { // Self-hosted API + dashboard Worker. Public blog rendering is deployed separately // from wrangler.public.jsonc and shares D1/R2 with this Worker. "$schema": "node_modules/wrangler/config-schema.json", "name": "vibecms", "main": "apps/api/src/index.ts", "compatibility_date": "2026-07-13", "compatibility_flags": ["nodejs_compat"], "upload_source_maps": true, "workers_dev": true, "preview_urls": false, "observability": { "enabled": true, "logs": { "enabled": true, "invocation_logs": true }, "traces": { "enabled": true, "head_sampling_rate": 0.1 } }, "version_metadata": { "binding": "CF_VERSION_METADATA" }, "d1_databases": [ { "binding": "DB", "database_name": "vibecms", "database_id": "000

  • paid ↗

    The Workers Paid plan includes Workers, Pages Functions, Workers KV, Hyperdrive, and Durable Objects usage for a minimum charge of $5 USD per month for an account. The plan includes increased initial usage allotments, with clear charges for usage that exceeds the base plan. There are no additional charges for data transfer (egress) or throughput (bandwidth).

  • paid ↗

    | Rows read | 5 million / day | First 25 billion / month included + $0.001 / million rows |

  • paid ↗

    | Storage | 10 GB-month / month |

  • paid ↗

    | **Outbound emails (Email Sending)** | Not available | 3,000 included per month, then $0.35 per 1,000 emails |

  • paid ↗

    | Class A Operations | 1 million requests / month |

  • paid ↗

    | Class B Operations | 10 million requests / month |

  • paid ↗

    | Rows written | 100,000 / day | First 50 million / month included + $1.00 / million rows |

  • paid ↗

    | Images Transformed | First 5,000 unique transformations included + $0.50 / 1,000 unique transformations / month |

  • AGPL-3.0 ↗

    GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you

  • architecture ↗

    { // Self-hosted Astro public-blog Worker. It shares D1/R2 with the API Worker and // forwards newsletter subscriptions through a service binding. "$schema": "node_modules/wrangler/config-schema.json", "name": "vibecms-public", "main": "@astrojs/cloudflare/entrypoints/server", "compatibility_date": "2026-07-13", "compatibility_flags": ["nodejs_compat"], "workers_dev": true, "preview_urls": false, "observability": { "enabled": true, "logs": { "enabled": true, "invocation_logs": true }, "traces": { "enabled": true, "head_sampling_rate": 0.1 } }, "version_metadata": { "binding": "CF_VERSION_METADATA" }, "d1_databases": [ { "binding": "DB", "database_name": "vibecms", "database_id": "00000000-0000-0000-0000-000000000000", "migrations_dir": "packages/db/drizzle" } ], "r2_buckets": [ { "binding": "ASSETS_BUCKET", "bucket_name": "vibecms-media" } ], "images":

  • architecture ↗

    { // Self-hosted API + dashboard Worker. Public blog rendering is deployed separately // from wrangler.public.jsonc and shares D1/R2 with this Worker. "$schema": "node_modules/wrangler/config-schema.json", "name": "vibecms", "main": "apps/api/src/index.ts", "compatibility_date": "2026-07-13", "compatibility_flags": ["nodejs_compat"], "upload_source_maps": true, "workers_dev": true, "preview_urls": false, "observability": { "enabled": true, "logs": { "enabled": true, "invocation_logs": true }, "traces": { "enabled": true, "head_sampling_rate": 0.1 } }, "version_metadata": { "binding": "CF_VERSION_METADATA" }, "d1_databases": [ { "binding": "DB", "database_name": "vibecms", "database_id": "00000000-0000-0000-0000-000000000000", "migrations_dir": "packages/db/drizzle" } ], "r2_buckets": [ { "binding": "ASSETS_BUCKET", "bucket_name": "vibecms-media" } ], "send_email": [{ "name": "EMAIL" }], "assets": { "directory": "apps/dashboard/dist", "binding": "ASSETS", "not_found_handling": "single-page-application", "run_worker_first": ["/api/*", "/mcp", "/mcp/*", "/webhooks/*", "/media-assets/*", "/internal/*"] }, "vars": { "APP_ENV": "production", "APP_URL": "https://vibecms.example.workers.dev", "BETTER_AUTH_URL": "https://vibecms.example.workers.dev", "PUBLIC_BLOG_DOMAIN": "vibecms-public.example.workers.dev", "SELF_HOSTED": "true", "EMAIL_FROM": "vibecms <hey@example.com>", "POLAR_SERVER": "production" } }

  • architecture ↗

    s - MCP endpoint for trusted agents - Polar billing for hosted vibecms Cloud - `SELF_HOSTED=true` mode without Polar ## Connect an MCP client vibecms exposes standards-based MCP over Streamable HTTP. In the dashboard, open **Connect**, create a scoped token, and copy it once. Claude Code is the primary example: ```sh claude mcp add --transport http vibecms https://your-vibecms-domain.com/mcp \ --header "Authorization: Bearer vc_..." ``` Any compatible MCP client uses the same endpoint and bearer credential: ```json { "mcpServers": { "vibecms": { "type": "http", "url": "https://your-vibecms-domain.com/mcp", "headers": { "Authorization": "Bearer vc_..." } } } } ``` Install the client-independent safety and writing skill

  • architecture ↗

    y // from wrangler.public.jsonc and shares D1/R2 with this Worker. "$schema": "node_modules/wrangler/config-schema.json", "name": "vibecms", "main": "apps/api/src/index.ts", "compatibility_date": "2026-07-13", "compatibility_flags": ["nodejs_compat"], "upload_source_maps": true, "workers_dev": true, "preview_urls": false, "observability": { "enabled": true, "logs": { "enabled": true, "invocation_logs": true }, "traces": { "enabled": true, "

  • architecture ↗

    "head_sampling_rate": 0.1 } }, "version_metadata": { "binding": "CF_VERSION_METADATA" }, "d1_databases": [ { "binding": "DB", "database_name": "vibecms", "database_id": "00000000-0000-0000-0000-000000000000", "migrations_dir": "packages/db/drizzle" } ], "r2_buckets": [ { "binding": "ASSETS_BUCKET", "bucket_name": "vibecms-media" } ], "send_email": [{ "name": "EMAIL" }], "assets": { "directory": "apps/dashboard/dis

  • architecture ↗

    abase_id": "00000000-0000-0000-0000-000000000000", "migrations_dir": "packages/db/drizzle" } ], "r2_buckets": [ { "binding": "ASSETS_BUCKET", "bucket_name": "vibecms-media" } ], "send_email": [{ "name": "EMAIL" }], "assets": { "directory": "apps/dashboard/dist", "binding": "ASSETS", "not_found_handling": "single-page-application", "run_worker_first": ["/api/*", "/mcp", "/mcp/*", "/webhooks/*", "/media-assets/*", "/internal/*"] }, "vars": { "APP_

  • architecture ↗

    "binding": "ASSETS_BUCKET", "bucket_name": "vibecms-media" } ], "images": { "binding": "IMAGES" }, "cache": { "enabled": true }, "services": [ { "binding": "API", "service": "vibecms

What it can replace

Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.

Ghost logoGhost ↗

Markdown blog content, media, editorial versions and publishing APIs; no WordPress plugin ecosystem, hosted Ghost suite or complete theme/migration parity.

See supporting source ↗
WordPress logoWordPress ↗

Markdown blog content, media, editorial versions and publishing APIs; no WordPress plugin ecosystem, hosted Ghost suite or complete theme/migration parity.

See supporting source ↗
external SaaS target
varies
→ D1 + Images + R2
external SaaS target
varies
→ D1 + Images + R2

How it works

The shape of vibecms on Cloudflare, and how it stacks up against the rented tools it replaces.

Architecture

Diagram of deployment declarations at the reviewed commit. Each app has its own entrypoint; declared resources do not prove runtime calls. Follow file and line sources below.

View upstream source ↗
Public interface
Configured entry points7
vibecms
wrangler.jsonc
vibecms-api-dev
apps/api/wrangler.jsonc
vibecms-public-dev
apps/public/wrangler.jsonc
vibecms-public
wrangler.public.jsonc
vibecms-api-test
apps/api/wrangler.test.jsonc
vibecms-public-test
apps/public/wrangler.test.jsonc
vibecms-db-test
packages/db/wrangler.test.jsonc
↓
App
vibecms
entry
Cloudflare Workers
Entrypoint: apps/api/src/index.ts
vibecms-api-dev
entry
Cloudflare Workers
Entrypoint: src/index.tsConfigured cron (UTC): 17 2 * * *Configured cron (UTC): */15 * * * *
vibecms-public-dev
entry
Cloudflare Workers
Entrypoint: @astrojs/cloudflare/entrypoints/server
vibecms-public
entry
Cloudflare Workers
Entrypoint: @astrojs/cloudflare/entrypoints/server
vibecms-api-test
entry
Cloudflare Workers
vibecms-public-test
entry
Cloudflare Workers
vibecms-db-test
entry
Cloudflare Workers
↓

Configuration and workflow sources

Reviewed commit efe7ae7133f3. Files were read as data; upstream applications and CI jobs were not executed.

Deployment configuration · 7 files
wrangler.jsonc ↗

Cloudflare Workers · compatibility 2026-07-13

vibecms · default

Entrypoint: apps/api/src/index.ts

Static assets: apps/dashboard/dist · single-page-application · Worker first: ["/api/*","/mcp","/mcp/*","/webhooks/*","/media-assets/*","/internal/*"]

  • DB → D1
  • ASSETS_BUCKET → R2
  • EMAIL → Send Email
  • ASSETS → Static assets
apps/api/wrangler.jsonc ↗

Cloudflare Workers · compatibility 2026-07-13

vibecms-api-dev · default

Entrypoint: src/index.ts

Static assets: ../dashboard/dist · single-page-application · Worker first: ["/api/*","/mcp","/mcp/*","/webhooks/*","/media-assets/*","/internal/*","/__scheduled"]

Cron triggers (UTC): 17 2 * * * · */15 * * * *

Configured route patterns: app.basedui.dev · app.basedui.dev/*

  • DB → D1
  • ASSETS_BUCKET → R2
  • EMAIL → Send Email
  • ASSETS → Static assets

vibecms-api-dev · env.development

Inherited from default: main, compatibility_date, compatibility_flags

Entrypoint: src/index.ts

Static assets: ../dashboard/dist · single-page-application · Worker first: ["/api/*","/mcp","/mcp/*","/webhooks/*","/media-assets/*","/internal/*","/__scheduled"]

Cron triggers (UTC): 17 2 * * * · */15 * * * *

Configured route patterns: app.basedui.dev · app.basedui.dev/*

  • DB → D1
  • ASSETS_BUCKET → R2
  • EMAIL → Send Email
  • ASSETS → Static assets

vibecms-prod · env.production

Inherited from default: main, compatibility_date, compatibility_flags

Entrypoint: src/index.ts

Static assets: ../dashboard/dist · single-page-application · Worker first: ["/api/*","/mcp","/mcp/*","/webhooks/*","/media-assets/*","/internal/*","/__scheduled"]

Cron triggers (UTC): 17 2 * * * · */15 * * * *

Configured route patterns: app.vibecms.dev · app.vibecms.dev/*

  • DB → D1
  • ASSETS_BUCKET → R2
  • EMAIL → Send Email
  • ASSETS → Static assets
apps/public/wrangler.jsonc ↗

Cloudflare Workers · compatibility 2026-07-13

vibecms-public-dev · default

Entrypoint: @astrojs/cloudflare/entrypoints/server

Configured route patterns: basedui.dev · *.basedui.dev/* · */*

  • DB → D1
  • ASSETS_BUCKET → R2
  • ANALYTICS → Analytics Engine
  • IMAGES → Images
  • API → Worker service · service vibecms-api-dev

vibecms-public-prod · env.production

Inherited from default: main, compatibility_date, compatibility_flags

Entrypoint: @astrojs/cloudflare/entrypoints/server

Configured route patterns: vibecms.dev · *.vibecms.dev/* · */*

  • DB → D1
  • ASSETS_BUCKET → R2
  • ANALYTICS → Analytics Engine
  • IMAGES → Images
  • API → Worker service · service vibecms-prod
wrangler.public.jsonc ↗

Cloudflare Workers · compatibility 2026-07-13

vibecms-public · default

Entrypoint: @astrojs/cloudflare/entrypoints/server

  • DB → D1
  • ASSETS_BUCKET → R2
  • IMAGES → Images
  • API → Worker service · service vibecms
apps/api/wrangler.test.jsonc ↗

Cloudflare Workers · compatibility 2026-05-01

vibecms-api-test · default

  • DB → D1
apps/public/wrangler.test.jsonc ↗

Cloudflare Workers · compatibility 2026-07-13

vibecms-public-test · default

  • DB → D1
  • IMAGES → Images
packages/db/wrangler.test.jsonc ↗

Cloudflare Workers · compatibility 2026-05-01

vibecms-db-test · default

  • DB → D1

Named environments are separate deployments. Bindings are shown only where declared. Configured routes are URL patterns, not verified application endpoints.

Runtime source · handlers, binding usage and workflow steps

Observed TypeScript/JavaScript declarations from Worker entrypoints and resolved relative imports. Calls and workflow steps may run conditionally; their listed order is not a proven end-to-end request flow. Router declarations may be mounted under a prefix or may not be registered. This shows code wiring, not a successful deployment or runtime test. Dynamic wiring, aliases and generated code may not resolve.

apps/api/src/index.ts ↗
  • L242 · fetch handler exported · calls runWithExecutionContext, app.fetch
  • L245 · scheduled handler exported · calls ctx.waitUntil, runWithExecutionContext, runScheduledJobs
  • L57 · app.use("*")
  • L63 · app.use("*")
  • L64 · app.use("*")
  • L70 · app.use("*")
  • L72 · app.use("/api/auth/*")
  • L73 · app.use("/api/dashboard/*")
  • L74 · app.use("/api/subscribe")
  • L75 · app.use("/api/v1/*")
  • L76 · app.use("/mcp")
  • L78 · app.use("*")
  • L88 · app.use("/api/auth/*")
  • L93 · app.use("/api/dashboard/*")
  • L98 · app.use("/api/subscribe")
  • L103 · app.use("/api/v1/*")
  • L108 · app.use("/mcp")
  • L113 · app.use("/internal/*")
  • L118 · app.get("/api/health/live")
  • L126 · app.get("/api/health/ready")
  • L141 · app.post("/api/subscribe")
  • L143 · app.all("/api/auth/*")
  • L149 · app.route("/api/dashboard")
  • L151 · app.post("/api/onboarding/ensure")
  • L164 · app.post("/api/polar/webhook")
  • L168 · app.get("/api/export.json")
  • L174 · app.post("/api/media/upload")
  • L186 · app.post("/api/media/delete")
  • L195 · app.get("/media-assets/:assetId")
  • L197 · app.all("/api/v1/*")
  • L198 · app.all("/mcp")
  • L199 · app.route("/internal/autoseopilot")
  • L36 · requestLog calls (conditional paths may differ): c.get, c.req.header
  • L48 · redactErrorText calls (conditional paths may differ): replace, value.slice

Environment references: c.env.CF_VERSION_METADATA · c.env.DB · c.env.ASSETS_BUCKET · c.env.ASSETS

Build and deployment pipeline · 3 GitHub Actions workflows

Repository CI declarations, separate from runtime request processing. Job dependencies and conditions are shown as written; long commands are shortened with an ellipsis; a workflow file does not prove a recent successful run.

CI · .github/workflows/ci.yml ↗

Triggers: pull_request, push

verify · no job dependencies declared

  1. Checkoutactions/checkout@v4
  2. Setup pnpmpnpm/action-setup@v4
  3. Setup Nodeactions/setup-node@v4
  4. Install dependenciespnpm install --frozen-lockfile
  5. Audit production dependenciespnpm audit --prod --audit-level=high
  6. Typecheckpnpm typecheck
  7. Lintpnpm lint
  8. Testpnpm test
  9. Audit public releasepnpm public:audit
  10. Verify OpenAPI artifactpnpm openapi:check
  11. Buildpnpm build
Deploy production · .github/workflows/deploy-production.yml ↗

Triggers: workflow_dispatch

deploy · no job dependencies declared

  1. Checkoutactions/checkout@v4
  2. Setup pnpmpnpm/action-setup@v4
  3. Setup Nodeactions/setup-node@v4
  4. Install dependenciespnpm install --frozen-lockfile
  5. Require authenticated smoke tokenif [ -z "${PRODUCTION_SMOKE_TOKEN}" ]; then echo "PRODUCTION_SMOKE_TOKEN secret is required for smoke_mode=authenticated" >&2 echo "This workflow is only a thin wrapper around pnpm deploy:prod and cannot claim production gates without that input." >&2 exit 1 fiCondition: ${{ inputs.smoke_mode == 'authenticated' }}
  6. Canonical production deploypnpm deploy:prod
Release · .github/workflows/release.yml ↗

Triggers: push

release · no job dependencies declared

  1. Checkoutactions/checkout@v4
  2. Setup pnpmpnpm/action-setup@v4
  3. Setup Nodeactions/setup-node@v4
  4. Install dependenciespnpm install --frozen-lockfile
  5. Changesets releasechangesets/action@v1
package.json ↗
  • build: pnpm --filter @vc/dashboard build && pnpm --filter @vc/api build && pnpm --filter @vc/public build
  • build:self-host: pnpm --filter @vc/dashboard build && pnpm --filter @vc/api exec wrangler deploy --dry-run --config ../../wrangler.jsonc --outdir ../../.wrangler/self-host-api && CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH=../../wrangler.public.jsonc pnpm --filter @vc/public build
  • deploy: pnpm build:self-host && pnpm db:migrate:self-host:remote && pnpm --filter @vc/api exec wrangler deploy --config ../../wrangler.jsonc && pnpm --filter @vc/public exec wrangler deploy --config dist/server/wrangler.json
  • deploy:dev: pnpm dev:sync-pepper && pnpm db:migrate:dev && pnpm --filter @vc/dashboard build && pnpm --filter @vc/api deploy && pnpm --filter @vc/public build && pnpm --filter @vc/public exec wrangler deploy --config dist/server/wrangler.json
  • deploy:prod: pnpm production:preflight && pnpm production:backup && pnpm --filter @vc/api exec wrangler d1 migrations apply DB --remote --env production && pnpm --filter @vc/api exec wrangler deploy --env production && pnpm --filter @vc/public exec wrangler deploy --config dist/server/wrangler.json && pnpm production:smoke
  • release: changeset publish
apps/api/package.json ↗
  • build: wrangler deploy --dry-run --env development --outdir=dist
  • deploy: wrangler deploy --env development
packages/cli/package.json ↗
  • build: tsc && cp ../../apps/api/openapi.json dist/openapi.json
  • prepublishOnly: pnpm build

Full upstream document by @moinulmoin · README.md · snapshot efe7ae7

vibecms

CMS for AI Agents.

The CMS your agents publish into: Markdown posts, versions, and media over scoped MCP or the REST /api/v1 API - you own every post. Every mutation creates activity, and meaningful post changes create versions.

Features

  • Clean hosted blog dashboard
  • Public blog pages
  • Markdown post editor
  • R2 media uploads
  • D1 database
  • Activity history
  • Post version history
  • Scoped agent tokens with vc_ prefixes
  • MCP endpoint for trusted agents
  • Polar billing for hosted vibecms Cloud
  • SELF_HOSTED=true mode without Polar

Connect an MCP client

vibecms exposes standards-based MCP over Streamable HTTP. In the dashboard, open Connect, create a scoped token, and copy it once.

Claude Code is the primary example:

claude mcp add --transport http vibecms https://your-vibecms-domain.com/mcp \
  --header "Authorization: Bearer vc_..."

Any compatible MCP client uses the same endpoint and bearer credential:

{
  "mcpServers": {
    "vibecms": {
      "type": "http",
      "url": "https://your-vibecms-domain.com/mcp",
      "headers": {
        "Authorization": "Bearer vc_..."
      }
    }
  }
}

Install the client-independent safety and writing skills:

npx skills add moinulmoin/vibecms --skill vibecms-core --skill vibecms-writing

Verify credentials with a protected read—not tools/list, which is intentionally available for tool discovery:

curl https://your-vibecms-domain.com/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer vc_..." \
  --data '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"sites.get","arguments":{}}}'

A valid token returns the current site in structuredContent; an invalid or missing token returns 401. The approval-first publishing flow is:

sites.get -> posts.format_guide -> draft -> posts.preview
  -> latest saved version -> explicit approval
  -> posts.publish(postId, expectedVersionNumber) -> returned URL

The REST /api/v1 API mirrors the MCP tools with full read/write parity. Post lists return summaries without full Markdown, so fetch a single post body through REST or MCP posts.get:

curl "https://your-vibecms-domain.com/api/v1/posts?limit=20&offset=0" \
  -H "Authorization: Bearer vc_..."

Hosted vibecms Cloud counts MCP and REST against the same workspace API quota. Rate-limit failures are machine-readable: REST returns 429 with RATE_LIMIT, and MCP returns a JSON-RPC rate-limit error.

License

vibecms is licensed under AGPL-3.0-or-later. See LICENSE.

The vibecms name and marks are covered by the trademark guidelines in TRADEMARKS.md.

Scripts

pnpm install
pnpm typecheck
pnpm lint
pnpm test
pnpm build
pnpm public:audit
pnpm db:migrate:local
pnpm db:seed:local
pnpm dev

Database migration SQL lives in packages/db/drizzle/ and is shared by both Workers. Local API development, Astro rendering, migrations, and seed commands share the root .wrangler/state directory so both Workers see the same D1 and R2 data.

See MILESTONES.md for the milestone-by-milestone build plan and acceptance checks.

Self-host mode

vibecms now has a real self-host switch:

SELF_HOSTED=true

In self-host mode, Polar is optional, billing gates are disabled, and hosted workspace API quotas are not enforced by default. After signup and blog setup, the owner lands on /dashboard; publishing, media uploads, scoped agent access, activity history, and post versions run on the self-hoster's Cloudflare D1/R2 resources.

The repository deploys the same two-Worker topology in hosted and self-hosted modes:

  • apps/api/wrangler.jsonc and apps/public/wrangler.jsonc configure hosted development/production.
  • Root wrangler.jsonc configures the self-hosted Hono API + dashboard Worker.
  • Root wrangler.public.jsonc configures the self-hosted Astro public-blog Worker.
  • pnpm deploy builds both, applies D1 migrations, then deploys API before public.

Deploy button shape, once this repo is public:

[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/moinulmoin/vibecms)

During deploy, configure both root Wrangler files. Public blogs are host-only (tenant identity is the host), so the API/dashboard and public blog use separate Worker hosts:

APP_URL=https://vibecms.<your-subdomain>.workers.dev
BETTER_AUTH_URL=https://vibecms.<your-subdomain>.workers.dev
PUBLIC_BLOG_DOMAIN=vibecms-public.<your-subdomain>.workers.dev
SELF_HOSTED=true

The public blog serves at /, /<post-slug>, and /tag/<tag> on PUBLIC_BLOG_DOMAIN; removed /blog/<site-slug>/* path mode is not supported.

The only required self-host secrets are listed in .dev.vars.example:

BETTER_AUTH_SECRET=<generate with openssl rand -hex 32>
TOKEN_PEPPER=<generate with openssl rand -hex 32>

Minimal local/self-host env shape (local URLs are not public hosted URLs):

APP_URL=https://vibecms.example.workers.dev
BETTER_AUTH_URL=https://vibecms.example.workers.dev
PUBLIC_BLOG_DOMAIN=vibecms-public.example.workers.dev
SELF_HOSTED=true
BETTER_AUTH_SECRET=<generate with openssl rand -hex 32>
TOKEN_PEPPER=<generate with openssl rand -hex 32>

See docs/self-hosting.md for the Cloudflare self-host flow and deploy-button notes.

Launch notes

  • Configure shared Cloudflare D1/R2 IDs in apps/api/wrangler.jsonc and apps/public/wrangler.jsonc before production deploy.
  • Set API Worker secrets with Wrangler: BETTER_AUTH_SECRET, TOKEN_PEPPER, POLAR_ACCESS_TOKEN, and POLAR_WEBHOOK_SECRET.
  • Set product, URL, and host variables in both Worker configs.
  • Run pnpm deploy:prod; it preflights and builds production artifacts, captures backup metadata, migrates D1, deploys API then public, then smokes. Astro sessions are disabled (no SESSION KV).

For self-hosted production, set SELF_HOSTED=true and only BETTER_AUTH_SECRET plus TOKEN_PEPPER are required as secrets; Polar access token/product/webhook secrets are hosted-SaaS only.

Dev deployment

Current Cloudflare development resources are wired in apps/api/wrangler.jsonc and apps/public/wrangler.jsonc:

  • API + dashboard Worker: vibecms-api-dev at https://app.basedui.dev
  • Public Astro Worker: vibecms-public-dev at https://basedui.dev and *.basedui.dev
  • Shared D1 database: vibecms_dev
  • Shared R2 bucket: vibecms-assets

Run the full dev deploy/test flow:

pnpm install
pnpm typecheck
pnpm lint
pnpm db:seed:dev
pnpm deploy:dev

pnpm deploy:dev syncs the development token pepper, applies remote D1 migrations, builds the dashboard assets, deploys the Hono API Worker, then builds and deploys the Astro public Worker.

For Polar billing, create a sandbox product in Polar and update:

# In apps/api/wrangler.jsonc, replace product_dev_placeholder:
# "POLAR_PRODUCT_ID": "<your Polar sandbox product id>"

pnpm --filter @vc/api exec wrangler secret put POLAR_ACCESS_TOKEN
pnpm --filter @vc/api exec wrangler secret put POLAR_WEBHOOK_SECRET
pnpm deploy:dev

Recommended sandbox product setup:

  • Recurring subscription product.
  • Price: $19/month, or $190/year if you create a yearly product/price in Polar.
  • Set the monthly product as POLAR_MONTHLY_PRODUCT_ID. If yearly is a separate Polar product, set it as POLAR_YEARLY_PRODUCT_ID.
  • In hosted mode, new workspaces stay behind the Polar checkout gate until checkout/webhooks mark billing active. In self-host mode, SELF_HOSTED=true bypasses billing gates entirely.
  • Launch entitlement: 1 hosted blog, unlimited posts, 5 GB media, scoped agent access, activity and version history, and reader + AI discovery analytics.
  • Upload policy enforced by the app: JPEG/PNG/WebP/GIF only, 10MB max image size, no video hosting, no generic file hosting.

Recommended minimum Polar organization access token scopes:

  • checkouts:write for creating checkout sessions.
  • customer_sessions:write for creating customer portal sessions.

You do not need product, order, refund, file, meter, webhook, or subscription write scopes for the current app runtime. Webhooks are verified with POLAR_WEBHOOK_SECRET, not the API token.

In Polar, set the webhook endpoint to:

https://app.vibecms.dev/polar/webhook

Subscribe to these webhook events:

  • Required: subscription.created, subscription.updated, subscription.active, subscription.past_due, subscription.canceled, subscription.revoked.
  • Required for checkout/customer reconciliation fallback: checkout.updated.
  • Optional but useful for analytics later: order.paid.

The app currently updates billing state from subscription.* payloads and from successful checkout.updated payloads. Keep the webhook delivery format as raw JSON and copy the endpoint signing secret into POLAR_WEBHOOK_SECRET.

Frequently asked about vibecms

What is vibecms?+

vibecms is a self-hosted Ghost/WordPress alternative built on the Cloudflare developer platform. Self-hosted Markdown blog publishing with media and scoped agent access

What does vibecms replace?+

vibecms is listed as an alternative to Ghost, WordPress. Compare the features and tradeoffs before migrating.

What Cloudflare primitives does vibecms use?+

vibecms is built on D1, Images, R2, Workers.

How much does vibecms cost to run?+

A conservative small self-hosted installation uses Workers Paid from $5 USD/account/month, covering dynamic rendering and arbitrary-recipient Email Service when enabled. D1/R2 overages, enabled email sending and image transformations beyond 5,000 unique transformations/month are additional. SELF_HOSTED=true makes Polar optional; hosted public Analytics Engine configuration is not the core self-host topology. Use the root self-host configuration, create your own D1/R2 resources and deploy the separate public renderer sharing those resources. Provide BETTER_AUTH_SECRET and TOKEN_PEPPER. Email verification/sending needs current Email Service entitlement; no blanket free-runtime claim is made. Source and configuration review establishes a deployment path and conditional costs; this candidate was not executed or load-tested. Check current Cloudflare pricing before deploying.

Is vibecms open source?+

The upstream repository declares the AGPL-3.0 license. Read its terms at https://raw.githubusercontent.com/moinulmoin/vibecms/efe7ae7133f3a3ef3ab9262c7dc46e4d09e94567/LICENSE. Source code and contributor credit are available at https://github.com/moinulmoin/vibecms.

Discussion · 0

sign in to comment →
No comments yet — be the first.