Every Drive in Nomad is an Autobase: multi-writer-capable, with a URL that stays the same for the drive’s entire life. Multiple Writers can append; reads are linearised across all writers into an eventually-consistent view.

“Collaborative” is a policy flag, not a separate kind of drive. A drive is locked (single-writer) by default and can be unlocked without changing its URL — so a drive can start private and open up to collaborators later. All of this goes through the single nomad.fs API.


Creating a drive

// Locked / single-writer (the default)
var drive = await nomad.fs.createDrive({ title: 'My Notes' })

// …or collaborative from the start (accepts writer requests)
var drive = await nomad.fs.createCollaborativeDrive({
  title: 'My Forum',
  type: 'walled.garden/forum'
})
console.log(drive.url) // hyper://abc123.../ — permanent

// Unlock an existing drive later — SAME URL
await nomad.fs.configure(drive.url, { collaborative: true })

Anyone with the URL can read. Write access must be explicitly granted (below).


Reading and writing

// Write a file (only Writers can do this)
await drive.put('/posts/hello.json', JSON.stringify({
  type: 'walled.garden/post',
  title: 'Hello world',
  body: 'My first post!',
  createdAt: new Date().toISOString()
}))

// Read it back (get(path, 'json') parses for you)
var post = await drive.get('/posts/hello.json', 'json')

// List files
var entries = await drive.list('/posts/')

Validate data before writing with nomad.schemas:

var result = nomad.schemas.validate('walled.garden/post', postData)
if (!result.success) throw new Error(result.error)
await drive.put('/posts/my-post.json', JSON.stringify(result.data))

Inviting Writers

There are two ways to grant write access: invite links (owner-initiated) and access requests (stranger-initiated). Both go through the same approval flow, and either one unlocks the drive.

// Owner creates a reusable invite link (this unlocks the drive so requests are accepted)
var inviteUrl = await drive.createInvite({ multiUse: true })
// Share inviteUrl with whoever you want to invite

The recipient opens the invite URL in Nomad, which triggers:

var { writerKey } = await nomad.fs.claimInvite(inviteUrl, {
  profileUrl: 'hyper://my-profile/'
})

That creates a pending request on the owner’s side. The owner approves it:

var requests = await drive.listRequests()
// [{ writerKey: '...', profileUrl: 'hyper://...', requestedAt: '...' }]

await drive.approveRequest(requests[0].writerKey)

Stranger-initiated: access request

// Anyone can request write access to a collaborative (unlocked) drive
var { writerKey } = await nomad.fs.requestAccess(driveUrl, {
  profileUrl: 'hyper://my-profile/'
})
// The owner sees this in listRequests()

Requests are never auto-accepted — the owner is always the gate. A locked drive ignores requests entirely (it doesn’t even advertise the request channel), so check (await nomad.fs.getInfo(url)).collaborative if you need to know whether a drive is open.


Resolving author identity

Each write can carry the author’s writerKey. To show a name and avatar, resolve their Profile Drive:

var writers = await drive.listWriters()
// [{ writerKey: '...', profileUrl: 'hyper://...' }]

for (const w of writers) {
  var profile = await nomad.fs.getInfo(w.profileUrl)
  console.log(profile.title, profile.description)
}

Revoking access

// Remove a writer (owner only)
await drive.removeWriter(writerKey)

Full example: a forum post

const drive = nomad.fs.drive(location.href)

async function createPost(title, body, category) {
  var myProfileUrl = await getMyProfileUrl() // read from address book
  var writers = await drive.listWriters()
  var myWriter = writers.find(w => w.profileUrl === myProfileUrl)

  var post = {
    type: 'walled.garden/post',
    title,
    body,
    category,
    createdAt: new Date().toISOString(),
    author: { url: myProfileUrl, writerKey: myWriter?.writerKey }
  }

  var valid = nomad.schemas.validate('walled.garden/post', post)
  if (!valid.success) throw new Error(valid.error)

  var slug = `${Date.now()}-${title.toLowerCase().replace(/\s+/g, '-')}`
  await drive.put(`/posts/${slug}.json`, JSON.stringify(valid.data, null, 2))
}