No description
Find a file
John McLear cd793294c4
fix(chat): icon click, disabled toggles, username layout (#7590, #7592, #7593) (#7597)
* fix(userlist): stop username input from overlapping the Log out button

Fixes #7593. In the pad's Users popup, #myusernameform had no width
set and the <input id="myusernameedit"> inside it took its natural
content width, pushing past the Log out button and making the button
overflow the popup at common widths.

Constrain #myusernameform to 75px and make the input fill its
container with box-sizing: border-box so the text field stays inside
the form and the Log out button sits visibly next to it rather than
getting covered or clipped off-screen.

Low-risk, CSS-only change. No test plan beyond visual verification
because the affected control is in the users popup UI.

* fix(chat): bottom-align titlebar controls; restore chat icon click (#7590)

Two regressions from the #7584 a11y refactor of the chat widget,
both pure-CSS fixes scoped to the chat panel.

1. Title bar — `<a>` → `<button>` for #titlecross/#titlesticky kept the
   `float: right` layout, but a `<button>`'s box is only as tall as its
   glyph, so the small `−` and `█` controls floated at the *top* of the
   44px title bar instead of sitting on the title's baseline as the
   anchors did. Switch #titlebar to a flex row with `align-items:
   flex-end`, give #titlelabel `flex: 1` to push the controls to the
   right edge, and use `order: 1/2` to keep the historical visual order
   `[█] [−]` (which `float: right` previously produced from reverse
   source order).

2. Chat-icon corner widget — `<div>` → `<button id="chaticon">` exposes
   the inner `<span class="buttonicon">` to the global `.buttonicon`
   rule's `display: flex; position: relative; align-items/justify-content:
   center;`. The existing override only reset `display`, leaving the
   span as a positioned flex item that, in some layouts, sat over the
   button's hit surface and swallowed clicks. Reset the remaining flex
   properties and add `pointer-events: none` so clicks always reach the
   `<button>`'s own click handler — preferred over weakening the global
   .buttonicon rule, which the toolbar relies on for icon centring.

Visual-only / behaviour-fix, no markup or JS changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(settings): grey disabled chat option labels (#7592)

When "Disable chat" is ticked in the Settings dialog, refreshMyViewControls()
already sets `disabled` on `#options-stickychat` and `#options-chatandusers`,
but the browser only greys the checkbox itself — the adjacent `<label>`
keeps its normal colour, so the row still looks interactive even though
clicks are no-ops.

Add a popup-scoped rule that follows the existing convention used for
disabled `.nice-select` controls (`color: #999; cursor: not-allowed`) so
any disabled checkbox or radio in a settings popup matches its label to
the disabled state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* revert(userlist): drop username input width cap (#7593 review)

The width:75px on #myusernameform and width:100%/box-sizing on
#myusernameedit from a55436ca0 were guarding against an overlap with
a "Log out" button — but no Log out button exists in vanilla
etherpad-lite (the original report came from a setup with a plugin
that adds one). Without that button visible, the cap just makes the
default username field unnecessarily narrow.

Restore #myusernameform to just `margin-left: 10px` and drop the
forced width on the input. If the overlap reappears in a real plugin
setup it should be re-fixed there (or with a more targeted rule that
only kicks in when a logout button is actually present).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chat): keep titlesticky at top of title bar (#7590 review)

The previous pass bottom-aligned both corner controls via
align-items: flex-end on #titlebar. That correctly placed the close
button (#titlecross) on the title's baseline, but it also dragged the
much smaller "stick to screen" button (#titlesticky) down to the same
baseline — visibly far below where it sat in the original layout.

Switch to per-control align-self so each lands where it should:
  - #titlesticky → align-self: flex-start  (top, where it always was)
  - #titlecross  → align-self: flex-end    (bottom, on the title's baseline)
  - #titlelabel  → align-self: center      (don't stretch the heading)

Drop align-items from #titlebar so the defaults don't override these.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* revert(chat): restore original #titlebar layout (#7590 review)

Both attempted CSS layouts for the title bar (full flex with
align-items: flex-end, then per-control align-self) ended up looking
worse than the original in review. Drop all the #titlebar / #titlelabel
/ #titlecross / #titlesticky changes from 905294d5b and f37da9a62 and
restore the pre-existing float-based layout. The chat panel ships with
its original visuals; we'll revisit #7590 separately if needed.

Keeps the chat-icon click fix from 905294d5b (#chaticon .buttonicon
flex/pointer-events reset) and the focus-visible additions for the
title-bar buttons.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chat): clear inline display:none in chat.show()

When the user disables chat in settings, applyShowChat(false) calls
\`$('#chatbox').hide()\` which sets the chatbox's inline display to
\`none\`. Re-enabling chat doesn't undo that — it only re-shows the
icon. Then clicking the icon runs chat.show(), which adds the
\`.visible\` class but only flips visibility, not display, so the
chatbox stays hidden by the lingering inline style and the chat
appears not to open.

Clear the inline display in chat.show() before adding the .visible
class so the box becomes visible regardless of how it got hidden.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(colibris): align username gap; grey unchecked-disabled toggles

users.css: change #myusernameform margin-left from 35px to 10px to
match the base popup_users.css. The 35px value was chosen for the
sticky chatAndUsers layout, but for the standalone Users popup it
opens an unnecessarily wide gap between the colour swatch and the
username field. (#7593 review)

form.css: drop the \`:checked\` qualifier from the disabled toggle
visual rule so unchecked-but-disabled toggles also dim. Without this,
"Chat always on screen" / "Show Chat and Users" stayed fully bright
when "Disable chat" was ticked even though the underlying inputs were
disabled. Fixes #7592 in the colibris skin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chat): simple flex titlebar — CHAT      _  []

Single flex row, vertically centred via align-items: center. Title
takes the remaining width with flex: 1; the two corner controls fall
in at the right edge in source order (titlecross then titlesticky),
giving the intended visual: minus on the left, sticky on the right.

Drops `float: right` from the controls, `display: inline` from the
heading, and the prior `padding-top: 2px` hack on titlesticky (flex
alignment handles the vertical position now).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chat): titlebar uses underscore for minimize; symmetric padding

- Replace \`&minus;\` with \`_\` in #titlecross. The minus glyph sits at
  the centre of its em-box and read as a hyphen mid-row when the row
  was vertically centred; \`_\` sits at the bottom of its em-box and
  reads as a proper minimize indicator.
- Even out #titlebar horizontal padding to 9px and drop the asymmetric
  \`margin-left: 4px\` on #titlelabel so CHAT on the left and the
  sticky button on the right are the same distance from the bar's
  edges.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chat): lift #titlecross underscore 5px

The \`_\` glyph renders at the bottom of its em-box, so even with the
title bar's flex \`align-items: center\` it sits noticeably below the
CHAT baseline. Lift it with \`transform: translateY(-5px)\` (doesn't
affect flex layout calculations) so the underscore reads at roughly
the same vertical line as the title.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(chat): cover #7590 / #7592 / #7593 fixes

Adds Playwright frontend specs for the changes in this PR:

chat.spec.ts
  - chat icon click reveals chatbox after disable→enable cycle
    (regression: chat.show() must clear inline display:none)
  - title bar lays out as a centred flex row with underscore minimize
    (covers display, align-items, label flex:1, no float, translateY
    lift, and visual padding symmetry via rendered geometry)
  - chat icon click reliably opens the chat box (#chaticon .buttonicon
    pointer/flex reset)

pad_settings.spec.ts
  - disabling chat disables and visually greys the dependent chat
    toggles (#7592 — checks input :disabled state and label opacity)

change_user_name.spec.ts
  - #myusernameform has 10px left margin and is not width-capped
    (#7593 review — colibris margin alignment, no input width cap)

Padding symmetry asserted via rendered rect deltas rather than the
CSS literal, since colibris ships its own #titlebar padding override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 09:56:38 +01:00
.github Revert "feat(packaging): add Debian (.deb) build via nfpm with systemd unit (…" (#7582) 2026-04-22 18:47:13 +01:00
admin fix(admin): restore i18n on /admin (issue #7586) (#7602) 2026-04-26 03:07:45 +01:00
bin bump version 2026-04-23 21:08:25 +00:00
doc ci: publish Docker images to GHCR alongside Docker Hub (#7569) 2026-04-20 10:19:11 +01:00
docs/superpowers fix(a11y): dialog semantics, focus management, icon labels, html lang (#7584) 2026-04-24 03:04:18 +01:00
local_plugins Fix installation of local plugins 2025-04-05 15:31:36 +02:00
src fix(chat): icon click, disabled toggles, username layout (#7590, #7592, #7593) (#7597) 2026-04-26 09:56:38 +01:00
ui feat!: replace Abiword with LibreOffice and add DOCX export (#7539) 2026-04-19 09:08:22 +01:00
var Use temporary directory for esbuild 2024-11-05 20:44:27 +01:00
.dockerignore Added new command to setup etherpad. Fixed Dockerfile 2024-07-23 17:43:32 +02:00
.editorconfig Added editorconfig configuration (#6347) 2024-04-23 07:04:30 +02:00
.env.default Fixed docker compose (#6337) 2024-04-17 20:50:21 +02:00
.env.dev.default Fixed docker compose (#6337) 2024-04-17 20:50:21 +02:00
.gitattributes tests: Microsoft Windows Server CI (#4791) 2021-02-18 18:49:43 +00:00
.gitignore feat!: replace Abiword with LibreOffice and add DOCX export (#7539) 2026-04-19 09:08:22 +01:00
.npmrc fix: use hardlink package-import-method so the Docker build works on ZFS (#7342) (#7533) 2026-04-17 12:03:21 +01:00
.pr_agent.toml docs: add AGENTS.MD for AI and developer guidance (#7348) 2026-03-04 21:03:58 +00:00
AGENTS.MD chore: Rename some occurences of etherpad-lite to etherpad (#7552) 2026-04-19 16:53:57 +02:00
best_practices.md chore: Rename some occurences of etherpad-lite to etherpad (#7552) 2026-04-19 16:53:57 +02:00
CHANGELOG.md chore: added release notes for 2.7.1 2026-04-23 23:05:56 +02:00
CONTRIBUTING.md chore: Rename some occurences of etherpad-lite to etherpad (#7552) 2026-04-19 16:53:57 +02:00
docker-compose.dev.yml Update docker-compose.dev.yml (#6654) 2024-09-13 08:08:56 +02:00
docker-compose.yml security: run Etherpad container as non-root user (fixes #7134) (#7287) 2026-01-10 20:28:58 +01:00
Dockerfile chore: Rename some occurences of etherpad-lite to etherpad (#7552) 2026-04-19 16:53:57 +02:00
LICENSE Update LICENSE 2013-06-26 23:34:35 +01:00
package.json bump version 2026-04-23 21:08:25 +00:00
pnpm-lock.yaml fix(admin): restore i18n on /admin (issue #7586) (#7602) 2026-04-26 03:07:45 +01:00
pnpm-workspace.yaml fix: downgrade ERR_PNPM_IGNORED_BUILDS to a warning (#7527) 2026-04-16 15:20:00 +01:00
README.md ci: publish Docker images to GHCR alongside Docker Hub (#7569) 2026-04-20 10:19:11 +01:00
SECURITY.md Create SECURITY.md 2020-07-07 10:36:17 +01:00
settings.json.docker Add creator-owned pad settings defaults (#7545) 2026-04-19 11:13:44 +01:00
settings.json.template chore: Rename some occurences of etherpad-lite to etherpad (#7552) 2026-04-19 16:53:57 +02:00
tests restructure: move bin/ and tests/ to src/ 2021-02-04 17:15:08 -05:00

Etherpad — the editor for documents that matter

Real-time collaborative editing where authorship is the default, your server is the only server, and you decide what AI (if any) ever touches your text.

Demo Etherpad Animated Jif

About

Etherpad is a real-time collaborative editor for documents that matter.

Every keystroke is attributed to its author. Every revision is preserved. The timeslider lets you scrub through a document's entire history, character by character. Author colours make collaboration visible at a glance — not buried in a menu.

Etherpad runs on your server, under your governance. No telemetry. No upsells. AI is a plugin you install, pointed at the model you choose, running on infrastructure you control — not a feature decided for you in a boardroom you weren't in.

The code is Apache 2.0. The data format is open. It scales to thousands of simultaneous editors per pad. Translated into 105 languages. Extended through hundreds of plugins. Used by Wikimedia, governments, public-sector institutions, and self-hosters worldwide since 2009.

Full data export is built in. The history is yours.

Try it out

Try out a public Etherpad instance

Project Status

Etherpad has been doing the same thing — well — since 2009. No pivots, no acquisitions, no enshittification. Maintained by a small volunteer team.

We are actively looking for maintainers. If you have experience with Node.js, real-time systems, or institutional collaboration tooling and you want to work on infrastructure that thousands of organisations quietly depend on, please open an issue or contact John McLear.

Code Quality

Code Quality

Testing

Backend tests Simulated Load Rate Limit Docker file Frontend admin tests powered by Sauce Labs Frontend tests powered by Sauce Labs Sauce Test Status Windows Build

Engagement

Docker Pulls Discord Etherpad plugins Languages Translation Coverage

Who uses Etherpad

For more than a decade, Etherpad has quietly underpinned the documents that matter to:

  • Wikimedia Foundation — collaborative drafting across editor communities.
  • Public-sector institutions across the EU — including organisations that legally cannot use US-cloud SaaS for sovereignty and GDPR reasons.
  • Universities and schools worldwide — including jurisdictions where Google Workspace is no longer permitted in education.
  • Civic-tech and democratic-deliberation projects — citizen assemblies, participatory budgeting, public consultations.
  • Newsrooms and investigative journalism teams — where authorship and editing history matter for legal and editorial integrity.
  • Tens of thousands of self-hosted instances worldwide, run by IT teams who chose Etherpad because it is theirs.

If your organisation runs Etherpad and would be willing to be listed publicly, please add it to the wiki.

Installation

Quick install (one-liner)

The fastest way to get Etherpad running. Requires git and Node.js >= 20.

macOS / Linux / WSL:

curl -fsSL https://raw.githubusercontent.com/ether/etherpad/master/bin/installer.sh | sh

Windows (PowerShell):

irm https://raw.githubusercontent.com/ether/etherpad/master/bin/installer.ps1 | iex

Both installers clone Etherpad into ./etherpad-lite, install dependencies, and build the frontend. When the installer finishes, run:

cd etherpad-lite && pnpm run prod

Then open http://localhost:9001.

To install and start in one go:

# macOS / Linux / WSL
ETHERPAD_RUN=1 sh -c "$(curl -fsSL https://raw.githubusercontent.com/ether/etherpad/master/bin/installer.sh)"
# Windows
$env:ETHERPAD_RUN=1; irm https://raw.githubusercontent.com/ether/etherpad/master/bin/installer.ps1 | iex

Docker-Compose

The official image is published to both Docker Hub (etherpad/etherpad) and GitHub Container Registry (ghcr.io/ether/etherpad) with identical tags. Use whichever suits your environment; GHCR avoids Docker Hub's anonymous pull rate limits.

services:
  app:
    user: "0:0"
    image: etherpad/etherpad:latest  # or: ghcr.io/ether/etherpad:latest
    tty: true
    stdin_open: true
    volumes:
      - plugins:/opt/etherpad-lite/src/plugin_packages
      - etherpad-var:/opt/etherpad-lite/var
    depends_on:
      - postgres
    environment:
      NODE_ENV: production
      ADMIN_PASSWORD: ${DOCKER_COMPOSE_APP_ADMIN_PASSWORD:-admin}
      DB_CHARSET: ${DOCKER_COMPOSE_APP_DB_CHARSET:-utf8mb4}
      DB_HOST: postgres
      DB_NAME: ${DOCKER_COMPOSE_POSTGRES_DATABASE:-etherpad}
      DB_PASS: ${DOCKER_COMPOSE_POSTGRES_PASSWORD:-admin}
      DB_PORT: ${DOCKER_COMPOSE_POSTGRES_PORT:-5432}
      DB_TYPE: "postgres"
      DB_USER: ${DOCKER_COMPOSE_POSTGRES_USER:-admin}
      # For now, the env var DEFAULT_PAD_TEXT cannot be unset or empty; it seems to be mandatory in the latest version of etherpad
      DEFAULT_PAD_TEXT: ${DOCKER_COMPOSE_APP_DEFAULT_PAD_TEXT:- }
      DISABLE_IP_LOGGING: ${DOCKER_COMPOSE_APP_DISABLE_IP_LOGGING:-false}
      SOFFICE: ${DOCKER_COMPOSE_APP_SOFFICE:-null}
      TRUST_PROXY: ${DOCKER_COMPOSE_APP_TRUST_PROXY:-true}
    restart: always
    ports:
      - "${DOCKER_COMPOSE_APP_PORT_PUBLISHED:-9001}:${DOCKER_COMPOSE_APP_PORT_TARGET:-9001}"

  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: ${DOCKER_COMPOSE_POSTGRES_DATABASE:-etherpad}
      POSTGRES_PASSWORD: ${DOCKER_COMPOSE_POSTGRES_PASSWORD:-admin}
      POSTGRES_PORT: ${DOCKER_COMPOSE_POSTGRES_PORT:-5432}
      POSTGRES_USER: ${DOCKER_COMPOSE_POSTGRES_USER:-admin}
      PGDATA: /var/lib/postgresql/data/pgdata
    restart: always
    # Exposing the port is not needed unless you want to access this database instance from the host.
    # Be careful when other postgres docker container are running on the same port
    # ports:
    #   - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
  plugins:
  etherpad-var:

Requirements

Node.js >= 20.

Windows, macOS, Linux

  1. Download the latest Node.js runtime from nodejs.org.
  2. Install pnpm: npm install -g pnpm (Administrator privileges may be required).
  3. Clone the repository: git clone -b master
  4. Run pnpm i
  5. Run pnpm run build:etherpad
  6. Run pnpm run prod
  7. Visit http://localhost:9001 in your browser.

Docker container

Find here information on running Etherpad in a container.

Plugins

Etherpad is very customizable through plugins.

Basic install

Full Features

Available Plugins

For a list of available plugins, see the plugins site.

Plugin Installation

You can install plugins from the admin web interface (e.g., http://127.0.0.1:9001/admin/plugins).

Alternatively, you can install plugins from the command line:

cd /path/to/etherpad-lite
pnpm run plugins i ep_${plugin_name}

Also see the plugin wiki article.

Suggested Plugins

Run the following command in your Etherpad folder to get all of the features visible in the above demo gif:

pnpm run plugins i \
  ep_align \
  ep_comments_page \
  ep_embedded_hyperlinks2 \
  ep_font_color \
  ep_headings2 \
  ep_markdown \
  ep_webrtc

For user authentication, you are encouraged to run an OpenID Connect identity provider (OP) and install the following plugins:

  • ep_openid_connect to authenticate against your OP.
  • ep_guest to create a "guest" account that has limited access (e.g., read-only access).
  • ep_user_displayname to automatically populate each user's displayed name from your OP.
  • ep_stable_authorid so that each user's chosen color, display name, comment ownership, etc. is strongly linked to their account.

Upgrade Etherpad

Run the following command in your Etherpad folder to upgrade

  1. Stop any running Etherpad (manual, systemd ...)
  2. Get present version
git -P tag --contains
  1. List versions available
git -P tag --list "v*" --merged
  1. Select the version
git checkout v2.2.5
git switch -c v2.2.5
  1. Upgrade Etherpad
./bin/run.sh
  1. Stop with [CTRL-C]
  2. Restart your Etherpad service

Next Steps

Tweak the settings

You can modify the settings in settings.json. If you need to handle multiple settings files, you can pass the path to a settings file to bin/run.sh using the -s|--settings option: this allows you to run multiple Etherpad instances from the same installation. Similarly, --credentials can be used to give a settings override file, --apikey to give a different APIKEY.txt file and --sessionkey to give a non-default SESSIONKEY.txt. Each configuration parameter can also be set via an environment variable, using the syntax "${ENV_VAR}" or "${ENV_VAR:default_value}". For details, refer to settings.json.template. Once you have access to your /admin section, settings can be modified through the web browser.

If you are planning to use Etherpad in a production environment, you should use a dedicated database such as mysql, since the dirtyDB database driver is only for testing and/or development purposes.

Secure your installation

If you have enabled authentication in users section in settings.json, it is a good security practice to store hashes instead of plain text passwords in that file. This is especially advised if you are running a production installation.

Please install ep_hash_auth plugin and configure it. If you prefer, ep_hash_auth also gives you the option of storing the users in a custom directory in the file system, without having to edit settings.json and restart Etherpad each time.

Customize the style with skin variants

Open http://127.0.0.1:9001/p/test#skinvariantsbuilder in your browser and start playing!

Skin Variant

Helpful resources

The wiki is your one-stop resource for Tutorials and How-to's.

Documentation can be found in doc/.

Development

Things you should know

You can debug Etherpad using bin/debugRun.sh.

You can run Etherpad quickly launching bin/fastRun.sh. It's convenient for developers and advanced users. Be aware that it will skip the dependencies update, so remember to run bin/installDeps.sh after installing a new dependency or upgrading version.

If you want to find out how Etherpad's Easysync works (the library that makes it really realtime), start with this PDF (complex, but worth reading).

Contributing

Read our Developer Guidelines

HTTP API

Etherpad is designed to be easily embeddable and provides a HTTP API that allows your web application to manage pads, users and groups. It is recommended to use the available client implementations in order to interact with this API.

OpenAPI (previously swagger) definitions for the API are exposed under /api/openapi.json.

jQuery plugin

There is a jQuery plugin that helps you to embed Pads into your website.

Plugin Framework

Etherpad offers a plugin framework, allowing you to easily add your own features. By default your Etherpad is extremely light-weight and it's up to you to customize your experience. Once you have Etherpad installed you should visit the plugin page and take control.

Translations / Localizations (i18n / l10n)

Etherpad comes with translations into all languages thanks to the team at TranslateWiki.

If you require translations in plugins please send pull request to each plugin individually.

FAQ

Visit the FAQ.

Get in touch

The official channel for contacting the development team is via the GitHub issues.

For responsible disclosure of vulnerabilities, please write a mail to the maintainers (a.mux@inwind.it and contact@etherpad.org).

Join the official Etherpad Discord Channel.

License

Apache License v2