> ## Documentation Index
> Fetch the complete documentation index at: https://cactal.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Customize site-wide files

> Create a custom 404 page, place trusted HTML at exact document positions, and understand which site-wide settings require publishing.

Cactal's singleton source files apply behavior across a website. Use `not_found` for unmatched paths and the four custom HTML slots for third-party tags that must appear at an exact document position.

## Site-wide settings at a glance

| Setting                     | Where it lives                     | Lease        | Publish for production                    |
| --------------------------- | ---------------------------------- | ------------ | ----------------------------------------- |
| Site metadata and redirects | `website_config` source            | Required     | Required                                  |
| Global CSS and Tailwind     | `global_css` source                | Required     | Required                                  |
| Custom 404 page             | `not_found` source                 | Required     | Required                                  |
| Custom head/body HTML       | Four custom-code source singletons | Required     | Required                                  |
| Website name and slug       | Website resource                   | Not required | No                                        |
| Favicon                     | Website favicon setting            | Not required | No                                        |
| Custom and primary domains  | Domain resources                   | Not required | No, but a site must be published to serve |

Source-backed settings follow head and published versions. Current-resource settings such as the favicon apply immediately and are not restored by source rollback.

## Create a custom not-found page

The `not_found` singleton is a page module with the reserved route literal `not_found`:

```tsx not_found theme={null}
import { definePage } from '@website'

export default definePage({
	route: 'not_found',
	metadata: {
		title: 'Page not found',
		description: 'The requested page could not be found.',
		robots: { index: false, follow: false }
	},
	render: ({ path }) => (
		<main className="mx-auto max-w-2xl px-6 py-24 text-center">
			<p className="text-sm font-medium uppercase tracking-widest">404</p>
			<h1 className="mt-4 text-4xl font-semibold">Page not found</h1>
			<p className="mt-4 text-slate-600">
				No page exists at <code>{path}</code>.
			</p>
			<a className="mt-8 inline-block underline" href="/">
				Return home
			</a>
		</main>
	)
})
```

The file identity is `{ "type": "not_found" }`; it has no `name`. The special route is not a public URL pattern and must be exactly `not_found`, without a leading slash.

When no page or redirect matches, Cactal renders this page with the originally requested `path`, the request's query map, and an empty route-parameter map. The HTTP response remains `404`, so crawlers and clients receive the correct status.

Without a `not_found` file, Cactal returns its standard platform 404 response.

### Use CMS data on the 404 page

The not-found page supports the same `queries`, metadata, and render props as other pages. For example, it can retrieve published navigation links:

```tsx not_found theme={null}
import { definePage } from '@website'

export default definePage({
	route: 'not_found',
	queries: ({ cms }) => ({
		links: cms.navigation_links.list({ limit: 6 })
	}),
	metadata: {
		title: 'Page not found',
		robots: { index: false }
	},
	render: ({ data, path }) => (
		<main>
			<h1>Nothing at {path}</h1>
			<nav>
				{data.links.items.map(link => (
					<a key={link.id} href={link.fields.url}>
						{link.fields.label}
					</a>
				))}
			</nav>
		</main>
	)
})
```

Only published CMS items are available, including on the draft host. A failing CMS query can prevent the 404 page from rendering, so keep its dependencies small and resilient.

<Note>
  A `get` query that finds no published item on a regular page falls through to `not_found` with HTTP 404, so a deleted or unpublished item serves this page instead of an error.
</Note>

## Add custom document HTML

Four singleton types insert raw HTML into every server-rendered page:

| File type           | Exact position                                                                             | Typical uses                                                                   |
| ------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| `custom_head_start` | Immediately after `<head>`, before Cactal metadata and site CSS                            | Early connection hints, required verification tags, critical third-party setup |
| `custom_head_end`   | After Cactal metadata and the compiled site stylesheet, before `</head>`                   | Deferred third-party scripts, external stylesheets, additional tags            |
| `custom_body_start` | Immediately after `<body>`, before the React root                                          | Noscript fallbacks, tag-manager body snippets                                  |
| `custom_body_end`   | After the React root and Cactal hydration scripts, before built-in analytics and `</body>` | Widgets and scripts that should run after page hydration                       |

The effective document order is:

```html theme={null}
<!doctype html>
<html>
  <head>
    <!-- custom_head_start -->
    <!-- Cactal metadata -->
    <style><!-- compiled global_css --></style>
    <!-- custom_head_end -->
  </head>
  <body>
    <!-- custom_body_start -->
    <div id="root"><!-- server-rendered page --></div>
    <!-- Cactal platform, site, and hydration scripts -->
    <!-- custom_body_end -->
    <!-- built-in analytics, when enabled -->
  </body>
</html>
```

### Add a head integration

```bash Set custom head HTML theme={null}
curl -X PUT 'https://api.cactal.ai/v1/websiteSourceCode/files' \
  -H "Authorization: Bearer $CACTAL_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "websiteId": "V1StGXR8_Z5jdHi6B-myT",
    "file": { "type": "custom_head_end" },
    "content": "<link rel=\"preconnect\" href=\"https://widget.example.com\">\n<script defer src=\"https://widget.example.com/client.js\"></script>",
    "leaseToken": "exampleLeaseToken00000000000000000000000000"
  }'
```

### Add a tag-manager fallback

```bash Set custom body-start HTML theme={null}
curl -X PUT 'https://api.cactal.ai/v1/websiteSourceCode/files' \
  -H "Authorization: Bearer $CACTAL_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "websiteId": "V1StGXR8_Z5jdHi6B-myT",
    "file": { "type": "custom_body_start" },
    "content": "<noscript><iframe src=\"https://tags.example.com/fallback\" height=\"0\" width=\"0\" hidden></iframe></noscript>",
    "leaseToken": "exampleLeaseToken00000000000000000000000000"
  }'
```

Delete a slot with `DELETE /v1/websiteSourceCode/files` and the same `{ websiteId, file, leaseToken }` shape.

## Raw HTML and security

Custom slot content is inserted without escaping, sanitization, templating, or React processing. It can run scripts, load remote resources, modify the DOM, collect visitor data, or break page markup.

<Warning>
  Treat custom HTML as privileged executable code. Paste only code you trust, review every remote hostname, and remove integrations you no longer use. Anyone who can edit full website source can change these slots.
</Warning>

Additional rules:

* Slots apply to every rendered page, including the custom 404 page.
* Slots cannot vary by route or use CMS/request template values.
* Prefer the structured metadata configuration for title, description, robots, canonical, Open Graph, Twitter, alternate links, and JSON-LD. Hand-authored duplicates can produce conflicting tags.
* Use `defer`, `async`, or module scripts when appropriate so a third-party script does not block document parsing.
* A syntactically valid source write can still contain broken HTML. Inspect the draft's rendered document before publishing.
* Third-party integrations may have consent, privacy, and data-processing requirements outside Cactal.

## Site favicon

Favicons are a current website setting, not a source file. Set a ready PNG, SVG, or ICO asset with `PUT /v1/websites/{websiteId}/favicon`:

```bash theme={null}
curl -X PUT 'https://api.cactal.ai/v1/websites/V1StGXR8_Z5jdHi6B-myT/favicon' \
  -H "Authorization: Bearer $CACTAL_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{ "assetId": "aR7sT9uV1wX3yZ5bC6dE8" }'
```

The favicon changes immediately on published and draft hosts without a lease or publish. Served pages carry `<link rel="icon">` tags for it: a PNG is linked once and doubles as the Apple touch icon, an ICO is linked as is, and an SVG is linked after its rasterized PNG preview so browsers that ignore SVG icons still show one. `/favicon.ico` redirects to the asset for clients that never read the tags. Send `{ "assetId": null }` to restore the Cactal fallback. A page or redirect explicitly handling `/favicon.ico` wins over the setting. The dashboard offers the same choice under the site's Settings.

See [Upload assets](/docs/guides/upload-assets) for allowed formats and upload steps.

## Validate and publish

For `not_found` and custom HTML changes:

1. Read the current head snapshot.
2. Acquire an edit lease and upsert the singleton.
3. Wait for the draft preview to become ready.
4. Inspect a normal page and an unmatched URL on the draft host.
5. Run `POST /v1/websiteSourceCode/head/check`.
6. Publish after the draft behaves correctly.

Custom HTML is not semantically validated. The quality gate confirms source integrity and buildability, but visual and browser verification remains necessary.

## Troubleshooting

| Problem                                       | Cause                                                               | Fix                                                               |
| --------------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------- |
| Custom 404 returns status 200                 | The request matched a real page                                     | Test an actually unmatched path and check redirects               |
| Custom 404 does not appear                    | A redirect matches first, or the file uses the wrong route sentinel | Remove the redirect or use `route: 'not_found'`                   |
| 404 page fails only for some content          | A CMS query cannot retrieve published data                          | Publish the dependency or simplify the query                      |
| A verification tag is not found               | It was placed after a script that rewrites the head                 | Move it to `custom_head_start`                                    |
| A widget runs before the page exists          | Its script is too early                                             | Move it to `custom_body_end` or use `defer`                       |
| A metadata tag appears twice                  | It exists in both structured metadata and a custom slot             | Keep the structured metadata version and remove the raw duplicate |
| A source rollback did not restore the favicon | Favicons are current settings                                       | Set the previous favicon asset explicitly                         |

## Next steps

* [Style a website](/docs/guides/style-a-website) for `global_css` and Tailwind CSS
* [Configure SEO metadata](/docs/guides/configure-seo-metadata) for structured head tags
* [Configure routes and redirects](/docs/guides/configure-routes-and-redirects) for URL behavior
* [Preview drafts and versions](/docs/guides/preview-drafts-and-versions) before publishing third-party code
