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

better-content

v0.2.0

Published

Framework-neutral, own-your-data, adapter-driven inline-edit CMS engine.

Downloads

220

Readme

better-content

CI npm

Own-your-data, adapter-driven, framework-agnostic inline-edit CMS engine. MIT.

Try the live demo: edit the page in your browser, every field persists to a real Postgres running in your tab.

17-second demo: toggling edit mode, typing into the page, saving, and inspecting the database

Edit content directly on your live pages. Changes persist to your database through a small adapter seam, with no hosted service, no lock-in, and no proprietary backend. The engine is a framework-free external store; React is the first binding.

Status: pre-1.0. The API is still settling; minor versions may break until 1.0. Two adapters (Postgres, Firestore), one storage provider (Cloudinary), and one auth provider (Firebase) ship as proof of the seams.

Why

  • Own your data — content lives in your Postgres/Firestore, queried through a 7-method DataAdapter you can implement in an afternoon.
  • DB-backed, not git-backed — unlike git-based CMSes, content is rows, so it works for apps, not just static sites.
  • Framework-agnostic core — the engine is getSnapshot()/subscribe() (the same seam Zustand and Redux use). better-content/core imports zero framework code; bindings are thin adapters.
  • Inline editing as the primary UX — headless contentEditable primitives with data-cms-* styling hooks. No admin panel required.
  • Transport-agnostic — the engine speaks a 3-method Transport (save/patch/remove). REST ships as the default; swap in tRPC, server actions, or a direct adapter without touching the engine.

Install

npm install better-content

React, database drivers, and SDKs are optional peer dependencies — install only what you use (react, drizzle-orm + pg, firebase-admin, firebase, cloudinary).

Quickstart (React + REST)

1. Wrap your page and make a field editable:

import { restTransport } from "better-content/core";
import {
  AnonymousEditProvider,
  ContentEditSpan,
  PageProvider,
} from "better-content/react";

export default function Page({ initialItems }) {
  return (
    <AnonymousEditProvider>
      <PageProvider transport={restTransport()} initialItems={initialItems}>
        <ContentEditSpan as="h1" collection="sections" itemId="hero" fieldKey="heading" />
      </PageProvider>
    </AnonymousEditProvider>
  );
}

2. Mount the API (Next.js App Router shown; any Request/Response runtime works):

// app/api/admin/[collection]/[id]/route.ts
import { createCmsHandlers } from "better-content/server";
import { PostgresDataAdapter } from "better-content/adapters/postgres";
import { firebaseAuth } from "better-content/auth/firebase";
import { schema } from "@/db/schema";

export const { GET, PATCH, PUT, DELETE } = createCmsHandlers({
  data: new PostgresDataAdapter({ connectionString: process.env.DATABASE_URL, schema }),
  auth: firebaseAuth({ adminEmails: ["[email protected]"] }),
});

3. Load initial content on the server:

import { loadItemMap } from "better-content/server";

const initialItems = await loadItemMap(data, {
  sections: { defaults: [{ id: "hero", heading: "Hello" }], merge: "byId" },
});

Toggle edit mode, click the text, type, save. Field edits are deferred (buffered locally, flushed by saveAll); item operations (create/update/delete/reorder) are immediate and optimistic with rollback.

Fine-grained re-renders: useCmsItem(collection, id) subscribes a component to a single item, so editing one field re-renders one editor, not the page. useCmsEngine() returns the stable engine for calling operations without subscribing.

See your data while you build: mount the dev inspector and get a floating button that opens a live view of your collections, refreshed on every save.

import { DataInspector } from "better-content/devtools/react";

{process.env.NODE_ENV === "development" && (
  <DataInspector adapter={adapter} engine={engine} collections={["sections"]} />
)}

It is a framework-free custom element underneath (better-content/devtools), so Vue, Svelte, and plain HTML apps can use registerDataInspector() + <better-content-inspector> directly.

Subpath exports

| Import | What | Runs on | |---|---|---| | better-content/core | engine, Transport (restTransport, adapterTransport, inMemoryTransport), types | anywhere | | better-content/react | PageProvider, ContentEditSpan, EditableImage, useCmsItem, useMarkdownEditor, auth context | client | | better-content/devtools | <better-content-inspector> custom element: live database view for development | client | | better-content/devtools/react | typed DataInspector React wrapper for the element | client | | better-content/server | createCmsHandlers, createAdminGate, loadItemMap, resolveRelations | server | | better-content/adapters/postgres | Drizzle-backed, typed-only adapter | server | | better-content/adapters/firestore | Firestore adapter (throws on unsupported ops) | server | | better-content/storage/cloudinary | client upload half (pure fetch) | client | | better-content/storage/cloudinary/server | signature endpoint half | server | | better-content/auth/firebase | request verification (AuthAdapter) | server | | better-content/auth/firebase/client | FirebaseAuthProvider + useFirebaseAuth | client |

Client subpaths never import server SDKs — safe to bundle.

Adapters

Postgres is typed-only: every collection is a Drizzle pgTable you declare and migrate yourself (Drizzle Kit). Writes to undeclared fields or unregistered collections throw. No JSONB fallback, no hidden DDL.

Firestore maps the neutral query ops it can honor natively and throws on the rest (contains, OR groups) rather than silently misleading.

Bring your own backend by implementing DataAdapter — 7 methods (fetchById, fetchCollection, create, createWithId, update, upsert, delete) against a neutral Query.

Auth

Server: an AuthAdapter resolves a request to an identity; createAdminGate rejects non-admins (401 with { logout: true }, or 403). Firebase ships built-in: admin custom claim and an email allowlist must agree.

Client: AnonymousEditProvider lets visitors toggle edit mode locally on a live site while every write stays gated server-side — try-before-you-buy editing with zero risk.

Development

npm test          # vitest (in-memory transport + PGlite — no infra needed)
npm run build     # tsup: ESM + CJS + .d.ts per subpath

The examples/playground Vite app runs the full stack (engine → REST → handlers → Postgres-on-PGlite) with npm run dev and zero external services.

License & positioning

MIT. Independent project — not affiliated with better-auth or the better-* family; it shares their values: own your data, framework-agnostic, TypeScript-first, MIT.