> For the complete documentation index, see [llms.txt](https://docs.usepylon.com/pylon-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.usepylon.com/pylon-docs/knowledge-base/styling-and-customization/custom-themes.md).

# Custom themes

Build your own knowledge base layouts with templates, CSS, and scripts, then preview and publish them.

Custom themes give you full control over your knowledge base's HTML, CSS, and scripts. Pylon fills your templates with your content, styling settings, and navigation. Your search, sign-in, and article features keep working.

Custom themes are available on the Enterprise plan. Manage them on your knowledge base's **Styling** settings page, on the **Theme** tab.

## Get started

* **Use a preset as-is**: under **Theme library**, click **Use Theme** on **Default**, **Sidebar**, or **Glass**. Presets update automatically when Pylon improves them, but you can't edit their code.
* **Customize a preset**: click **Duplicate** on a preset. The copy appears under **My themes**, where you can edit it.
* **Start from scratch**: under **My themes**, click **Create theme** (**Start with default styles**). This creates an editable copy of the Default preset.

From a theme's menu under **My themes** you can **Preview**, **Edit** (rename), **Duplicate**, or **Delete** it. You can't delete the theme that's live. Click **Use Theme** to make a theme live. Visitors see its published version.

## Theme files

Click a theme under **My themes** to open the code editor. The sidebar groups the files you can edit:

| Folder     | File            | What it controls                                                  |
| ---------- | --------------- | ----------------------------------------------------------------- |
| Layouts    | **homepage**    | The knowledge base homepage                                       |
| Layouts    | **collections** | Collection pages                                                  |
| Layouts    | **articles**    | Article pages                                                     |
| Navigation | **navbar**      | The header. Include it in a layout with `{{template "header" .}}` |
| Navigation | **footer**      | The footer. Include it in a layout with `{{template "footer" .}}` |
| Styling    | **styles.css**  | The stylesheet for every page                                     |
| Scripts    | **scripts**     | HTML added to the `<head>` of every page                          |

Layouts only render the page body. Pylon generates the rest of the `<head>` for you: the page title, SEO and social tags, canonical and language links, and the favicon.

The 404 page uses Pylon's built-in layout. It still includes your navbar, footer, and `styles.css`.

<figure><img src="https://982123570-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9hXBoCTTNw5flARlUVJf%2Fuploads%2F5M8hEnJBgBVKe2WqCkQk%2FCleanShot%202026-05-12%20at%2015.00.16%402x.png?alt=media&amp;token=153f394a-86da-4a42-ac37-639a45314a45" alt="The theme code editor with the homepage layout open"><figcaption></figcaption></figure>

## Template syntax

Layouts, the navbar, and the footer use [Go HTML templates](https://pkg.go.dev/html/template). The editor autocompletes variables and keywords, and it flags errors as you type, such as an unclosed `{{if}}` or a variable that doesn't exist in that file.

| Syntax                                                        | Use                                                                           |
| ------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `{{.KB.Title}}`                                               | Print a value                                                                 |
| `{{if .Tags}}…{{else}}…{{end}}`                               | Conditional                                                                   |
| `{{range .Articles}}<a href="{{.URL}}">{{.Title}}</a>{{end}}` | Loop over a list. Inside the loop, `.` is the current item                    |
| `{{with .PreviousArticle}}…{{end}}`                           | Use a value only if it exists. Inside the block, `.` is that value            |
| `{{template "header" .}}`, `{{template "footer" .}}`          | Insert the navbar or footer                                                   |
| `{{if eq .A .B}}`, `ne`, `not`, `and`, `or`                   | Comparisons and logic. Standard built-ins such as `len` and `index` also work |
| `{{$.KB.Title}}`                                              | Reach page-level data from inside a `range` or `with` block                   |
| `{{- … -}}`                                                   | Trim whitespace around a tag                                                  |
| `{{t "Popular Articles"}}`                                    | Translate fixed text (see below)                                              |

### Escaping

Values are escaped automatically for the context they appear in, such as HTML text, attributes, URLs, inline styles, or inline scripts. Values listed as **HTML** in the tables below (article bodies and icons) are output as markup. If a URL value isn't safe, it renders as `#ZgotmplZ`.

### Translating fixed text

Wrap fixed text in `t` with a double-quoted string, for example `{{t "Related Articles"}}`. Pylon collects these strings from your live, published theme. When a translation exists, visitors who browse in another language see it. Otherwise they see your original text. Titles, descriptions, and URLs from the variables below are already localized for the visitor's language.

## Template variables

Variables only include content the current visitor is allowed to see.

### Available in every file

These work in all layouts, the navbar, and the footer.

| Variable               | Type                                       | Description                                         |
| ---------------------- | ------------------------------------------ | --------------------------------------------------- |
| `.KB.Title`            | Text                                       | Knowledge base title                                |
| `.KB.Slug`             | Text                                       | Knowledge base URL slug                             |
| `.KB.LogoURL`          | Text                                       | **Header Logo** image URL                           |
| `.KB.LogoDarkURL`      | Text                                       | **Header Logo on Dark** image URL                   |
| `.KB.Header`           | Text                                       | Homepage header text                                |
| `.KB.TextLinkColorHex` | Text                                       | Text link color hex code, for example `#2563eb`     |
| `.KB.Links`            | List of [links](#link)                     | **Header Links**                                    |
| `.Org.Name`            | Text                                       | Your organization's name                            |
| `.Org.BrandColorHex`   | Text                                       | Primary brand color hex code, for example `#4F46E5` |
| `.Footer.LogoURL`      | Text                                       | **Footer Logo** image URL                           |
| `.Footer.Sections`     | List of [footer sections](#footer-section) | **Footer Page Links**                               |
| `.Footer.SocialLinks`  | List of [links](#link)                     | **Footer Social Media Links**                       |
| `.Collections`         | List of [collections](#collection)         | Top-level collections, for navigation               |
| `.CurrentPath`         | Text                                       | Current page URL path                               |
| `.PageType`            | Text                                       | `homepage`, `collection`, or `article`              |
| `.FaviconURL`          | Text                                       | Favicon image URL                                   |

### homepage

| Variable           | Type                         | Description                                                   |
| ------------------ | ---------------------------- | ------------------------------------------------------------- |
| `.PopularArticles` | List of [articles](#article) | Most-viewed articles                                          |
| `.BackgroundType`  | Text                         | Banner background: `color`, `image`, or empty for the default |
| `.BannerColorHex`  | Text                         | Banner background color hex code                              |
| `.BannerImageURL`  | Text                         | Banner background image URL                                   |
| `.DarkText`        | True/false                   | Whether banner text should be dark (true) or light (false)    |

### collections

| Variable                       | Type                               | Description                                                                             |
| ------------------------------ | ---------------------------------- | --------------------------------------------------------------------------------------- |
| `.Collection`                  | [Collection](#collection)          | The current collection                                                                  |
| `.Articles`                    | List of [articles](#article)       | Articles in this collection                                                             |
| `.Subcollections`              | List of [collections](#collection) | Child collections                                                                       |
| `.Breadcrumbs`                 | List of [breadcrumbs](#breadcrumb) | Breadcrumb trail                                                                        |
| `.ActiveTopLevelCollectionURL` | Text                               | URL of the top-level collection this page belongs to, for highlighting it in navigation |

### articles

| Variable           | Type                                     | Description                                                         |
| ------------------ | ---------------------------------------- | ------------------------------------------------------------------- |
| `.Article`         | [Article details](#article-details)      | The current article                                                 |
| `.ArticleHTML`     | HTML                                     | The rendered article body                                           |
| `.Tags`            | List of [tags](#tag)                     | Tags on this article                                                |
| `.Collection`      | [Collection](#collection)                | Parent collection. May be empty                                     |
| `.RelatedArticles` | List of [articles](#article)             | Related articles                                                    |
| `.SectionArticles` | List of [articles](#article)             | All articles in the same collection                                 |
| `.PreviousArticle` | [Article](#article)                      | Previous article in the same collection. Empty on the first article |
| `.NextArticle`     | [Article](#article)                      | Next article in the same collection. Empty on the last article      |
| `.Breadcrumbs`     | List of [breadcrumbs](#breadcrumb)       | Breadcrumb trail                                                    |
| `.TOC`             | List of [headings](#heading)             | Table of contents built from the article's headings                 |
| `.RelatedIssues`   | List of [related issues](#related-issue) | Related closed issues. Only filled in for signed-in team members    |

### Fields on list items and objects

#### Link

| Field       | Type | Description                |
| ----------- | ---- | -------------------------- |
| `.Label`    | Text | Link text                  |
| `.URL`      | Text | Link URL                   |
| `.IconHTML` | HTML | Icon SVG, for social links |

#### Footer section

| Field    | Type                   | Description           |
| -------- | ---------------------- | --------------------- |
| `.Title` | Text                   | Section heading       |
| `.Links` | List of [links](#link) | Links in this section |

#### Collection

| Field            | Type                         | Description                                                                 |
| ---------------- | ---------------------------- | --------------------------------------------------------------------------- |
| `.Title`         | Text                         | Collection title                                                            |
| `.Slug`          | Text                         | Collection URL slug                                                         |
| `.Identifier`    | Text                         | Collection identifier                                                       |
| `.Description`   | Text                         | Collection description                                                      |
| `.Icon`          | HTML                         | System icon SVG. Empty when a custom icon is set                            |
| `.CustomIconURL` | Text                         | Uploaded icon image URL. Empty when a system icon is used                   |
| `.ArticleCount`  | Number                       | Number of articles in the collection                                        |
| `.URL`           | Text                         | Collection page URL                                                         |
| `.Articles`      | List of [articles](#article) | Articles in the collection. Filled in on subcollections on collection pages |
| `.Children`      | List of collections          | Child collections. Filled in on navigation collections on collection pages  |

#### Article

| Field    | Type | Description      |
| -------- | ---- | ---------------- |
| `.Title` | Text | Article title    |
| `.Slug`  | Text | Article URL slug |
| `.URL`   | Text | Article page URL |

#### Article details

| Field                 | Type       | Description                                                                                      |
| --------------------- | ---------- | ------------------------------------------------------------------------------------------------ |
| `.Title`              | Text       | Article title                                                                                    |
| `.Slug`               | Text       | Article URL slug                                                                                 |
| `.Identifier`         | Text       | Article identifier                                                                               |
| `.AuthorName`         | Text       | Author name. Empty when there's no author                                                        |
| `.AuthorAvatarURL`    | Text       | Author avatar image URL. Empty when unavailable                                                  |
| `.LastUpdatedDisplay` | Text       | Human-readable last updated time                                                                 |
| `.URL`                | Text       | Article page URL                                                                                 |
| `.Hidden`             | True/false | Whether the article is **Direct link only**                                                      |
| `.Visibility`         | Text       | Empty for public, or `contact_only` (customers only), `user_only` (internal), or `ai_agent_only` |

#### Tag

| Field       | Type | Description                              |
| ----------- | ---- | ---------------------------------------- |
| `.Value`    | Text | Tag label                                |
| `.URL`      | Text | Search results URL for this tag          |
| `.HexColor` | Text | The tag's color hex code                 |
| `.Color`    | Text | Color name used by the preset tag styles |

#### Breadcrumb

| Field    | Type | Description                          |
| -------- | ---- | ------------------------------------ |
| `.Title` | Text | Breadcrumb label                     |
| `.URL`   | Text | Link URL. Empty for the current page |

#### Heading

| Field    | Type   | Description                              |
| -------- | ------ | ---------------------------------------- |
| `.ID`    | Text   | Heading anchor ID, for `href="#{{.ID}}"` |
| `.Title` | Text   | Heading text                             |
| `.Level` | Number | Heading level, 1 to 3                    |

#### Related issue

| Field                 | Type   | Description                                                 |
| --------------------- | ------ | ----------------------------------------------------------- |
| `.AccountName`        | Text   | Account name                                                |
| `.AccountLogoURL`     | Text   | Account logo image URL                                      |
| `.RequesterName`      | Text   | Requester name                                              |
| `.RequesterAvatarURL` | Text   | Requester avatar image URL                                  |
| `.TicketNumber`       | Number | Issue number                                                |
| `.Title`              | Text   | Issue title                                                 |
| `.BodyPreview`        | Text   | Shortened issue body                                        |
| `.Source`             | Text   | Issue source, such as `email`, `form`, `manual`, or `slack` |
| `.ClosedDateDisplay`  | Text   | Human-readable closed date                                  |
| `.ViewAllURL`         | Text   | Link to all related issues in Pylon                         |

### Functions

These functions work in every template file. They return nothing if the item doesn't exist or the visitor can't see it, so wrap them in `with` or `range`.

| Function                          | Returns                                                                          | Example                                                                             |
| --------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `collection "identifier"`         | A [collection](#collection): title, slug, identifier, description, icon, and URL | `{{with collection "identifier"}}<a href="{{.URL}}">{{.Title}}</a>{{end}}`          |
| `collectionChildren "identifier"` | A list of that collection's child collections                                    | `{{range collectionChildren "identifier"}}<a href="{{.URL}}">{{.Title}}</a>{{end}}` |
| `article "identifier"`            | An [article](#article)                                                           | `{{with article "identifier"}}<a href="{{.URL}}">{{.Title}}</a>{{end}}`             |

When you type `collection "`, the editor suggests your collections and their identifiers. An article's identifier is the number at the start of its URL: `/articles/<identifier>-<slug>`.

## Interactive components

Pylon adds its interactive features to elements with these IDs. Keep the ones you need in your layouts. If you remove one, that feature doesn't appear.

| Element                              | Feature                                                                                  |
| ------------------------------------ | ---------------------------------------------------------------------------------------- |
| `<div id="kb-search"></div>`         | Search box                                                                               |
| `<div id="kb-search-results"></div>` | Search results and AI answers. Shown while a visitor searches                            |
| `<div id="kb-auth"></div>`           | Sign-in button and account menu                                                          |
| `<div id="kb-locale"></div>`         | Language picker                                                                          |
| `<div id="kb-article-body"></div>`   | Interactive article body, such as code copy buttons, tabs, accordions, and heading links |
| `<div id="kb-toc"></div>`            | Table of contents with scroll tracking                                                   |
| `<div id="kb-feedback"></div>`       | Article feedback                                                                         |
| `<span id="kb-article-edit"></span>` | **Edit in Pylon** link for signed-in team members                                        |

While a visitor is searching, Pylon hides the element with class `kb-homepage-content`, `kb-collection-page-content`, `kb-article-page-content`, or `kb-not-found-message`. It also sets `data-kb-state="searching"` on `.kb-banner` (otherwise `idle`). Keep these class names so search results replace the page content.

On article pages, the presets output `{{.ArticleHTML}}` inside `.kb-article-body-placeholder` next to a `.kb-article-body-skeleton` and `#kb-article-body`. The article is readable right away, and Pylon swaps in the interactive version when it loads. Keep this structure from the preset when you restyle articles.

## styles.css

`styles.css` is loaded on every page as a regular stylesheet, before Pylon's own component styles. Pylon serves it exactly as you write it, with no sanitization or rewriting. Any valid CSS works, including `@import`, `@font-face`, media queries, and custom properties. There's no restriction on which selectors you can target, and you can load fonts and images from any domain.

### Selectors to target

The presets use `kb-` class names you can style directly. Common ones:

| Area                                | Classes                                                                                                                                                                                     |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Page                                | `.kb-page-layout`, `.kb-content-wrapper`, `.kb-content`                                                                                                                                     |
| Header                              | `.kb-header`, `.kb-header-inner`, `.kb-header-logo`, `.kb-header-nav`, `.kb-header-link`, `.kb-header-mobile-nav`                                                                           |
| Homepage banner                     | `.kb-banner`, `.kb-banner--dark-text`, `.kb-banner--light-text`, `.kb-banner-bg`, `.kb-banner-content`, `.kb-banner-title`, `.kb-search-container`                                          |
| Collection cards                    | `.kb-collections-grid`, `.kb-collection-card`, `.kb-collection-card-icon`, `.kb-collection-card-title`, `.kb-collection-card-description`, `.kb-collection-card-count`                      |
| Article lists                       | `.kb-article-card`, `.kb-article-card-title`, `.kb-popular-articles`                                                                                                                        |
| Collection page                     | `.kb-collection-page-header`, `.kb-collection-page-title`, `.kb-collection-page-description`, `.kb-breadcrumbs`                                                                             |
| Article page                        | `.kb-article-layout`, `.kb-article-main`, `.kb-article-sidebar`, `.kb-article-title`, `.kb-article-meta`, `.kb-article-body`, `.kb-related-articles`, `.kb-article-tags`, `.kb-article-tag` |
| Article content                     | Element selectors inside `.kb-article-body`, such as `.kb-article-body h2`, `.kb-article-body p`, `.kb-article-body pre`, `.kb-article-body table`, and `.kb-article-body .EditorCallout`   |
| Search, sign-in, language, feedback | `.kb-search-*`, `.kb-auth-*`, `.kb-locale-*`, `.kb-feedback-*`, `.kb-toc-item`                                                                                                              |
| Footer                              | `.kb-footer`, `.kb-footer-section`, `.kb-footer-section-link`, `.kb-footer-social-link`, `.kb-powered-by`                                                                                   |

The **Sidebar** and **Glass** presets add their own `kb-sidebar-*` and `kb-glass-*` layout classes.

### Typography and colors

The presets read these CSS variables from `:root`:

* `--body-font-family` and `--headlines-font-family`
* `--primaryColor` and `--text-link-color`. The preset navbar sets these from your brand colors.
* `--neutral-*` shades for text, borders, and backgrounds

If you've set fonts in **Settings > Workspace > Brand center > Typography**, Pylon sets `--body-font-family` and `--headlines-font-family` after your stylesheet loads, so the brand fonts win. To use a different font, set `font-family` on your own selectors instead of overriding those variables.

## Scripts

The **scripts** file is HTML that Pylon adds as-is at the end of every page's `<head>`. Use it for `<script>` tags (analytics, tracking pixels), `<link>` tags (web fonts), `<meta>` tags, or `<style>` blocks. Scripts aren't sanitized, and pages don't restrict which domains they load from.

How it relates to **Head Scripts** on the knowledge base's **Code** settings page:

* When one of your themes under **My themes** is live, Pylon uses that theme's **scripts** file and ignores **Head Scripts**. The **Code** page becomes read-only and links to the theme editor. A new theme's scripts file starts empty, so copy over anything you still need.
* When a **Theme library** preset is live, Pylon uses **Head Scripts** from the **Code** page.

The appearance preview on the **Styling** tab doesn't run scripts. Use **Preview** to test them.

## What's sanitized

* **Your theme files** aren't sanitized. Templates, CSS, and scripts are output as you write them, with template values escaped automatically.
* **Article content** is sanitized before it reaches `{{.ArticleHTML}}`. Scripts and event handlers are removed. Custom HTML blocks are stricter: forms, buttons, iframes, video, SVG, and `<style>` tags are removed, and inline styles can't use `url()`, `var()`, or image functions. To add behavior or styles to article pages, use your theme files.

## Draft, preview, and publish

* **Drafts**: edits save automatically as a draft. The editor header shows **Draft** or **Published**. Changed files are marked with a dot in the sidebar.
* **Undo Changes** discards the draft for the open file.
* **Preview** opens your knowledge base in a new tab with your drafts applied and a **Draft Preview** banner. Click **Exit** to leave. Preview pages aren't indexed by search engines, and a preview session lasts up to an hour.
* **Publish** lists the files you've changed. Click **Publish All** to publish them together, or undo a single file from the list. Visitors see the changes right away if the theme is live.
* **Template errors block publishing.** The **Publish** menu lists each error by file and line under **Fix these template errors before publishing:**.

{% hint style="warning" %}
If a published layout fails to render, or takes longer than 5 seconds, visitors see that page in the Default theme instead. In **Preview**, you see the error message so you can fix it.
{% endhint %}

There's no fixed size limit for theme files. Because `styles.css` and scripts load on every page, keep them lean.

You can also use Ask AI while the editor is open. Its edits are saved as drafts for you to review and publish. All theme edits and publishes are recorded in your workspace's audit logs.

## Preset updates

Themes you duplicated from a preset, or created with **Create theme**, keep track of the preset they came from. When Pylon releases a new version of that preset, your theme shows **Base update available**.

1. Click the badge to read the **Release notes** and see the **Files in this update**. Files that conflict with your edits are marked **Needs attention**.
2. Click **Create Update Draft**. First publish or discard any existing changes. Pylon merges the update into your theme as a draft. Only files that are part of the update can be edited while the draft is open.
3. Preview the draft, then click **Review & Publish** and **Publish Update**. To skip it, click **Discard Update**. Update files are published or discarded together.

Presets you use directly from the **Theme library** always run the latest version, so there's nothing to merge.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.usepylon.com/pylon-docs/knowledge-base/styling-and-customization/custom-themes.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
