# Config Reference (config.toml)

## Config Reference

**File:** `src/config/config.toml` · **Skill level:** Beginner

`config.toml` holds every site-wide setting. It is grouped into tables: a line in `[brackets]` starts a table, and every `key = value` line below it belongs to that table until the next one. The file has a comment beside most keys; this page explains them in one place.

In the Tina edition, these keys are under **Site settings** at `/admin/`. In the Sanity edition, most are under **Site settings** in the Studio, which win over this file. Favicons, the language settings, and the collection folder names stay in `config.toml` there.

<Aside type="note">
  `npm run dev` picks up a saved change to `config.toml` and reloads the page.
  Restart it after you add a language, and after you change `trailingSlash`,
  `defaultLanguage`, or `showDefaultLangInUrl`, which Astro reads only when it
  starts.
</Aside>

### Site Identity
Table: `[site]`

| Key                | What it does                                                                                    |
| ------------------ | ----------------------------------------------------------------------------------------------- |
| `title`            | Site name. Used in page titles, the logo's alt text, structured data, `llms.txt`, and the outlined wordmark at the bottom of the `wordmark` footer |
| `description`      | Default description for search results and social previews. Empty uses each language's own, in `src/i18n/<lang>.json`. A value here is used for every language. |
| `tagline`          | Added after every page title ("Pricing - your tagline"). Empty uses each language's own, in `src/i18n/<lang>.json`. A value here is used for every language. A page turns it off with `disableTagline: true` |
| `taglineSeparator` | Text between title and tagline. Empty uses `" - "`                                              |
| `baseUrl`          | Your live address, for example `"https://www.your-domain.com"`. Ships as the placeholder `"https://example.com"`, and `npm run build` warns until you change it. A `SITE_URL` environment variable on the build overrides it |
| `logo`             | Path to the logo for light backgrounds                                                          |
| `logoAlternate`    | Path to the logo for dark backgrounds: the dark footers, and the `transparent` header over a photo |
| `logoText`         | Text next to the logo. Empty shows only the image                                               |
| `logoWidth`, `logoHeight` | Logo size, for example `"132px"` and `"32px"`                                            |
| `trailingSlash`    | `true` makes every URL end with `/` (`/pricing/`)                                              |
| `[site.favicon] image` | Source image for `npm run generate-favicons`. Leave `path` as it is                         |

### Search Engines
Tables: `[seo]`, `[seo.robotsTxt]`, `[seo.ai]`, `[seo.localBusiness]`, `[seo.sitemap]`

| Key                  | What it does                                                                                 |
| -------------------- | -------------------------------------------------------------------------------------------- |
| `author`             | Default author meta tag                                                                      |
| `keywords`           | Default keywords for pages without their own `keywords`                                      |
| `robots`             | Default robots meta tag. The shipped value lets search and AI answers quote and preview pages |
| `[seo.robotsTxt] enable` | Builds `/robots.txt`                                                                     |
| `[seo.robotsTxt] disallow` | Paths crawlers must skip, for example `["/drafts/"]`                                   |
| `[seo.robotsTxt] aiSearch` | `false` asks the crawlers of AI answer engines (ChatGPT search, Claude, Perplexity, and others) not to fetch your pages |
| `[seo.robotsTxt] aiTraining` | `false` asks crawlers that collect content to train AI models to stay away             |
| `[seo.ai] llmsTxt`   | Builds `/llms.txt` and `/llms-full.txt`, one per language                                    |
| `[seo.ai] markdownPages` | Builds a Markdown copy of each service, project, post, open role, FAQ, pricing, and legal page at `<page>.md` |
| `[seo.localBusiness] type` | The schema.org business type, for example `"HomeAndConstructionBusiness"`, `"Electrician"`, or `"RoofingContractor"` |
| `[seo.localBusiness] areaServed` | The towns or regions you install in. Ships with sample town names                  |
| `[seo.localBusiness] priceRange`, `openingHours` | Optional. `openingHours` uses the schema.org format, for example `"Mo-Fr 08:00-18:00"` |
| `[seo.sitemap] enable` | Builds `/sitemap-index.xml`                                                                |
| `[seo.sitemap] exclude` | Folder names to leave out of the sitemap                                                  |

The business name in the structured data is `site.title`; its address, phone, and email come from `[settings.contactInfo]`. Details: [SEO & AI Visibility](/ecozen/customization/seo-ai-visibility/).

### General Settings
Table: `[settings]`

| Key                   | What it does                                                                                  |
| --------------------- | --------------------------------------------------------------------------------------------- |
| `blogFolder`, `serviceFolder`, `projectsFolder`, `testimonialFolder`, `teamFolder`, `careersFolder` | Folder names of the collections. Changing one also means renaming the content and route folders and the collection in `src/content.config.ts`. Most sites keep them |
| `pagination`          | Posts per blog list page                                                                      |
| `stickyHeader`        | Keeps the header bar at the top of the window while you scroll. The rows above it (top bar, info row, announcement strip) scroll away; once the bar reaches the top, only its colours change |
| `contactFormAction`   | Where the quote form sends its data. The shipped value is a placeholder, and `npm run dev` and `npm run build` print a `[forms]` warning until you change it. See [Forms](/ecozen/customization/forms/) |
| `contactFormProvider` | `"formsubmit.co"` (default), `"formspree"`, or `"netlify"`                                    |
| `footerDescription`   | Text under the logo in the `cta` and `contact` footer layouts. Empty uses `footer.description` from `src/i18n/<language>.json` |

### Smooth Scroll
Table: `[settings.smoothScroll]`

Smooth scrolling powered by Lenis. It stays off on touch devices.

| Key                    | What it does                                                         |
| ---------------------- | -------------------------------------------------------------------- |
| `enable`               | `false` gives the browser's normal scrolling                         |
| `respectReducedMotion` | Switches it off for visitors who ask for less motion                 |
| `duration`             | How long the glide lasts. Higher feels slower                        |
| `easing`               | `expo`, `linear`, `easeOutQuad`, or `easeOutCubic`                   |
| `smoothWheel`, `syncTouch`, `wheelMultiplier`, `touchMultiplier`, `anchors`, `autoRaf`, `stopInertiaOnNavigate` | Finer settings. [Smooth Scroll](../../features/smooth-scroll/) explains each one |

### Animations
Table: `[settings.animation]`

| Key                    | What it does                                                    |
| ---------------------- | --------------------------------------------------------------- |
| `enable`               | Fade-in animations as content scrolls into view, and the slow drift (parallax) of full-width photos. A page turns the fade-in off for itself with `scrollReveal: false` in its frontmatter, as the main homepage does |
| `respectReducedMotion` | Shows content without animation for visitors who ask for less motion |
| `duration`             | Empty keeps each element's own timing; a value such as `"900ms"` sets one duration for all |
| `distance`, `blur`, `easing` | How far content moves in, how blurred it starts, and with which curve |
| `rootMargin`, `threshold` | How early an element starts animating                        |

### Cursor
Table: `[settings.cursor]`

`enable = true` adds an animated custom cursor that follows the mouse. It ships off, and it never shows on touch screens.

### Cookie Banner
Table: `[settings.cookieConsent]`

| Key        | What it does                                                                  |
| ---------- | ----------------------------------------------------------------------------- |
| `enable`   | Shows the cookie banner and holds consent-gated scripts until visitors choose |
| `revision` | Raise it by 1 when your cookie use changes, to ask every visitor again        |

Details: [Cookies & Analytics](/ecozen/customization/cookies-analytics/).

### Page Actions And AI Links
Table: `[settings.pageActions]`

| Key         | What it does                                                                                 |
| ----------- | -------------------------------------------------------------------------------------------- |
| `enable`    | The **Copy page**, **View as Markdown**, and **Ask** links on blog posts and on service, project, and job pages. `false` also removes the footer row. The Markdown links need `[seo.ai] markdownPages = true` |
| `footer`    | The **Ask AI** and llms row in every footer layout                                           |
| `providers` | Assistants offered: any of `"chatgpt"`, `"claude"`, `"perplexity"`, `"grok"`. Ships with all four. `[]` hides the "Ask" links |

Details: [AI Links & llms](../../features/ai-links-llms/).

### Theme Demo Settings
Table: `[settings.layoutSwitcher]`

This table is used only by the theme's own demo site. It has no effect on your build, so leave it as it is. To change how a section looks, set `options.layout` in its content file: see [Section Layouts](/ecozen/customization/section-layouts/).

### Footer
Tables: `[settings.footer]`, `[settings.footer.band]`, `[settings.footer.cta]`, `[settings.footer.contact]`, `[settings.newsletter]`

| Key                              | What it does                                                                 |
| -------------------------------- | ---------------------------------------------------------------------------- |
| `[settings.footer] layout`       | `wordmark` (default), `newsletter`, `cta`, or `contact`. A page's `footerLayout` frontmatter wins |
| `[settings.footer.band]`         | The call-to-action band that overlaps the top of the `newsletter` and `cta` layouts (in the `cta` layout it is the photo band below). `enable = false` turns it off on every page; it also hides on its own on pages that show a call to action section. `url` is where its button goes, and also the quote row of the `wordmark` layout. `image` and `imageAlt` are the arch photo in the `newsletter` layout's band (empty `image` hides it). Its text is `footer.band` in the i18n files |
| `[settings.footer.cta]`          | The photo band of the `cta` layout: `enable`, `title`, `label`, and `url` of its closing line and button, and `image` and `imageAlt` of its photo (empty `image` hides it). Empty `title` and `label` use `footer.cta.title` and `footer.cta.button` from the i18n files; the project card is `footer.cta.projects` |
| `[settings.footer.contact]`      | The area check of the `contact` layout: `postcodes`, the postcode prefixes you cover (the shipped ones are placeholders), and `url`, the quote page it links to. An empty `postcodes` list sends visitors to the quote page |
| `[settings.newsletter]`          | `enable`, `action`, and `provider` (`formsubmit.co`, `formspree`, `netlify`, or `""` for a plain form post) of the sign-up form in the `newsletter` layout. The shipped `action` is a placeholder: `npm run dev` and `npm run build` print a `[forms]` warning until you change it. See [Forms](/ecozen/customization/forms/#check-the-setup) |

Details: [Footer](/ecozen/customization/footer/).

### Copyright
Table: `[settings.copyright]`

`enable` shows the copyright line in the footer. `text` overrides `footer.copyright` from the i18n files; `{{ year }}` is replaced with the year of the build.

### Theme Credit
Table: `[settings.themeCredit]`

The theme credit in every footer layout, and the last line of `llms.txt`. It has `enable`, `text`, and `url`, and it is the same in every language. The part of `text` in `[brackets]` links to `url`:

```toml
[settings.themeCredit]
  enable = true
  text   = "..."
  url    = "..."
```

### Languages
Table: `[settings.multilingual]`

| Key                    | What it does                                                         |
| ---------------------- | -------------------------------------------------------------------- |
| `enable`               | `false` builds only the default language                             |
| `defaultLanguage`      | Language served without a prefix (`"en"`)                            |
| `disableLanguages`     | Languages to skip, for example `["fr"]` or `["fr", "ar"]`            |
| `showDefaultLangInUrl` | `true` adds the prefix to the default language too (`/en/pricing/`)  |

Details: [Internationalization](/ecozen/customization/internationalization/).

### Contact Details
Table: `[settings.contactInfo]`

`address`, `email`, `phone`, and `hours`. They are shown in the header's contact row, the mobile menu, the footers, and the contact cards on `/contact/`, and they feed the structured data and `llms.txt`. Write them as plain text; the phone number becomes a `tel:` link on its own.

### Mobile Menu Panel
Tables: `[settings.headerOffcanvas]`, `[settings.headerOffcanvas.button]`

The slide-in panel that opens from the menu button on small screens: a short `description`, the menu links, your contact details, social icons, and a `button`. Empty `description` and `label` use `headerOffcanvas` from the i18n files.

<Aside type="caution">
  Keep `[settings.headerOffcanvas] enable = true`. The panel is the mobile
  menu, so turning it off removes the menu button on phones and tablets.
</Aside>

### Header Layout
Table: `[settings.header]`

`layout`: `classic` (default), `transparent`, `floating`, or `promo`. A page's `headerLayout` frontmatter wins. See [Header](/ecozen/customization/header/).

### Header Button
Table: `[settings.navigationButton]`

The button at the right of the header ("Get a free quote"). It has `enable`, `label`, and `url`. An empty `label` uses `navigation.buttonLabel` from the i18n files.

### Announcement
Table: `[settings.announcementBar]`

The citrus announcement strip at the top of the page. The `promo` header layout always shows it; `enable = true` also shows it above the `classic` header. `label` is the text for every language and accepts Markdown; empty uses `header.announcement` from the i18n files, and a page's `headerAnnouncement` wins over both. See [Header](/ecozen/customization/header/).

### Tables Of Contents
Table: `[settings.markup.tableOfContents]`

`enable`, `startLevel`, `endLevel`, `ordered`, and `expanded` for an "On this page" list on blog posts and text pages such as the privacy policy. It ships off.

### Social Previews
Table: `[opengraph]`

| Key              | What it does                                                                          |
| ---------------- | ------------------------------------------------------------------------------------- |
| `image`          | Shared preview image, used when a page has no `image`                                 |
| `ogLocale`, `ogType` | Open Graph locale and type                                                        |
| `twitter`, `twitterCard` | X (Twitter) handle and card type                                              |

### Custom Head Scripts
Table: `[head]`

`content` is pasted into the `<head>` of every page. Use it for analytics, verification tags, or extra stylesheets. With the cookie banner on, mark tracking scripts so they wait for consent: see [Cookies & Analytics](/ecozen/customization/cookies-analytics/).