Documentation
Getting started
1. Install
Presscomet is a pnpm monorepo. The CMS lives in apps/cms, and its data — a single SQLite file plus uploads — in apps/cms/data/.
$ git clone https://github.com/nextpress/nextpress.git$ cd nextpress$ pnpm install# Create the database with sample content and an admin user$ pnpm db:seed# Start the CMS on http://localhost:3000$ pnpm dev:cmsOpen http://localhost:3000/wp-admin/ and log in as admin with the password password. Change it under Users › Profile before you put the site anywhere public.
Useful scripts
pnpm db:resetwipes the database and starts again.pnpm buildbuilds every app for production; run the CMS withpnpm --filter @presscomet/cms start.pnpm typecheckchecks the whole workspace.- Set
NEXTPRESS_DATA_DIRto keep the database and uploads outside the repository.
2. Migrate a WordPress site
In your new Presscomet admin, go to Tools › Import › WordPress (a fresh install also offers Migrate an existing WordPress site in the installer). Pick a source:
- Live site. Enter the site address and an application password created on the old site under Users › Profile › Application Passwords.
- WXR export. Upload the file from Tools › Export on the old site. Media is downloaded from the original URLs.
- SQL dump. Upload a
mysqldumpof the database together with a zip ofwp-content/uploads. - Companion plugin. For hosts that block the REST API, install the exporter plugin on the old site and paste the key it shows.
Keep Capture theme and Activate equivalent plugins ticked for an identical-looking site. The import runs in the background; you can leave the page and come back. When it finishes, compare both sites, then point your DNS at the new server.
Prefer a terminal? The same pipeline runs from the importer package:
$ pnpm --filter @presscomet/importer exec tsx src/cli.ts --url https://example.com --user admin --app-password "xxxx xxxx xxxx"# or from a file$ pnpm --filter @presscomet/importer exec tsx src/cli.ts --wxr export.xml# see what would map, without importing$ pnpm --filter @presscomet/importer exec tsx src/cli.ts --url https://example.com --analyze3. Static export
Go to Tools › Static Export, set the public URL the files will be served from, and run the export. You get every public page, archive, feed, sitemap and the 404.html page, with uploads alongside, as a folder or a zip.
Deploy the result anywhere that serves files:
- Netlify or Vercel: drag the folder into a new project, or point the project’s output directory at it.
- GitHub Pages: commit the folder to a
gh-pagesbranch. - S3: sync the folder to a bucket configured for static website hosting, with
404.htmlas the error document.
Comments and form submissions need a server. On a static site, either keep a private Presscomet instance reachable for those endpoints or turn them off.
4. Write a plugin
A plugin is a PluginDefinition object. setup(api) runs on boot and gets the hooks system plus registration functions for post types, taxonomies, blocks, meta boxes, widgets, dashboard widgets, settings pages and REST routes. Hook names are WordPress’s: the_content, init, save_post, wp_head.
import type { PluginDefinition } from "@presscomet/core";
// A complete plugin: one filter, one settings field, migrated options.
export const readingTime: PluginDefinition = {
slug: "reading-time",
name: "Reading Time",
description: "Shows an estimated reading time above each post.",
version: "1.0.0",
author: "You",
category: "content",
replaces: ["reading-time-wp/rt-reading-time.php"],
settings: [{
slug: "reading-time",
title: "Reading Time",
parent: "options-general.php",
sections: [{
id: "general",
title: "Display",
fields: [{ id: "wpm", label: "Words per minute", type: "number", default: 230 }],
}],
}],
setup(api) {
api.hooks.addFilter("the_content", async (html: string) => {
const { wpm } = await api.getSettings<{ wpm: number }>();
const words = html.replace(/<[^>]+>/g, " ").split(/\s+/).length;
const minutes = Math.max(1, Math.round(words / wpm));
return `<p class="reading-time">${minutes} min read</p>${html}`;
});
},
async migrate({ wpOptions, log }) {
// Carry over the old PHP plugin's setting during migration.
const old = wpOptions["rt_reading_time_options"] as { rt_words_per_minute?: number };
if (old?.rt_words_per_minute) log(`Reading speed: ${old.rt_words_per_minute} wpm`);
},
};Add it to the array exported from packages/plugins/src/index.ts and it appears on the Plugins screen, ready to activate. The optional replaces list and migrate() function let the migrator switch your plugin on and carry settings over when a site used the PHP original.
5. REST API
The WordPress REST API is served at /wp-json/wp/v2 with the same routes, parameters and response shapes. Authenticate with an application password using HTTP Basic auth:
$ curl -u "admin:abcd efgh ijkl mnop qrst uvwx" http://localhost:3000/wp-json/wp/v2/posts?per_page=5