This plugin lets you surface pads from an Etherpad instance inside Nextcloud and organize them there like other files.
- Each Etherpad pad is represented by a
.padfile inside Nextcloud .padfiles live in normal Nextcloud folders and integrate with sharing, trash, restore, and file organization- Opening a
.padfile in Nextcloud opens the linked Etherpad pad inside the native Nextcloud file viewer in an iframe - Protected and public pad modes
- Public folder/file share support for
.pad - Periodic sync from Etherpad into
.padsnapshots - Pad text is searchable through Nextcloud's full-text search, where that is set up
- A pad lives as long as its file: the trash keeps it, and it goes once the file is deleted for good
- A
.padfile whose pad is gone gets a new one from its snapshot: at once when it comes back from the trash, and offered when it is opened
Create a pad straight from the Files app with + New:
Open a .pad file and the linked Etherpad pad loads in the native Nextcloud viewer, with real-time collaboration and per-author colours:
Pads live alongside your other files and behave like any other Nextcloud file:
- Nextcloud
32.0.9to34and PHP8.1to8.5(see appinfo/info.xml) - On Nextcloud 33,
33.0.3or later: the Viewer of 33.0.0 to 33.0.2 lacks a fix, and a click on a.padfile downloads it instead of opening the pad - On Nextcloud 34, until a 34 release has the fix (planned for 34.0.6): the pads of a folder deleted for good, or of a trash emptied as a whole, stay in Etherpad. The consistency check on the admin page lists them for deletion, and a public one stays reachable by its link until then (docs/deleting-pads.md)
- Etherpad reachable from Nextcloud server
- Etherpad API key
- HTTPS for production deployments
- For protected pads in the embedded viewer: Nextcloud and Etherpad must allow iframe embedding and send compatible cookies.
- Recommended: run Nextcloud and Etherpad on the same registrable domain, for example
cloud.example.org+pad.example.org.
- Works with different Etherpad releases via API version detection (fallback supported).
- This plugin requires Etherpad API key mode (
authenticationMethod: "apikey"). - OAuth-only Etherpad setups are not supported by this plugin.
- Running this app and Ownpad at the same time is not supported.
- Both apps hook into
.padMIME/viewer handling, which leads to ambiguous file-type resolution and open-action conflicts.
- Both apps hook into
- Legacy Ownpad
.padfiles ([InternetShortcut]+URL=...) are automatically migrated to this app's binding-and-.padmodel the first time a user opens them. The migration branches on the source URL's origin and the embedded pad-id:- Same Etherpad server, free-form pad-id → re-bound as a managed public pad.
- Same Etherpad server, group pad-id (
g.<group>$<name>) → re-bound as a managed protected pad. - Different Etherpad server → converted to an external (
ext.*) public pad pointing at the original URL.
- Importing protected (group) pads is off by default. A legacy
.padnames the pad it opens, and for a group pad that name also picks the Etherpad group a session is minted for. Turn Allow importing protected pads from legacy Ownpad files on only where this Nextcloud is the only thing creating group pads on its Etherpad server. Public pads are imported either way, and a refused import leaves the.paduntouched. - If two Ownpad
.padfiles point at the same pad, only the first one to be opened gets the binding; the second is treated like a copied pad (no second binding row, the existing copy-of-a-pad flow handles open from there). Seedocs/legacy-ownpad-migration.mdfor the full state table, audit-log shape, and the security rationale.
The repository contains Vite-built frontend assets in js/. If you change
files in src/, rebuild before copying the app:
npm install
npm run buildPlace this repository as:
<nextcloud-root>/apps/etherpad_nextcloud
php occ app:enable etherpad_nextcloudEnabling the app registers the .pad type, its icon and its name with
Nextcloud. One step is not part of that, because only an occ command writes
the browser's copy of the icon mapping:
php occ maintenance:mimetype:update-jsIf .pad files still open or look wrong afterwards, see
Troubleshooting.
Go to:
Settings -> Administration -> Pads
and configure:
- Etherpad Base URL
- Etherpad API key (OAuth is not required; Etherpad API key auth is used)
- Etherpad API URL (optional; saved empty, it is set to the Base URL of that save and does not follow a later change of it)
- Etherpad session cookie domain (see below)
- Protected pads, Public pads: which pad types users may create (see below)
- Import of protected pads from legacy Ownpad files: off by default; leave it off unless this Nextcloud alone creates group pads on your Etherpad server (see docs/legacy-ownpad-migration.md)
- .pad file sync interval (seconds)
- Delete the Etherpad pad when its .pad file is deleted permanently
- Allow linking external public pads, and the External Etherpad host allowlist (optional; empty, it trusts every public HTTPS host)
- Trusted embed origins (optional)
Two settings control which pad types the app offers, both enabled by default:
- Protected pads — only people who can open the
.padfile in Nextcloud can open the pad. Created as Etherpad group pads, which require a session issued by Nextcloud. - Public pads — anyone with the pad link can open the pad, without a Nextcloud account.
Switching a type off hides its + New entries and refuses new pads of that
type on the API create endpoints. Pads that already exist keep working either
way.
Two paths fall back rather than refuse, so nothing gets stranded: a template
carrying the disabled mode, and the first-open initialisation of a .pad
that arrived outside the app (over WebDAV, say). Both create the pad in the
enabled mode; only with both types off is creation refused outright. Note
that this can widen access — a protected template becomes a public pad when
protected pads are off.
The setting decides what the app offers; it is not a hard boundary against a
user who crafts .pad files by hand. Recovering an orphaned .pad from its
snapshot still re-provisions the mode recorded in that file, because rescuing
existing content takes precedence.
Linking external public pads is governed separately by the external pad policy and is unaffected by these two settings.
If protected pads should open inside the Nextcloud viewer iframe:
-
Etherpad responses must allow embedding from your Nextcloud origin.
-
Reverse proxies must not enforce a conflicting
X-Frame-Optionspolicy. -
A
Content-Security-Policy: frame-ancestors ...header on the Etherpad side is the most reliable modern setup. Protected pads additionally require Nextcloud and Etherpad to share a parent domain, unless both run on the same host. Nextcloud issues the Etherpad session cookie from its own response, and a browser only accepts aDomain=that covers the host which sent it. A response fromcloud.example.orgcan therefore never set a cookie that reachespad.otherdomain.example, andSameSite=Nonedoes not change that — it governs whether an already valid cookie is sent cross-site, not which domain may be set. -
Same host for both: nothing to configure, a host-only cookie covers it.
-
Shared parent domain: the settings page derives the cookie domain and stores it with the first save, after which it no longer follows a change of either host. Etherpad's default
SameSite: Laxusually works. -
Unrelated registrable domains: protected pads cannot work with the current session model. If a reverse proxy already serves Etherpad under the Nextcloud domain, put that address in the base URL — pad links use it, so the cookie follows. Otherwise move one host, or switch protected pads off and offer public pads instead.
The check compares the Nextcloud host with the host in the base URL, which is exactly the address the browser loads a pad from. A proxy alias therefore counts as soon as it is the configured base URL, and does not need to be detected separately.
Example:
cloud.example.org+pad.example.org-> works, cookie domain.example.orgcloud.example.org+cloud.example.org-> works, host-only cookiecloud.example.org+pad.otherdomain.example-> not supported for protected pads
Test Etherpad connection reports this alongside the connection result, and the
settings page shows it without running the test.
This version needs Nextcloud 32.0.9 or later, and on Nextcloud 33 33.0.3
or later. On an older one, upgrade Nextcloud first, with the version of the
app you have, and the app after it.
- Replace app files in
apps/etherpad_nextcloud - Run:
php occ app:disable etherpad_nextcloud
php occ app:enable etherpad_nextcloudFor deployment, copy the app to apps/etherpad_nextcloud and exclude development-only content such as .git/, node_modules/, tests/, docs/, .phpunit.cache/, and local temp files. Keep the built js/ assets in the deployed app.
Frontend source lives in src/ and is built into js/ with Vite.
npm test
npm run buildPHP checks:
composer install --no-interaction
composer test:phpunit
composer psalmThe end-to-end suite is described in tests/e2e/README.md, the release flow in docs/release-process.md.
+ New -> New pad, then pick a template or the blank entry. The blank entry creates a protected pad; the Public pad tile creates a public one. The tile only appears when both pad types are enabled — with one switched off the blank entry already creates the only type on offer.- The Public pad from URL tile in the same picker links a pad on another Etherpad server. The picker asks for the pad's address itself, so the file is linked as it is created. The tile appears while external pads are allowed in the admin settings.
An administrator can add shared templates under
Settings -> Administration -> Pads: upload a .pad file and it appears as a
tile for everyone the app is enabled for. Its content becomes the new pad and
placeholders such as {{date}} are resolved on creation. The pad type of the
template is used when that type is enabled; otherwise the pad is created with
the type that is available, which can turn a protected template into a public
pad.
Shared templates live in the instance's appdata directory, not in any user's
files. They are covered by a full backup of the data directory, but not by a
per-user export, and they have no file versioning or trash — uploading over an
existing name asks for confirmation because the previous file is gone for good.
- Click
.padfile in Files app - Nextcloud's own viewer opens it, against the MIME type this app registers a viewer handler for
- One-way sync only: content is copied from Etherpad into the
.padfile snapshot. - No automatic reverse sync from
.padfile content back into Etherpad. - Automatic while viewer is open (interval from admin settings) and on viewer hide / page unload.
- Backend endpoint
POST /api/v1/pads/sync/{fileId}remains available for programmatic syncs. - Every visible viewer sends a sync each interval, whether or not anyone types, so V viewers make V divided by the interval requests a second; the server paces the syncs of one pad's viewers, not their number. On an instance with thousands of viewers at once, raise the interval with their number, and run Nextcloud as its admin manual recommends under load: Redis for file locking, and a database that takes at least as many connections as PHP has workers. Measured figures: docs/architecture.md ("Open viewers").
- With Nextcloud's full-text search installed (
fulltextsearch,files_fulltextsearchand a backend such asfulltextsearch_elasticsearch),.padfiles are found by their content and not only by their name. No Etherpad-specific search configuration is required. - Only the plain-text snapshot stored in the file is indexed, never the frontmatter, the pad id or the stored HTML, and Etherpad is not contacted. A pad becomes searchable with its first snapshot.
- A
.padfile moved to the Nextcloud trash keeps its pad; restored, it has the same pad back, with its history. - Once the file is deleted for good - from the trash, past it, or with the account that owned it - a background job deletes the pad within minutes, and tries again while Etherpad is unavailable.
- A file restored without a pad - one Etherpad lost, or one trashed under 1.1.0-beta.1 - gets a new pad from the snapshot in the file.
- What each way of deleting a file does to its pad: docs/deleting-pads.md.
-
Check the Nextcloud version (
php occ status): the Viewer of Nextcloud 32 before 32.0.9 and 33 before 33.0.3 opens no.padfile -
Ensure app is enabled:
php occ app:list | grep etherpad_nextcloud
-
Rebuild mimetype caches:
php occ maintenance:mimetype:update-jsphp occ maintenance:mimetype:update-db --repair-filecache
-
Give every
.padfile the app's MIME type again. The app does that when it is enabled or updated, until a run that found the.padmapping inconfig/mimetypemapping.jsonalready in place has done it, and again where the mapping was missing. A file that came in untyped after that - through a web server without the mapping, or from a long-running process started before it was written - is typed by this, once that process has been restarted or has ended:php occ config:app:delete etherpad_nextcloud pad_files_mime_versionphp occ maintenance:repair. That command skips an app limited to groups; for one, runphp occ app:enable etherpad_nextcloud -g <group>instead, with a-gfor every group it is enabled for, as the list replaces the one before. It takes group IDs, whichphp occ config:app:get etherpad_nextcloud enabledlists; an ID it does not know is dropped without a word, and with none it knows, the app is enabled for everyone. It shows nothing of the repair: oncephp occ config:app:get etherpad_nextcloud pad_files_mime_versionprints a value again, the repair has finished. Where it does not, run it again:app:enableshows none of the step's warnings, andnextcloud.loghas only an error that stopped it, such as a mapping it could not write.
With several web servers, every one needs the same mapping in its config directory: deploy it with the rest of the configuration, or run the second step on every web server with a config directory of its own. A web server that served files before it had the mapping left them untyped: once it has it, run both steps.
-
Reload browser with hard refresh
- In the file list the icon comes from the app's preview provider; run
occ preview:cleanupif an old one is cached - Search results carry the app's icon through a listener; everywhere else without a preview it is the MIME alias, which resolves to an icon Nextcloud ships. Re-run the MIME maintenance commands above
- Confirm the app's MIME repair step completed without warnings
- Hard refresh browser once
- Confirm Files app JS loaded without fatal errors in browser console
- Verify Etherpad API key and Etherpad auth mode
- Run admin
Test Etherpad connectioninSettings -> Administration -> Pads
- First check the domain relationship: Nextcloud and Etherpad must share a
parent domain, or run on the same host.
Test Etherpad connectionnames both hostnames when they do not. - Check Etherpad cookie settings:
- shared parent domain: default
SameSite: "Lax"is usually enough - across subdomains behind a proxy:
cookie.sameSite: "None"andtrustProxy: true
- shared parent domain: default
- HTTPS is required when using
SameSite=None - A shared parent that is a public suffix (
co.uk,github.io) does not count: browsers reject it as a cookie domain
- Check response headers on the Etherpad side and in the reverse proxy
- Remove or relax conflicting
X-Frame-Optionsrules - Prefer a
Content-Security-Policy: frame-ancestors 'self' https://your-nextcloud.exampleheader that explicitly allows your Nextcloud origin
- Usually caused by small editor/form font sizes inside Etherpad, not by the outer Nextcloud shell
- For the default
colibrisskin, adjustsrc/static/skins/colibris/pad.css, for example in the mobile@media (max-width: 768px)section, and raise the effective pad/editor font size from15pxto16px - Test in a private Safari tab or after clearing website data because Etherpad CSS is cached aggressively
- Architecture: docs/architecture.md
- When a pad is deleted: docs/deleting-pads.md
- API routes: docs/api-reference.md
- Etherpad integration details: docs/etherpad-integration.md
.padformat: docs/pad-format.md- I18N: docs/i18n.md
- UI icons: docs/ui-icons.md
- Release process: docs/release-process.md
- App code: AGPL-3.0-or-later (full text: LICENSES/AGPL-3.0.txt)
- Etherpad logo assets in
img/etherpad-icon-*.svg: Apache-2.0 (full text: LICENSES/Apache-2.0.txt)
- Thanks to the Ownpad project for the groundwork, ideas, and lessons learned that inspired and shaped this plugin.


