---
title: Build one site for several products
description: Build the docs of several products as one site with a config file: one sidebar for all of them, a docs home, a link at the top of the sidebar, and a search box.
slug: /guides/several-products
order: 8
visibility: public
redirects_from: []
---

# Build one site for several products

Prompt:

> Our three products each have a docs folder. Put them all in one docs area with one sidebar.

Also works: "Add a search box to our docs sidebar." "Make /docs/ open the first page of our docs." "Add a link to our support page at the top of the docs sidebar."

Write a config file that names each product, its docs folder, and the address its pages live under ([step 1](#1-write-the-config-file)). Add the docs home, the link, and the search address if the site wants them ([step 2](#2-add-the-docs-home-the-link-and-the-search-box)). Mark any product only some readers may see as gated ([step 3](#3-mark-the-products-only-some-readers-may-see)). Check every product with one command ([step 4](#4-check-every-product)), then build the site ([step 5](#5-build-the-site)).

## What your tool does

- Finds each product's docs folder and the address its pages are served under today, so that no page moves.
- Writes the config file, with the products in the order the sidebar should list them.
- Adds the docs home, the sidebar's link, and the search address where the site has them.
- Checks every product, fixes what the check reports, and builds.
- Asks for each product's label and name when the project does not state them.

## What you need

- For each product: a short label that says what it does, such as `Backend & Hosting`, and its name, such as `Turn Zero Cloud`.
- The order the products should appear in.
- If you want a search box: the address of a search service, on your site or another. The tool shows the box, but it does not search by itself.

## Before your AI starts

This section is for your AI coding tool: what it checks and gathers before it begins. You don't need to do these steps yourself.

- Each product's docs folder passes the check on its own ([Check a corpus](/tzdocs/guides/check-a-corpus/)).
- Each product has its own address, such as `/cloud` and `/tracker`. No address may sit inside another.

## Steps

### 1. Write the config file

The config file is a JSON file. Save it as `tzdocs.json` in the folder where you run the tool, and the commands read it without a flag. Its `products` list names each product in sidebar order:

```json
{
  "products": [
    { "label": "Backend & Hosting", "name": "Turn Zero Cloud", "source": "cloud/docs", "base": "/cloud" },
    { "label": "AI Issue Tracking", "name": "tzTrack", "source": "tracker/docs", "base": "/tztrack" }
  ]
}
```

Each product takes these keys:

| Key | Required | What it does |
| --- | --- | --- |
| `label` | yes | What the product does, in plain words. The sidebar shows it first. |
| `name` | yes | The product's name. The sidebar shows it after the label, in parentheses: `Backend & Hosting (Turn Zero Cloud)`. |
| `source` | yes | The product's docs folder, relative to the config file's folder. |
| `base` | yes | The address the product's pages live under, such as `/cloud`. It works like `--base` for a single folder, so each page keeps the address a build of that folder alone gives it. It cannot be `/`. |
| `fullTextExclude` | no | Works like `--full-text-exclude` for this product alone. |
| `version` | no | Works like `--corpus-version` for this product alone. |
| `opening` | no | An HTML file placed at the head of this product's home page, as `--home-opening` places one for a single folder. Its path is relative to the config file's folder. |
| `tree` | no | The word a search hit uses to name this product. Default: the base without its first `/`, such as `cloud`. |
| `gated` | no | `true` for a product only some readers may see. Its pages still build at its base. Every other product's pages leave it out of the sidebar and put a marker in its place. Default: `false`. |

### 2. Add the docs home, the link, and the search box

Four more keys sit beside `products`, and each is optional:

```json
{
  "home": "/docs",
  "link": { "title": "Have a question?", "text": "Ask our support agent", "address": "/questions/" },
  "search": "/api/v1/docs/search",
  "searchParameter": "query",
  "products": [
    { "label": "Backend & Hosting", "name": "Turn Zero Cloud", "source": "cloud/docs", "base": "/cloud" }
  ]
}
```

- `home` is the docs home's address. Opening it opens the first page of the first product that is not gated. The home has no heading or cards of its own. It cannot sit inside a product's address.
- `link` puts a link at the top of the sidebar: `title` on its first line, `text` under it, going to `address`.
- `search` puts a search box under the link. As the reader types, the box asks this address with the words as `q`, such as `/api/v1/docs/search?q=deploy`, and lists the answers under the box. [Output](/tzdocs/reference/output/#the-search-box) says what the answer must look like. Without `search`, there is no box.
- `searchParameter` names the parameter that carries the words, where your search service expects another name than `q`. With `"searchParameter": "query"`, the box asks `/api/v1/docs/search?query=deploy`. It is a short word: a letter, then up to 31 letters, digits, `-`, or `_`. Default: `q`.

### 3. Mark the products only some readers may see

Set `"gated": true` on a product whose docs are not for everyone, such as one that is not yet public. The pages of every other product then never name it. In the sidebar, where its entry would stand, they carry a marker: the comment `<!--docs-product:<tree>-->`, such as `<!--docs-product:team-->`. The server that serves your site can put the product's entry in place of the marker for a reader allowed to see it.

The gated product's own pages build as usual, with the whole sidebar. Protect them on your server, because the tool does not. The docs home never opens a gated product's page.

### 4. Check every product

Check the whole site with one command. Each finding starts with the address of its product, such as `/tztrack: `, so you know which folder to fix.

```text
tzdocs check
```

With the config file saved under another name, add `--config <file>`.

### 5. Build the site

Build with the config file in place of `--source`:

```text
tzdocs build --out site --origin https://example.com
```

To wrap every page of every product in your own page, such as one with your site's menu and footer, add `--shell <file>` ([Style the site](/tzdocs/guides/style-the-site/)). One shell serves the whole site. Each product's `opening` adds its own HTML at the head of that product's home page.

`--origin`, `--shell`, `--stylesheet`, `--include-hidden`, `--diagrams-as-code`, and `--next-previous` apply to every product. `--source`, `--base`, `--full-text-exclude`, `--corpus-version`, and `--home-opening` belong in the config file, as each product's `source`, `base`, `fullTextExclude`, `version`, and `opening`. Giving any of them with a config file is a usage error.

## Expected result

The success line gives the number of pages and products, such as `built 120 pages of 5 products into site`.

The output folder holds each product's pages under its address, such as `site/cloud/` and `site/tztrack/`, and the docs home at its address, such as `site/docs/index.html`. Each product keeps its own index, manifest, whole text, and Markdown copies, just as a build of its folder alone writes them.

Every page has the same sidebar. Each product is a heading you can open and close, with its sections and pages inside. The product of the page you are on is open, with its section open and the page marked. The others are closed.

## Refusals

[Refusals and findings](/tzdocs/reference/refusals/) gives the exact text.

| Message | Exit code | Cause | Remedy |
| --- | --- | --- | --- |
| `--<name> cannot be given with a config file` | 2 | A flag the config file takes the place of was given with a config file. | Move the value into the config file, or leave the flag out. |
| `the config file is absent at <path>` | 1 | No file is at the path. | Correct the path. |
| `the config file <path> is invalid — <reason>` | 1 | The file is not a config file. The reason says what is wrong. | Fix the file as the reason says. |
| `the home <route> has no page to open — the first product not gated, at <base>, has no page` | 1 | The site names a home, but the first product that is not gated has no public page. | Add a page, or put another product first. |
| `the home <route> has no page to open — every product is gated` | 1 | The site names a home, but every product is gated. | Leave one product ungated, or remove the home. |

## Related

- [Getting started](/tzdocs/getting-started/) lists every flag with its default.
- [Output](/tzdocs/reference/output/#a-site-of-several-products) describes the site's folder, its sidebar, and its search box.
- [Add search to your docs](/tzdocs/guides/add-search/) has your AI tool write the service the search box asks.
- [Build for AI tools](/tzdocs/guides/build-for-ai-tools/) explains the base path, the whole text, and the version.
