# auto-cms-server

> Auto turn any webpage into editable CMS without coding.

Latest version **0.32.0** (published 2026-09-28) · BSD-2-Clause license · 0 weekly downloads

## Install

```sh
npm install auto-cms-server
pnpm add auto-cms-server
yarn add auto-cms-server
bun add auto-cms-server
```

Provides the commands `auto-cms`, `auto-cms-server`.

## Health

**Score 55/100 (C)** — status: active.

Positive: no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; no types; no esm support; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.32.0 |
| Published | 2026-09-28 |
| First published | 2024-01-22 |
| Weekly downloads | 0 |
| License | BSD-2-Clause |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 21 |
| Unpacked size | 130.7 KB |
| Known vulnerabilities | 0 (+15 in 1 direct dependencies) |
| Install scripts | no |
| GitHub stars | 9 |
| Author | Beeno Tung |
| Maintainers | beenotung |
| Keywords | cms, content, editor, html, nocode, low-code, server, cli |

## Links

- npm: https://www.npmjs.com/package/auto-cms-server
- Repository: https://github.com/beenotung/auto-cms
- Homepage: https://github.com/beenotung/auto-cms#readme
- Issues: https://github.com/beenotung/auto-cms/issues
- npm.io page: https://npm.io/package/auto-cms-server

## Dependencies (21)

- [knex](https://npm.io/package/knex.md) ^3.2.10
- [bytes](https://npm.io/package/bytes.md) ^3.1.2
- [debug](https://npm.io/package/debug.md) ^4.4.3
- [dotenv](https://npm.io/package/dotenv.md) ^16.6.1
- [cast.ts](https://npm.io/package/cast.ts.md) ^1.19.0
- [express](https://npm.io/package/express.md) ^4.22.2
- [formidable](https://npm.io/package/formidable.md) ^3.5.4
- [nodemailer](https://npm.io/package/nodemailer.md) ^6.10.1
- [emoji-regex](https://npm.io/package/emoji-regex.md) ^10.6.0
- [mime-detect](https://npm.io/package/mime-detect.md) ^1.3.0
- [listening-on](https://npm.io/package/listening-on.md) ^2.1.0
- [node-easynmt](https://npm.io/package/node-easynmt.md) ^1.2.0
- [populate-env](https://npm.io/package/populate-env.md) ^2.6.1
- [cookie-parser](https://npm.io/package/cookie-parser.md) ^1.4.7
- [better-sqlite3](https://npm.io/package/better-sqlite3.md) ^12.11.1
- [express-session](https://npm.io/package/express-session.md) ^1.19.0
- [@beenotung/tslib](https://npm.io/package/@beenotung/tslib.md) ^24.26.0
- [timezone-date.ts](https://npm.io/package/timezone-date.ts.md) ^2.1.0
- [better-sqlite3-proxy](https://npm.io/package/better-sqlite3-proxy.md) ^2.11.4
- [better-sqlite3-schema](https://npm.io/package/better-sqlite3-schema.md) ^3.1.10
- [open-google-translator](https://npm.io/package/open-google-translator.md) ^1.0.5

## Alternatives

- [@tsparticles/shape-image](https://npm.io/package/@tsparticles/shape-image.md) — 303.7K weekly downloads
- [@tsparticles/shape-line](https://npm.io/package/@tsparticles/shape-line.md) — 233.7K weekly downloads
- [stringify-attributes](https://npm.io/package/stringify-attributes.md) — 58.6K weekly downloads
- [mobile-drag-drop](https://npm.io/package/mobile-drag-drop.md) — 46.3K weekly downloads
- [@comunica/actor-rdf-parse-html](https://npm.io/package/@comunica/actor-rdf-parse-html.md) — 29.2K weekly downloads

## Recent versions

- 0.32.0 (latest) — 2026-09-28
- 0.31.6 — 2026-09-28
- 0.31.5 — 2026-09-28
- 0.31.4 — 2026-08-20
- 0.31.3 — 2026-06-17
- 0.31.2 — 2026-06-17
- 0.31.1 — 2026-03-11
- 0.31.0 — 2025-04-19
- 0.30.0 — 2025-04-19
- 0.29.3 — 2025-04-19
- 0.29.2 — 2025-04-19
- 0.29.1 — 2025-04-19
- 0.29.0 — 2025-04-19
- 0.28.0 — 2025-04-19
- 0.27.0 — 2025-04-18
- … 54 more at https://npm.io/package/auto-cms-server/versions

## README

# auto-cms

Auto turn any webpage into editable CMS without coding.

[![npm Package Version](https://img.shields.io/npm/v/auto-cms-server)](https://www.npmjs.com/package/auto-cms-server)

## Features

- [x] Click with `Ctrl` key or `Alt` key to show menu
- [x] Edit from web UI
  - text
  - link
  - image
  - audio
  - video
- [x] media management
  - [x] view
  - [x] upload
  - [x] delete
  - [ ] see which pages are using the media
  - [x] support image
  - [x] support video / audio
- [x] Reuse html template
  - For common header, footer, e.t.c.
- [ ] style editing
  - text alignment
  - text color
  - font size
  - font family
- [x] SEO settings
  - title
  - description
  - preview image
- [x] Save changes to file
- [x] Custom 404 layout (send `404.html` if exists, otherwise send `index.html`)
- [x] Multi-language support
  - convert 150+ languages with [node-EasyNMT](https://github.com/beenotung/node-EasyNMT)
  - convert traditional Chinese / simplified Chinese with [繁化姬 API](https://docs.zhconvert.org)
  - convert 104 languages with [open-google-translator](https://github.com/vidya-hub/open-google-translator)
- [x] Contact form
- [x] IFrame inlining
- [ ] Auto scan 404
- [x] Auto setup `.env` file
- [x] Robust
  - Correctly set Content-Type even when the filename of the HTML file is not ending with `.html`
  - Support next.js image proxy url (e.g. "/\_next/image?url=xxx&w=xx&q=xx")
  - Auto backup edits
  - View and restore from backups

## Enhancement

- [x] support editing element with multiple text nodes with br
- [x] cleanup html
  - remove duplicated script, style and css link caused by repeated runtime script injection
  - deduplicate class names
  - remove tracking scripts
  - fix elements position
    - move `title`, `meta`, `link` from `body` into `head`

## Usage

1. Login at `/auto-cms` to enable the edit menu
2. Navigate to any page on the site
3. Press `Ctrl` or `Alt` key while **clicking** (left or right click) to show the edit menu

Usage with installation to lock the version:

```bash
npm i -D auto-cms-server
npx auto-cms-server
```

Usage without installation:

```bash
npx -y auto-cms-server
```

## API

### Trailing Slash Redirect

When enabled with `AUTO_CMS_TRAILING_SLASH=true` (default `false`), a request
for a directory page without a trailing slash (e.g. `/about`) is redirected
(302) to the trailing-slash form (`/about/`).

This is useful when pages use relative links (e.g. `href="styles.css"` or
`fetch('i18n.json')`), which the browser resolves against the current URL.
Without the redirect, such links resolve one directory up when the URL has no
trailing slash.

It is opt-in because relative links are ambiguous: a link meant to be relative
to the parent directory (e.g. a shared `rss.xml`) would instead break once the
page URL gains a trailing slash. Only enable it for a site after verifying
that its relative links resolve correctly with the trailing slash.

### Multi Language

The `lang` cookie is used to specify the client-preferred language. Possible values are: `en`, `zh_cn`, `zh_hk`, `ja`, `ko`, and `ar`.

A `?lang=` query parameter (e.g. `?lang=zh_hk`) overrides the cookie and is saved into it.

Region variants are normalized (e.g. `en-US` → `en`, `zh-Hans` → `zh_cn`).

The cookie lifetime can be set in the environment variable `AUTO_CMS_LANG_COOKIE_DAYS` (default `365` days).

> **Tip:** Use an AI agent (e.g. [opencode](https://opencode.ai) with free model like Big Pickle) to improve translation JSON files. Built-in per-text translation has no context, so "Train" in a button alone → 火車 🚂 (vehicle), but with the full page the AI knows it means 訓練 🎯 (training).

The default value can be set in the environment variable `AUTO_CMS_DEFAULT_LANG`.

Below is example UI and code to show and set the language:

```html
<form id="langForm">
  Language:
  <label>
    <input type="radio" name="lang" value="en" />
    English
  </label>
  <label>
    <input type="radio" name="lang" value="zh_cn" />
    簡體中文
  </label>
  <label>
    <input type="radio" name="lang" value="zh_hk" />
    繁體中文
  </label>
  <label>
    <input type="radio" name="lang" value="ja" />
    日本語
  </label>
  <label>
    <input type="radio" name="lang" value="ko" />
    한국어
  </label>
  <label>
    <input type="radio" name="lang" value="ar" />
    عربي
  </label>
</form>
<script>
  {
    let lang = Object.fromEntries(document.cookie.split(';').map(s=>s.trim().split('='))).lang
    langForm.lang.value = lang
    langForm.lang.forEach(input => {
      input.addEventListener('change', event => {
        if (input.checked) {
          let maxAge = 365 * 24 * 60 * 60 // one year in seconds
          document.cookie = `lang=${input.value}; path=/; max-age=${maxAge}`
          location.reload()
        }
      })
    })
  }
</script>
```

### Submit Contact Form

```
Method: POST
Pathname: /contact
Content-Type: application/x-www-form-urlencoded or application/json
Accept: text/html or application/json
Body Fields:
- name
- email
- tel
- company_name
- business_nature
- remark
- extra
```

All body fields are optional.

If you submit additional fields in the request body, they will be stored as JSON in the `extra` field.

If the `Accept` is `application/json`, the response will be a json object with optional `error` string; otherwise the response will be a html page.

The response file can be configured in the env variable `SUBMIT_CONTACT_RESULT_PAGE`. If it is not specified, or specified as `default`, a simple html page will be response as below:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Submitted</title>
  </head>
  <body>
    <p>Your submission has been received.</p>
    ${error ? `
    <pre><code>${escapeHTML(error)}</code></pre>
    ` : ''}
    <p>Back to <a href="/">home page</a>.</p>
  </body>
</html>
```

If you want to implement custom form submission experience, you can do that with AJAX like below example:

```html
<form method="POST" action="/contact" onsubmit="submitContact(event)">
  <h1>Contact Form</h1>
  <div class="contact-form--field">
    <label>
      Nickname: <input type="text" name="name" autocomplete="nickname" />
    </label>
  </div>
  <div class="contact-form--field">
    <label>
      Email: <input type="email" name="email" autocomplete="email" />
    </label>
  </div>
  <div>
    <input type="submit" value="Submit" />
  </div>
  <div class="contact-form--submit-result"></div>
</form>
<script>
  async function submitContact(event) {
    let form = event.target
    let result = form.querySelector('.contact-form--submit-result')
    function showResult(text) {
      result.textContent = text
    }
    try {
      let formData = new FormData(form)
      let params = new URLSearchParams(formData)
      let body = params.toString()
      event.preventDefault()
      let res = await fetch('/contact', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'Accept': 'application/json',
        },
        body,
      })
      let json = await res.json()
      if (json.error) throw json.error
      showResult('Thank you. Your submission is received.')
    } catch (error) {
      showResult(String(error))
    }
  }
</script>
```

## License

This project is licensed with [BSD-2-Clause](./LICENSE)

This is free, libre, and open-source software. It comes down to four essential freedoms [[ref]](https://seirdy.one/2021/01/27/whatsapp-and-the-domestication-of-users.html#fnref:2):

- The freedom to run the program as you wish, for any purpose
- The freedom to study how the program works, and change it so it does your computing as you wish
- The freedom to redistribute copies so you can help others
- The freedom to distribute copies of your modified versions to others

---
_Source: https://npm.io/package/auto-cms-server · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
