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
httpare 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.
Sharing: a read-only link as a sync input
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 });