Create a structured documentation site with sidebar navigation, full-text search, and a custom domain.
In this guide you will build a structured documentation site powered by Lifestream Vault — the same stack used for professional product docs, API references, and knowledge bases.
By the end you will have:
v1/, v2/ directoriesdocs.example.com)You can manage everything through the Lifestream Vault web UI, the TypeScript SDK, or the lsvault CLI.
Prerequisites
A vault is an isolated document container that maps to a single published site. Give your docs vault a descriptive name and a clean slug — the slug becomes part of every public URL.
Via the UI: open Settings → Vaults → New Vault, enter a name (e.g. Product Docs) and a slug (e.g. product-docs), then click Create.
Or use the SDK or CLI:
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
'you@example.com',
'your-password',
);
// The slug is generated from the name — you do not pass it.
const vault = await client.vaults.create({
name: 'Product Docs',
});
console.log('Vault ID:', vault.id);
console.log('Vault slug:', vault.slug);Every document in a vault is identified by its path — a forward-slash-delimited string ending in .md. Organising documents into folders gives your site a clear hierarchy that is reflected in the sidebar navigation when the vault is published.
A recommended layout for a documentation site:
product-docs/
├── index.md # Site home / landing page
├── getting-started.md # Quick-start for new users
├── installation.md # Installation instructions
├── api-reference/
│ ├── index.md # API overview
│ ├── authentication.md
│ ├── endpoints.md
│ └── errors.md
├── guides/
│ ├── index.md # Guides overview
│ ├── advanced-config.md
│ └── integrations.md
└── changelog.md # Version history
The published sidebar mirrors this tree structure exactly. Folders appear as collapsible groups; the index.md inside each folder becomes the group's landing page.
Naming conventions:
api-reference, getting-started)// Create the initial document scaffold via the SDK
const docPaths = [
'index.md',
'getting-started.md',
'installation.md',
'api-reference/index.md',
'api-reference/authentication.md',
'api-reference/endpoints.md',
'guides/index.md',
'guides/advanced-config.md',
'changelog.md',
];
for (const docPath of docPaths) {
await client.documents.put(
vault.id,
docPath,
`# ${docPath.replace(/\.md$/, '').replace(/[-/]/g, ' ')}
Content coming soon.`,
);
console.log('Created:', docPath);
}Lifestream Vault automatically generates an in-page table of contents from the heading hierarchy in each document. The renderer scans #, ##, and ### headings and builds a nested, clickable ToC that appears beside the content.
To get a well-structured ToC, follow these conventions:
# (H1) as the document title — it does not appear in the ToC itself## (H2) for top-level sections### (H3) for subsections within an H2Here is an example well-structured document:
---
title: Authentication
description: How to authenticate requests to the Product API.
---
# Authentication
All API requests must include a valid Bearer token in the `Authorization` header.
## Obtaining a Token
You can obtain tokens via the login endpoint or by creating an API key.
### Login Endpoint
POST your credentials to `/api/v1/auth/login` to receive a short-lived access token.
### API Keys
API keys are long-lived tokens scoped to specific operations. Create them in
**Settings → API Keys**.
## Token Expiry
Access tokens expire after **15 minutes**. Use the refresh endpoint to obtain a
new token without re-authenticating.
## Error Codes
| Code | Meaning |
|------|---------|
| 401 | Missing or invalid token |
| 403 | Token lacks required scope |
This document produces a ToC with three H2 entries (Obtaining a Token, Token Expiry, Error Codes) and two H3 sub-entries nested under Obtaining a Token.
The ToC is generated at render time — you never need to write or maintain it manually. Keep your heading hierarchy clean and the ToC stays accurate automatically.
Documentation for different product versions lives in separate top-level folders. The pattern v1/, v2/, etc. keeps versions cleanly separated and maps naturally to sidebar groups.
product-docs/
├── v1/
│ ├── getting-started.md
│ ├── api-reference/
│ │ └── authentication.md
│ └── changelog.md
├── v2/
│ ├── getting-started.md
│ ├── api-reference/
│ │ └── authentication.md
│ └── changelog.md
└── index.md # Points readers to the latest version
When you create a new major version, create the v2/ folder and migrate or copy documents from v1/. The older version remains publicly accessible at its original paths — no redirects needed.
// Create the v2 getting-started document
const gettingStartedV2 = await client.documents.put(
vault.id,
'v2/getting-started.md',
`---
title: Getting Started (v2)
description: Quick-start guide for Product v2.
---
# Getting Started
Welcome to Product v2! This version introduces a new authentication model
and an improved API surface.
## What Changed in v2
- Bearer tokens replaced by signed JWTs with shorter expiry
- Rate limits increased to 1000 req/min on Pro tier
- New \`/api/v2/batch\` endpoint for bulk operations
`,
);
// Copy the v1 authentication reference to v2 with updates
await client.documents.put(
vault.id,
'v2/api-reference/authentication.md',
`---
title: Authentication (v2)
---
# Authentication
v2 uses signed JWTs issued by the \`/api/v2/auth/token\` endpoint.
`,
);
console.log('v2 docs created:', gettingStartedV2.id);When your docs vault is published, visitors can search across all documents using the built-in search bar. Search uses PostgreSQL's websearch_to_tsquery full-text engine with ranked results and highlighted snippets.
Via the UI: open your vault, go to Settings → Search, and enable Full-text indexing.
Or enable it programmatically:
The search index is updated automatically whenever a document is created or updated. For large vaults with many pre-existing documents, run npm run backfill-search -w packages/api on the server side to rebuild the index from scratch.
// Full-text search across the vault
const results = await client.search.search({
vault: vault.id,
q: 'authentication bearer token',
limit: 10,
});
for (const result of results.results) {
console.log(result.path);
console.log(' Rank:', result.rank);
console.log(' Title:', result.title);
}Publishing turns your vault into a publicly browsable documentation site complete with:
Via the UI: open your vault and go to Settings → Publishing → Enable vault publishing.
// Publish the vault — publishVault.publish is positional: (vaultId, params).
// slug and title are required.
await client.publishVault.publish(vault.id, {
slug: 'product-docs',
title: 'Product Docs',
description: 'Official documentation for Product.',
showSidebar: true,
enableSearch: true,
});
// Update published-site settings later with publishVault.update(vaultId, params)
await client.publishVault.update(vault.id, {
description: 'Official documentation for Product (v2).',
theme: 'docs',
});
console.log('Documentation site is live!');After enabling, your docs site is immediately available at:
https://vault.lifestreamdynamics.com/{profileSlug}/{vaultSlug}
No build step, CDN configuration, or deployment pipeline required.
Custom domains require a Business plan. Upgrade in Settings → Subscription.
Serve your docs from docs.example.com (or any subdomain) by adding two DNS records:
Step 1 — TXT record for ownership verification:
| Type | Host | Value |
|---|---|---|
TXT | _lsv-verify.docs.example.com | lsv-verify=<token> |
The verification token is returned when you register the domain (see below).
Step 2 — CNAME to route traffic:
| Type | Host | Value |
|---|---|---|
CNAME | docs.example.com | vaults.lifestreamdynamics.com |
DNS propagation typically takes 1–60 minutes. Once the TXT record resolves, ownership verification runs automatically via the background domain-verification worker — no manual trigger is needed. Monitor status in Settings → Custom Domains or via the SDK/CLI below.
After verification, Lifestream Vault automatically provisions a TLS certificate for your domain via Let's Encrypt. Your docs site will be available over HTTPS within a few minutes.
// Step 1 — register the domain (returns verification token).
// create takes only { domain }.
const domainRecord = await client.customDomains.create({
domain: 'docs.example.com',
});
console.log('Verification token:', domainRecord.verificationToken);
console.log('Add TXT record: _lsv-verify.docs.example.com =', domainRecord.verificationToken);
// Step 2 — verification runs automatically via the background worker once the
// TXT record resolves. Poll status with get(domainId) (positional).
const status = await client.customDomains.get(domainRecord.id);
console.log('Domain status:', status.status); // 'pending' → 'verified'title and description in every document — these power search results and page metadata.[Installation](./installation.md) rather than absolute URLs so links work in both the editor and the published site.The sidebar reflects alphabetical path ordering by default. Prefix filenames with numbers to control order:
getting-started/
├── 01-introduction.md
├── 02-installation.md
├── 03-first-steps.md
└── 04-next-steps.md
Use wikilinks ([[document-slug]]) for internal references. They resolve automatically and are tracked as backlinks, making it easy to see which pages reference a given document.
document.updated event to notify your team in Slack or email when a docs page changesdue: 2026-06-01) to flag docs that need review by a certain date — the calendar view highlights overdue documentsAny document in the vault is visible to authenticated team members but is not public until you explicitly publish it. Use this to draft new sections, have them reviewed, and publish when ready — without a separate staging environment.
description frontmatter field; it appears as the search result excerptYou now have a fully published, searchable documentation site on Lifestream Vault. Here are some places to go next:
client.publishVault including branding, password protection, and analytics