npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

basic-blog-convex-blog-cms

v0.1.13

Published

Headless blog and CMS as a Convex component: posts, block content, site settings, Convex file uploads, bundled TipTap admin + convex-blog-admin CLI, and optional Next.js metadata, RSS, sitemap, and JSON-LD helpers.

Downloads

1,349

Readme

basic-blog-convex-blog-cms

Published npm package: headless blog and CMS on Convex—isolated component tables (posts, postBlocks, siteSettings), validated queries and mutations, Convex file storage for images, and a bundled TipTap admin you run with npx blog-admin-serve (recommended) or npx convex-blog-admin serve.

Also ships TypeScript helpers to mount a host API (makeBlogAdminAPI) and, optionally, Next.js metadata, RSS, sitemap, and JSON-LD (including richer post fields: canonical path, noindex, answer summary, key takeaways, FAQ for structured data). Public site rendering is your app’s job: use hydrated DTOs from public queries and your own React (or see the reference UI and Next.js example in the repo).

This monorepo also includes @basic-blog/convex-blog-mcp (not published to npm): an MCP server that calls your host’s listPostsForAdmin, createPost, and updatePost over HTTP—useful for agents and automation. See packages/convex-blog-mcp/README.md.

Follows Convex component authoring (the Components Authoring page summarizes directory expectations): convex.config, ComponentApi, and a ./test entry for convex-test.

Install

npm install basic-blog-convex-blog-cms convex

Peer dependencies: convex (^1.33.1). Optional: next (^14 || ^15 || ^16) for ./next helpers.

Demo (this repository)

To run the minimal Convex host that exercises this component from a checkout of basic-blog:

  1. From the monorepo root: pnpm install
  2. cd examples/convex-host and run npx convex dev (link or create a Convex project when prompted).
  3. From the monorepo root: CONVEX_URL="https://YOUR_DEPLOYMENT.convex.cloud" pnpm blog:admin. Or from packages/convex-blog-cms: pnpm blog:admin. From any project that depends on this package: npx blog-admin-serve or npx convex-blog-admin serve.

Open http://127.0.0.1:3847/admin. See examples/convex-host/README.md for details.

Start the admin panel

This package ships a pre-built admin UI and two CLIs: blog-admin-serve (wrapper: reads CONVEX_URL or NEXT_PUBLIC_CONVEX_URL, normalizes .convex.site.convex.cloud, runs the pinned convex-blog-admin) and convex-blog-admin (underlying serve command). You do not need to scaffold a Next.js app for the default workflow: you wire Convex once, then run one command and open a local URL in the browser.

Bundled admin UI (screenshot)

The SPA includes an articles list (search, Drafts / Published), a post editor with title, slug, author, created/published date, excerpt, collapsible SEO & metadata (meta title/description, Open Graph and featured images with 16:9 / 5:4 previews and an interactive focal point), and a TipTap body editor. Additional post fields (canonical path, noindex, answer summary, key takeaways, FAQ) are stored on the component and consumed by Next.js metadata, JSON-LD, and the reference BlogPost; set them with updatePost, custom UI, or the MCP update_article tool.

The body editor is Markdown-oriented: headings, bullet and numbered lists, blockquotes, bold / italic / strikethrough, links, inline and fenced code, images (URL or device upload to storage), horizontal rule, and YouTube embeds. Paragraph blocks are stored as Markdown and rendered on the public site with react-markdown (see the Next.js and blog-ui examples). Rich paste from sources such as Google Docs usually preserves lists and basic formatting; plain pasted text stays as paragraphs unless you add list markers or structure in the editor.

Bundled admin UI: article list, post editor, and cover image with aspect previews

Prerequisites

| You need | Why | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | basic-blog-convex-blog-cms installed | Supplies blog-admin-serve, convex-blog-admin, and the static UI under dist/admin-spa. | | Component + makeBlogAdminAPI in your Convex app | The admin talks to your deployment using blog.* queries/mutations (e.g. convex/blog.ts), including blog.generateUploadUrl for image uploads. See Register the component and Host API: makeBlogAdminAPI below, and the full Setup guide. | | Your Convex deployment URL | Use CONVEX_URL or NEXT_PUBLIC_CONVEX_URL (wrapper accepts either). Must be the .convex.cloud deployment URL for the JS client; if you only have a .convex.site HTTP Actions URL, blog-admin-serve rewrites it to .convex.cloud and warns. |

Steps (bundled UI — recommended)

  1. Install in the project that contains your Convex backend (or any project where you will run the CLI):
 npm install basic-blog-convex-blog-cms
  1. Implement and deploy the host Convex API (convex/convex.config.ts, convex/blog.ts exporting everything from makeBlogAdminAPI, including generateUploadUrl) so functions are available on the deployment you will target. Follow Setup if you have not done this yet.
  2. Start the admin from that project’s directory (so node_modules resolves). Prefer the wrapper (same env vars as a Next.js app, no extra serve token):
 CONVEX_URL="https://YOUR_DEPLOYMENT.convex.cloud" npx blog-admin-serve

Equivalent low-level command:

 CONVEX_URL="https://YOUR_DEPLOYMENT.convex.cloud" npx convex-blog-admin serve
  1. Open the UI in your browser: http://127.0.0.1:3847/admin
  • Default listen address: 127.0.0.1, default port: 3847.
  • Another port: npx blog-admin-serve --port 3001 (or npx convex-blog-admin serve --port 3001).
  • Optional: BLOG_ADMIN_PORT or PORT if you omit --port (wrapper passes through; PORT suits Railway and other hosts).
  • Remote / Docker: set BLOG_ADMIN_HOST=0.0.0.0 so the process accepts external connections (the Dockerfile.admin used on Railway sets this).

Deploy the admin on Railway

Deploy on Railway

  1. Click the button, connect GitHub, and import this repository.
  2. Set the service Root Directory to packages/convex-blog-cms (so railway.toml picks Dockerfile.admin; if Railway builds from the monorepo root instead, confirm Build uses that Dockerfile, not Nixpacks — same pitfall as convex-blog-mcp).
  3. Under Variables, add CONVEX_URL = your deployment URL (https://….convex.cloud). Optional: BLOG_ADMIN_API_KEY if you use token auth (must match Convex).
  4. Networking → Generate Domain, then open /admin on that URL (for example https://your-service.up.railway.app/admin).

Railway sets PORT automatically; the image sets BLOG_ADMIN_HOST=0.0.0.0. Health check: GET /health200 + ok.

Security: A public URL exposes the admin to the internet. Prefer Convex Auth in makeBlogAdminAPI for real protection; use BLOG_ADMIN_API_KEY / strictAdminApiKey as a baseline if you deploy this way.

Token auth (optional, demo-style): If you set BLOG_ADMIN_API_KEY in Convex (npx convex env set …), pass the same value when serving so the browser can call admin APIs:

CONVEX_URL="https://…" BLOG_ADMIN_API_KEY="your-secret" npx blog-admin-serve

Uploads and strictAdminApiKey: blog.generateUploadUrl is an admin mutation and uses the same auth as updatePost and replacePostBlocks. If you pass strictAdminApiKey: true to makeBlogAdminAPI, the client must send a matching adminApiKey on every admin call, including generateUploadUrl. The bundled admin SPA attaches that key for saves and image uploads. If you build your own admin UI, pass adminApiKey into generateUploadUrl the same way you do for other mutations—otherwise uploads return Unauthorized.

npm script (optional): add to your app’s package.json:

{
  "scripts": {
    "cms:admin": "blog-admin-serve"
  }
}

Then run CONVEX_URL="https://…" npm run cms:admin, or load env vars from a file (e.g. [dotenv-cli](https://www.npmjs.com/package/dotenv-cli)).

What’s happening: npx blog-admin-serve runs a small launcher that sets CONVEX_URL and spawns convex-blog-admin serve from the same installed package (no extra npx fetch). The serve command serves the built files in node_modules/basic-blog-convex-blog-cms/dist/admin-spa and exposes GET /config.json using CONVEX_URL (and optional BLOG_ADMIN_API_KEY) so the UI connects to your deployment. Nothing is copied into your repo.

Register the component

// convex/convex.config.ts
import { defineApp } from "convex/server";
import blogCms from "basic-blog-convex-blog-cms/convex.config.js";

const app = defineApp();
app.use(blogCms);
export default app;

The default component name is blogCms, so your generated API exposes components.blogCms. This matches:

export const BLOG_CMS_COMPONENT_NAME = "blogCms" as const;

Import BLOG_CMS_COMPONENT_NAME from this package if you want to avoid typos in docs or tooling (the runtime registration still uses defineComponent("blogCms") in the published component).

Host API: makeBlogAdminAPI

import { makeBlogAdminAPI } from "basic-blog-convex-blog-cms";
import { components } from "./_generated/api.js";

export const { getPublishedPostBySlug, listPublishedPosts, generateUploadUrl, /* ... */ } =
  makeBlogAdminAPI(components.blogCms, {
    // Optional: shared secret checked on the server; clients pass `adminApiKey` on each admin call.
    // adminApiKeySecret: process.env.BLOG_ADMIN_API_KEY,
    auth: async (ctx, operation) => {
      if (operation.type === "adminRead" || operation.type === "adminWrite") {
        // your auth (e.g. Convex Auth) — skipped when adminApiKeySecret is set and key matches
      }
    },
  });

Public read queries do not call auth. Admin operations use adminApiKeySecret (if set; optionally strict) or auth.

Configuration

Environment variables (Convex)

Set with npx convex env set NAME value or the Convex dashboard.

| Variable | When | Purpose | | -------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | BLOG_ADMIN_API_KEY | Optional simple token auth | If set, clients may pass matching adminApiKey. Missing key is allowed unless you pass strictAdminApiKey: true to makeBlogAdminAPI. Treat like a password when you rely on it; prefer Convex Auth for production. |

blog.generateUploadUrl (from makeBlogAdminAPI) handles file storage uploads with the same admin auth as other writes (including optional adminApiKey when you use token auth or strictAdminApiKey); no separate env var for uploads.

Next.js / browser

| Variable | Required | Purpose | | -------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | NEXT_PUBLIC_CONVEX_URL | Yes for Convex React / convex/nextjs | Your deployment URL (from npx convex dev). | | NEXT_PUBLIC_BLOG_ADMIN_API_KEY | Only with simple token auth | Must match BLOG_ADMIN_API_KEY if you use that flow from a browser admin UI. Exposed to the client — dev/demo only; use Convex Auth in production. |

makeBlogAdminAPI auth

The component does not call ctx.auth internally. Your host convex/blog.ts passes options to makeBlogAdminAPI:

  • adminApiKeySecret: If set (e.g. process.env.BLOG_ADMIN_API_KEY), clients may pass adminApiKey matching the secret; the auth callback is not run when the key matches. If the secret is set but the client omits adminApiKey, access is still allowed by default; set strictAdminApiKey: true to require the token whenever the secret is configured.
  • auth: When adminApiKeySecret is unset (or empty), this runs for every admin operation. Use adminRead / adminWrite to enforce Convex Auth, sessions, or roles (or a no-op for an open dev admin).
  • Public queries (getPublishedPostBySlug, listPublishedPosts, getPublicSiteSettings) never use these paths.

makeBlogAdminAPI resolves Convex file storage ids to HTTPS URLs on public reads. getPublishedPostBySlug / listPublishedPosts / getPublicSiteSettings return hydrated DTOs suitable for SEO helpers. getPostForAdmin returns raw post and blocks (including storageId fields where used) plus hydratedPost and hydratedBlocks for previews.

Component instance name

The default registration name is blogCms, so generated code uses components.blogCms in makeBlogAdminAPI(components.blogCms, …). If you register the component under a different name with app.use, pass that name instead. The package exports BLOG_CMS_COMPONENT_NAME ("blogCms") to avoid typos.

HTTP routes (RSS / sitemap)

RSS and sitemap live in the host app, not inside the component. Mount httpAction routes in your convex/http.ts (see the reference http.ts in this repo). Typical paths: GET /rss.xml, GET /sitemap.xml.

Site settings (global)

The component stores a single siteSettings row (key default) edited from the bundled admin at Site settings (/admin/settings). Values are the canonical place for public-site identity and URL base used across SEO helpers; they live in Convex so you can change branding or the public origin without redeploying the host app.

| Field | Purpose | | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Site name | Default site title and title.template for Next.js via siteSettingsToDefaultMetadata (%s | {siteName}). | | Base URL (https://…, no trailing slash required) | Origin for absolute canonical URLs, Open Graph url, JSON-LD, RSS links, and sitemap loc. Should match the URL visitors use in production. | | Default OG image URL | Fallback when a post has no OG/featured image and no inline image applies—used by resolvePrimaryImage and thus postToNextMetadata / social previews. Optional HTTPS URL. | | Locale (e.g. en) | RSS <language> in buildRssXml (defaults to en when unset). | | Default robots | Passed to Next.js root metadata as robots via siteSettingsToDefaultMetadata (e.g. sitewide crawl hints). |

Consuming settings in your app: use the public query getPublicSiteSettings from makeBlogAdminAPI (returns a hydrated SiteSettingsDTO). Pass that object into:

  • postToNextMetadata, blogIndexToNextMetadata, and siteSettingsToDefaultMetadata (basic-blog-convex-blog-cms/next)
  • buildRssXml, postsToSitemapEntries + buildSitemapXml (URLs plus optional image metadata in one sitemap), buildArticleJsonLd, buildBlogIndexJsonLd, buildWebSiteJsonLd, as shown in the reference http.ts

Optional fallbackBaseUrl on postToNextMetadata, blogIndexToNextMetadata, and buildArticleJsonLd aligns absolute URLs with NEXT_PUBLIC_BASE_URL (or similar) when Convex baseUrl is unset in dev. absoluteUrlFromSite (and helpers that use it) resolve paths against site.baseUrl or that fallback. Production should still set baseUrl to the real public origin so RSS, sitemap, and metadata stay consistent.

siteSettingsToDefaultMetadata sets Next metadataBase only when baseUrl parses as a valid URL (invalid values are skipped so render does not throw).

Your visitor-facing routes (e.g. Next.js app/blog/[slug]) should combine post + blocks + site from Convex so metadata and absolute links stay consistent with the CMS.

Post-level SEO and structured content

| Field / behavior | Role | | ---------------- | ---- | | canonicalPath | Overrides the path segment used for canonical URL, Open Graph url, and JSON-LD when set (otherwise the route path you pass into helpers is used). | | noindex | When true, postToNextMetadata sets Next robots to index: false, follow: true. | | Meta description fallback | postToNextMetadata uses meta description, then excerpt, then derivePlainTextDescriptionFromBlocks(blocks) (exported from ./next) for description. | | answerSummary | Short lead text; included in article JSON-LD as abstract when set; also used in JSON-LD description fallbacks. | | keyTakeaways, faq | Optional lists for on-page rendering (see examples/blog-ui). Non-empty faq makes buildArticleJsonLd emit an FAQPage alongside BlogPosting in a @graph. |

For featured images with featuredImageFocalX / featuredImageFocalY (0–100), featuredImageCoverStyle(post) (basic-blog-convex-blog-cms/next) returns inline styles (object-fit: cover and object-position) for visitor-facing layouts.

SEO and URLs (routing)

  • Set baseUrl in site settings to your real public origin (see table above).
  • Match post URL paths in HTTP handlers and in your framework to the same pattern (e.g. /blog/{slug} in Next.js and in sitemap/RSS builders). Use canonicalPath when a post must resolve to a different public path than your default slug route.

Rendering (bring your own UI)

For visitor-facing pages, this package does not ship a bundled blog layout. Import PostDTO, BlockDTO, and SEO helpers from basic-blog-convex-blog-cms/next (or the root export for makeBlogAdminAPI and hydration helpers). See docs/RENDERING.md. For example BlogPost / BlockRenderer implementations, see examples/blog-ui in the repo (reference only — not an npm package). The reference BlogPost renders answer summary (lead) and key takeaways when those fields are set on the post.

Migration from basic-blog-convex-blog-cms/react

That export was removed. Replace imports with:

  • Types: basic-blog-convex-blog-cms/next
  • Components: copy or adapt from [examples/blog-ui](https://github.com/daocodotorg/basic-blog/tree/main/examples/blog-ui) (reference implementation in the repo, not published).

Package exports

| Export path | Purpose | | ------------------------------------------------- | ----------------------------------------------------------------------------------------- | | basic-blog-convex-blog-cms | makeBlogAdminAPI, types, hydration helpers | | basic-blog-convex-blog-cms/convex.config | defineComponent default for app.use() | | basic-blog-convex-blog-cms/next | PostDTO / BlockDTO, normalizeBaseUrl, absoluteUrlFromSite, derivePlainTextDescriptionFromBlocks, postToNextMetadata, blogIndexToNextMetadata, siteSettingsToDefaultMetadata, featuredImageCoverStyle, resolvePrimaryImage, buildArticleJsonLd, buildBlogIndexJsonLd, buildWebSiteJsonLd, RSS/sitemap builders | | basic-blog-convex-blog-cms/test | convex-test registration helper | | basic-blog-convex-blog-cms/_generated/component | ComponentApi type for components.blogCms |

basic-blog-convex-blog-cms/test

The ./test export points at TypeScript source (src/test.ts), not dist. Use it only from unit tests with [convex-test](https://www.npmjs.com/package/convex-test) to register this package as a component. Do not import it from production browser or app bundles; keep convex-test as a devDependency in the project that uses it. Published tarballs omit *.test.ts / *.test.tsx sources via package.json files patterns; src/test.ts is kept because it is not named *.test.ts.

Development / codegen

The bundled admin SPA served by convex-blog-admin serve is built from admin-spa/ (npm run build runs vite build after tsc).

Consumers run npx convex dev as usual. Package maintainers also run component codegen before build:

npx convex codegen --component-dir ./src/component
npm run build

See PUBLISHING.md for npm release steps.

Test the package before publishing

From the monorepo root (or after cloning):

pnpm install
pnpm --filter basic-blog-convex-blog-cms run build
pnpm --filter basic-blog-convex-blog-cms test
cd packages/convex-blog-cms && pnpm pack

Install the tarball in another project with npm install /path/to/basic-blog-convex-blog-cms-0.1.0.tgz (version from package.json). For release tagging and npm publish, see PUBLISHING.md.

More in the repository

Longer copy-paste walkthroughs (Next.js routes, fetchQuery, metadata) and the same topics in standalone pages:

License

Apache-2.0 (see repository LICENSE). Bundled third-party code in dist/admin-spa is summarized in [NOTICE](./NOTICE).