Docs-as-Code with Markdown
With docs-as-code, your documentation lives as Markdown files on disk, versioned right next to the code it describes. Export your project to a folder, edit it in your own editor, review it in a pull request, and import it back. Everything Theneo renders, including your navigation, API reference details, and widgets, is captured in plain files you can commit.
How the round trip works
1
Export
Run theneo export --dir ./docs to write the full folder to disk.
2
Edit in Git
Edit the Markdown and metadata in your editor, review it in a pull request, and version it alongside your code.
3
Import back
Run theneo import --dir ./docs --project <slug> to push your changes back into Theneo.
How your project is structured on disk
Every section of your docs is a folder containing exactly two files: an index.md with the content, and a section.json with its API and widget metadata. A single theneo.json at the root defines the project and assembles the navigation. Nesting folders creates nested sections in the sidebar.
your-documentation/
├── theneo.json
├── introduction/
│ ├── index.md
│ └── section.json
├── getting-started/
│ ├── index.md
│ └── section.json
└── api-reference/
├── index.md
├── section.json
└── endpoints/
└── create-customer/
├── index.md
└── section.json
The rules: every folder is one section, and it must contain both index.md and section.json. The folder name becomes the section's slug (lowercase, hyphenated) and must match what you declare in theneo.json.
theneo.json: the manifest
The root theneo.json defines your project and assembles the sidebar. Its key fields:
id,name,baseUrl: project identity, and the base URL used in code samples.isSinglePage: render as one continuous page (true) or a navigable multi-page site (false).sections: the ordered tree of sections, which becomes your sidebar.tabs: the top-level tabs that group those sections.
Each section object looks like this:
{
"name": "Introduction",
"slug": "introduction",
"children": [],
"icon": null,
"isHeader": false,
"externalPageLink": "",
"isNewTab": false
}
name: the sidebar label.slug: must match the folder on disk.children: nested section objects, empty for a leaf.isHeader: set totruefor a group label with no page of its own.externalPageLinkandisNewTab: point the entry at an external URL instead of an internal page.
Each tab object lists which top-level section slugs appear under it:
{
"title": "Documentation",
"slug": "documentation",
"sections": ["introduction", "getting-started", "api-reference"],
"iconUrl": "",
"svgCode": ""
}
section.json: per-section metadata
Every section folder needs a section.json, even plain documentation pages, which use the default below. Theneo treats it as a no-op and simply renders the index.md.
{
"endpoints": { "method": "GET", "path": "" },
"request": null,
"responses": [],
"showBaseUrl": false,
"showLanguageBox": false,
"showRequestDescription": true,
"showResponseDescription": true,
"errorCodes": [],
"statusCodes": [],
"dataExample": [],
"endpointSummary": []
}
When a section documents an API endpoint, populate the relevant fields: endpoints.method and endpoints.path (for example /v1/customers/{id}), the request body, responses, errorCodes, statusCodes, and toggles like showBaseUrl and showLanguageBox.
index.md: your content
Standard Markdown plus Theneo's widgets. If your project uses tabs, start each index.md with a tab marker comment whose slug matches a tab in theneo.json:
<!-- tab:documentation -->
# Introduction
Welcome to the API documentation...
The marker is an HTML comment, so it never renders. A section can belong to one tab only, meaning its slug appears in exactly one tab's sections array.
Commands
Create a new project from Markdown
theneo create --dir <directory> --name <project-name> --workspace <workspace-slug>
Import or update a project from Markdown
theneo import --project <project-slug> --workspace <workspace-slug> --dir <directory>
Working with project tabs
Target a specific project tab on export and import using the --tab option:
# Export a tab as Markdown
theneo export --project <project-slug> --workspace <workspace-slug> --tab <tab-slug> --dir <directory>
# Export an OpenAPI spec from a tab
theneo export --openapi --format json --project <project-slug> --tab <tab-slug> --dir <directory>
# Import Markdown into a tab
theneo import --project <project-slug> --workspace <workspace-slug> --tab <tab-slug>
# Import an OpenAPI spec into a tab
theneo project import --file <file> --project <project-slug> --tab <tab-slug> --publish
Ready to automate this both ways? GitHub Sync wires these commands into a pipeline so your repo and Theneo stay in lockstep.
Best practices
- Version control: keep your Markdown in Git for history and collaboration.
- Consistent structure: match folder names to slugs for clean imports.
- Modular organization: nest endpoints logically within folders.
- Descriptive slugs: use clear, kebab-case names for sections and endpoints.
- Regular sync: push updates via CI/CD with GitHub Actions or GitHub Sync.
Troubleshooting
Import fails with a structure error
Import fails with a structure error
- Verify
theneo.jsonexists in the root directory. - Ensure every section slug matches its folder name exactly.
- Check that each section folder contains both
index.mdandsection.json.
API endpoints not appearing
API endpoints not appearing
- Confirm
section.jsonholds a valid endpoint configuration. - Validate the JSON syntax in your configuration files.
- Ensure
methodandpathare correctly specified.
Markdown rendering issues
Markdown rendering issues
- Escape special characters where needed.
- Verify custom widget syntax matches the Content Widgets reference.
- Test widgets individually before combining them.
On this page
- Docs-as-Code with Markdown