Search selfstore
v1.8.21

WebDAV home

WebDAV is the self-hoster’s durable home: a Nextcloud or ownCloud instance, a NAS, any server speaking the protocol. The user brings a URL and credentials; the store re-writes one encrypted backup file there. For an audience that already refuses Big-Tech clouds, this is the home that makes your app feel built for them.

One call

const outcome = await store.connectWebdav({
  url: 'https://cloud.example.com/remote.php/dav/files/ada/backups/my-app.zip',
  username: 'ada',
  password: appPassword,
});
// 'started' | 'merged' | 'manual' | 'cancelled'

Two distinct passwords can be in play, worth naming clearly in your UI: the server credential above (how the store reaches WebDAV), and the backup password that encrypts the file itself (store.protect(...), or connectWebdav(config, { password }) for an already-encrypted file). Users may set either, both, or neither.

Security posture

  • https is enforced. Basic-auth credentials over plain http are refused outright (loopback aside), so a misconfigured URL fails fast instead of leaking a credential.
  • Credentials are sealed at rest. The server config persists locally under a non-extractable device key, which defeats casual inspection of IndexedDB (not code running in your origin). The threat model states both halves.
  • Prefer app passwords. Nextcloud and ownCloud issue per-application passwords (Settings, Security); recommend those over the account password.

A WebDAV share link can also act as a peer: another person publishes their copy read-only, you attach it, and the merge folds their changes in. That is how a small group shares a store read-write without anyone granting write access, and without Google. The peers guide has the model; the helper is webdavTarget.peer({ url }) from selfstore/advanced.

On the advanced store

Driving createLocalStore yourself:

import { webdavTarget } from 'selfstore/advanced';

const target = await webdavTarget.connect({
  kv: cache.kv,
  config: { url, username, password },
});
await store.attachTarget(target, { password: backupPassword });