Skip to content
Guides— browse docs
On this page

The host

One object connects your creation to the platform — and it is the reason your tool runs unchanged in four different places.

Your creation will run in more places than you're picturing. The world editor. Inside somebody's card as an embedded block. On a published wiki that a stranger reads without an account. On a share link. In the examples on this page.

In three of those there is no session to authenticate with, and in one of them there is no server at all. So the moment you write fetch("/api/…") — or reach for a database client — your creation stops being portable and starts being a thing that works on exactly one surface.

The host is the alternative.

You will learn

  • What useHost() and useHostCapability() give you
  • Which capabilities you declare in needs, and which you get without asking
  • Why a missing capability throws instead of returning undefined

One object, many namespaces

src/tool.tsx
import { useHost, useHostCapability } from "@vvd/sdk"

const host = useHost()                    // the whole surface
const nav = useHostCapability("nav")      // one namespace, fail-fast

useHost() is the whole thing — reach for it when you're touching several namespaces. useHostCapability("nav") is the discoverable form, and the one you should usually use: if the host doesn't provide that namespace, it throws there, naming it, instead of handing you undefined to deref three frames deeper.

What's on it

NamespaceFor
identitywho the reader is, and whether they may edit
scopewhich world and project you're in
mediaresolving media ids to URLs, and letting the user pick or upload
navopening a document, building an href, pushing a route
refsmetadata about a referenced document
typesthe world's entity types (Character, Location, …)
worldthe world's live catalog — Reading the world
searchfull-text search across the world
embedsfetching and subscribing to embedded document content
documents · projectscreating and mutating world documents and app instances
runtimethe document connection itself — the SDK uses this; you don't

The two kinds of capability

This distinction saves you a lot of confusion:

Capabilities you declare. world, search, documents, projects, publish, access — these carry world data or change things, so they're gated by an install grant and belong in needs and capabilities in your manifest.

Capabilities you inherit. Icons, audio, context menus, drag-and-drop, analytics, theme, reactions — these are cross-cutting. Every host provides them, they carry no world content, and they never go in needs. Under a host that omits one, they degrade quietly rather than throwing: a sound doesn't play, a right-click falls through to the browser's own menu.

src/tool.tsx
import { HostIcon, useHost } from "@vvd/sdk"

// Inherited — no declaration, no consent prompt, works everywhere.
<HostIcon icon="compass" size={16} />
useHost().analytics?.track("creature_logged", { threat: 3 })

Reading the host, live

The tool below reads three things straight off the host: the icon engine, identity, and whether this surface allows editing. Everything it shows is real — this docs page is another host.

A tool reading its host
Starting the example…

Swap the host and every one of those values changes, without the tool changing at all. That is the entire argument for the seam.

needs is the list of host capability namespaces your code calls by name:

vvd.json
{
  "needs": ["world", "nav"],
  "capabilities": { "readsWorld": true }
}

When someone installs your creation, the platform derives the consent screen from that declaration — so what the user is asked to approve can't drift from what your code actually calls. When it mounts, the host it gets is built with only the granted namespaces on it, so a missing one throws at the boundary, naming the capability — a creation cannot quietly exceed its consent. Reading the world shows the failure, and the fix, in full.

The rule, stated once

Reach the platform through the host, never around it.

No fetch to vvd's API from inside a creation. No database client. No localStorage for anything that ought to be shared. If you find yourself wanting one of those, the thing you want is almost always a host capability — and if it genuinely isn't, it's a server endpoint in your project's api/ folder, which is the one sanctioned way to talk to the outside world.

Two practical consequences worth internalising:

  • Media never gets a hardcoded URL. Store a media id, resolve it through host.media. URLs expire, and private media is only reachable through the platform's own door.
  • Permission is never computed by you. context.canEdit and host.identity already fold in membership, sharing, published read-only views, and live permission changes.

When it doesn't work

useHost() must be used within a <HostProvider>. You're rendering your component outside the tool mount — usually in a test. Wrap it: <HostProvider services={createFakeHost()}>. See Testing.

Host capability "x" is not provided by this host. Either the install wasn't granted it (add it to capabilities / needs and re-save), or you're on a surface that genuinely doesn't have it — a logged-out published page has no search, for example. Decide which, and either declare it or handle its absence.

An icon renders as a blank square. The id didn't resolve. Icon ids are namespaced ("tabler:sword"); bare lucide names like "compass" still resolve for compatibility. An unknown id falls back rather than throwing.

host.analytics?.track seems to do nothing. It's fire-and-forget, and there's no analytics sink on every surface. That's why it's optional-chained. Events land in your creation's Analytics tab, not in the console.

Recap

  • useHost() is the one seam between your creation and the platform — media, navigation, identity, the world.
  • A tool never calls fetch or touches a database, which is what lets it run unchanged in four different places.
  • needs declares the capabilities you call by name, so a missing one fails loudly instead of as an undefined.
  • Cross-cutting capabilities — icons, audio, menus, analytics — are inherited, never declared, and degrade quietly.

Next steps

  • Testing — swapping the host for a fake one, which is how every example on this site runs.