## Documentation index This index lists every available documentation page and its Markdown source. - [Documentation](https://veta.varavel.com/docs/index.md) - [Getting Started](https://veta.varavel.com/docs/getting-started/index.md) - [Installation](https://veta.varavel.com/docs/installation/index.md) - [Guides](https://veta.varavel.com/docs/guides/index.md) - [Project Structure](https://veta.varavel.com/docs/guides/project-structure/index.md) - [Configuration](https://veta.varavel.com/docs/guides/configuration/index.md) - [Pages](https://veta.varavel.com/docs/guides/pages/index.md) - [Data](https://veta.varavel.com/docs/guides/data/index.md) - [Markdown](https://veta.varavel.com/docs/guides/markdown/index.md) - [Templates](https://veta.varavel.com/docs/guides/templates/index.md) - [Components](https://veta.varavel.com/docs/guides/components/index.md) - [Filters](https://veta.varavel.com/docs/guides/filters/index.md) - [Assets And Tailwind CSS](https://veta.varavel.com/docs/guides/assets-and-tailwind/index.md) - [Themes](https://veta.varavel.com/docs/guides/themes/index.md) - [Development Server](https://veta.varavel.com/docs/guides/development-server/index.md) - [Build And Output](https://veta.varavel.com/docs/guides/build-and-output/index.md) - [Deployment](https://veta.varavel.com/docs/guides/deployment/index.md) - [Reference](https://veta.varavel.com/docs/reference/index.md) - [CLI Reference](https://veta.varavel.com/docs/reference/cli/index.md) - [Config Reference](https://veta.varavel.com/docs/reference/config/index.md) - [Page Generators Reference](https://veta.varavel.com/docs/reference/page-generators/index.md) - [Template Context Reference](https://veta.varavel.com/docs/reference/template-context/index.md) - [Troubleshooting](https://veta.varavel.com/docs/reference/troubleshooting/index.md) - [API](https://veta.varavel.com/docs/api/index.md) - [JavaScript API](https://veta.varavel.com/docs/api/javascript/index.md) - [File API](https://veta.varavel.com/docs/api/files/index.md) - [HTTP Client](https://veta.varavel.com/docs/api/http-client/index.md) - [Parse API](https://veta.varavel.com/docs/api/parse/index.md) - [Template Functions](https://veta.varavel.com/docs/api/template-functions/index.md) - [Environment And Console](https://veta.varavel.com/docs/api/environment-and-console/index.md) - [Markdown Frontmatter](https://veta.varavel.com/docs/api/frontmatter/index.md) ## Documentation content The complete documentation for this website follows, reproduced verbatim from every page. --- # Veta Documentation Veta is a static site generator built around JavaScript page generators, templates, structured data, components, filters, Markdown, themes and Tailwind CSS. It is distributed as a single CLI binary and is designed to turn project files into a static output directory that can be deployed anywhere. Start with the step-by-step tutorial: [Getting Started](./getting-started/). --- # Getting Started This guide builds a small Veta site from scratch. By the end, you will understand the project structure, the page generator model, templates, data files, components, Markdown content, Tailwind CSS, the development server, and the production build. ## 1. Install Veta Install Veta with any of the methods documented in [Installation](../installation/). Verify the CLI is available: ```sh veta --version ``` ## 2. Create A Project Create a starter project: ```sh veta init my-site cd my-site ``` The starter contains these files: ```txt my-site/ veta.yaml components/ note.html data/ site.json pages/ site.js public/ robots.txt styles.css templates/ base.html ``` Start the development server: ```sh veta dev ``` Open the printed local URL. Veta builds the site into a temporary directory, serves it locally, watches your project files, and reloads the browser when a rebuild finishes. The development server does not write to `dist/`. ## 3. Understand The Config The starter `veta.yaml` looks like this: ```yaml build: output: dist clean: true dev: host: 127.0.0.1 port: 3000 watch: [] html: minify: true tailwindcss: stylesheets: - styles.css minify: true ``` The important defaults are: - `build.output` is the directory written by `veta build`. - `build.clean` removes the output directory before writing a new build. - `dev.host` configures the local development server host. - `dev.port` configures the local development server port. - `dev.watch` is an array of additional directories for the development server to watch, beyond Veta's own files and directories. - `html.minify` minifies generated `.html` files. - `tailwindcss.stylesheets` points to Tailwind CSS entrypoints under `public/`. - `tailwindcss.minify` minifies the generated stylesheet. ## 4. Edit Site Data Open `data/site.json` and change the name or description: ```json { "name": "My Veta Site", "description": "A small site built with Veta." } ``` Data files become available in templates and page generators through the `data` object. The file `data/site.json` becomes `data.site`. ## 5. Generate Pages With JavaScript Open `pages/site.js`: ```js export default function({ data, parse }) { const home = parse.markdown(`Welcome to **${data.site.name}**.`).html; const about = parse.markdown( "This page was generated from `pages/site.js`.", ).html; return [ { permalink: "/", template: "base", title: "Home", content: home, }, { permalink: "/about/", template: "base", title: "About", content: about, }, ]; } ``` Every file in `pages/` must be a JavaScript file. It must export a default function that returns an array of page objects. Each page object needs a `permalink`: ```js { permalink: "/about/", template: "base", title: "About", content: "About this site." } ``` If `template` is present, Veta passes `content` unchanged and trusted to the selected file in `templates/`. It does not automatically process Markdown or components, so the generator must produce the final expected format, usually HTML. If `template` is omitted, Veta writes `content` unchanged as raw output. ## 6. Use A Template Open `templates/base.html`: ```html {{ page.title }} - {{ data.site.name }}

{{ page.title }}

{{ page.content }}
``` Templates receive four root values: - `data`: global data loaded from `data/`. - `pages`: every normalized page returned by your page generators. - `page`: the current page. - `props`: component props when rendering a component. ## 7. Add A Component Components are templates stored in `components/`. The starter includes `components/note.html`: ```html ``` Use it inside page content: ```js export default function({ parse }) { const { html } = parse.markdown( "Welcome to **Veta**.\n\nComponents are explicit.", ); const content = parse.renderComponents(html); return [ { permalink: "/", template: "base", title: "Home", content, }, ]; } ``` `parse.renderComponents` resolves registered component tags and passes tag attributes and slot content through `props`. It does not render Markdown, which is why this example calls `parse.markdown` first. Components are not resolved unless the generator explicitly calls it. ## 8. Read Markdown Files Create content files: ```txt content/posts/hello.md content/posts/second.md ``` Example Markdown file with YAML frontmatter: ```md --- title: Hello World date: "2026-06-30" tags: - intro --- # Hello World This post is stored as Markdown. ``` Generate pages from those files: ```js export default function({ files, parse }) { const posts = files.listFiles("content/posts/**/*.md"); return posts.map((path) => { const { frontmatter, html } = parse.markdown(files.readFile(path)); const content = parse.renderComponents(html); return { permalink: files.toPermalink(path, { stripPrefix: "content" }), template: "base", title: frontmatter.title, content, }; }); } ``` `parse.markdown` returns: ```js { frontmatter: { title: "Hello World", date: "2026-06-30", tags: ["intro"] }, content: "# Hello World\n\nThis post is stored as Markdown.\n", html: "

Hello World

\n

This post is stored as Markdown.

\n" } ``` `content` is the raw body, while `html` is the Markdown-rendered body. Without frontmatter, `frontmatter` is `{}` and `content` is the full input. ## 9. Add Styles With Tailwind CSS The starter uses `public/styles.css` as the Tailwind entrypoint: ```css @import "tailwindcss"; ``` When `tailwindcss.stylesheets` includes `styles.css`, Veta reads `public/styles.css`, runs the embedded Tailwind CSS standalone CLI against the generated output, and writes `dist/styles.css`. Use classes directly in templates and components: ```html ``` ## 10. Build For Production Stop the dev server and run: ```sh veta build ``` Veta writes the production site to `dist/` by default. Generated `.html` files are minified when `html.minify: true` is set. Public assets are copied from `public/` to the output root. You can deploy `dist/` to any static host. ## Next Steps Read these next: - [Project Structure](../guides/project-structure/) - [Pages](../guides/pages/) - [Templates](../guides/templates/) - [JavaScript API](../api/javascript/) - [Build And Output](../guides/build-and-output/) --- # Installation Veta is distributed as prebuilt binaries through GitHub Releases. The installers and package integrations download those release assets instead of rebuilding Veta locally. ## Linux And macOS Use the shell installer: ```sh curl -fsSL https://get.varavel.com/veta | sh ``` Or install with Homebrew: ```sh brew install varavelio/tap/veta ``` ## Windows Use the PowerShell installer: ```powershell irm https://get.varavel.com/veta.ps1 | iex ``` ## npm Install globally: ```sh npm install --global @varavel/veta ``` Or install as a project development dependency: ```sh npm install --save-dev @varavel/veta ``` The npm package installs the matching Veta binary for your platform during `postinstall`. ## Docker Run Veta from Docker: ```sh docker run --rm varavel/veta --help ``` Mount your project when you want to build it from a container: ```sh docker run --rm -v "$PWD:/site" -w /site varavel/veta build ``` Images are also published to GitHub Container Registry as `ghcr.io/varavelio/veta`. ## Manual Download Download archives from GitHub Releases: ```txt https://github.com/varavelio/veta/releases ``` Release archives include Linux, macOS, and Windows binaries for supported architectures. Releases also publish `checksums.txt` and `manifest.json`. ## Verify Installation Run: ```sh veta --version veta --help ``` You should see version information and the available commands. --- # Guides These guides explain how to build with Veta in practice. - [Project Structure](/docs/guides/project-structure/) - [Configuration](/docs/guides/configuration/) - [Pages](/docs/guides/pages/) - [Data](/docs/guides/data/) - [Markdown](/docs/guides/markdown/) - [Templates](/docs/guides/templates/) - [Components](/docs/guides/components/) - [Filters](/docs/guides/filters/) - [Assets And Tailwind CSS](/docs/guides/assets-and-tailwind/) - [Themes](/docs/guides/themes/) - [Development Server](/docs/guides/development-server/) - [Build And Output](/docs/guides/build-and-output/) - [Deployment](/docs/guides/deployment/) --- # Project Structure A Veta project is a folder with a `veta.yaml` configuration file and optional feature directories. The starter project created by `veta init` shows the common layout: ```txt . veta.yaml components/ data/ filters/ functions/ pages/ public/ templates/ ``` Only `veta.yaml` and `pages/` are necessary for most useful sites. The other directories are optional and can be introduced as the project grows. ## `veta.yaml` `veta.yaml` configures Veta itself. It controls build output, clean mode, generated HTML minification, Tailwind CSS, and themes. Site content does not belong in `veta.yaml`. Put content, navigation, SEO metadata, and theme data in `data/` or content files read through the JavaScript file API. ## `pages/` `pages/` contains flat JavaScript page generator files. Each file must end in `.js` and export a default function that returns an array of page objects. The directory is intentionally flat. Do not put nested folders under `pages/`. ## `templates/` `templates/` contains Pongo page templates and any supporting template files. A page object references templates relative to this directory: ```js { permalink: "/", template: "base", } ``` That can resolve `templates/base.html`, `templates/base.j2`, or another non-ignored file with the same stem. Templates can include other files or import exported macros through normal Pongo tags. Veta does not prescribe subdirectories inside `templates/`; projects can organize supporting files however they prefer. ## `components/` `components/` contains reusable component templates. Component tags are derived from file paths: ```txt components/note.j2 -> components/ui/card.j2 -> ``` Components can be placed inside page content and explicitly resolved with `parse.renderComponents(text)`. They receive tag attributes and slot content through `props`. Component resolution does not render Markdown. ## `data/` `data/` contains global data files. Veta supports JSON, YAML, TOML, and JavaScript: ```txt data/site.json -> data.site data/navigation.yaml -> data.navigation data/theme/colors.toml -> data.theme.colors ``` Nested directories become nested keys. ## `filters/` `filters/` contains custom JavaScript template filters. The directory is flat and every filter file must end in `.js`. ```txt filters/titlecase.js -> {{ page.title|titlecase }} ``` ## `functions/` `functions/` contains custom JavaScript template functions. The directory is flat and every function file must end in `.js`. ```txt functions/excerpt.js -> {{ excerpt(page.content, 120) }} ``` ## `public/` `public/` contains static files copied to the output root. For example: ```txt public/robots.txt -> dist/robots.txt public/images/logo.svg -> dist/images/logo.svg public/styles.css -> Tailwind entrypoint when configured ``` Public assets are copied as-is. Generated HTML minification applies only to generated page output, not to copied public files. --- # Configuration Veta configuration lives in YAML. The supported file names are checked in this order: ```txt veta.yaml veta.yml .veta.yaml .veta.yml ``` When you run `veta build` or `veta dev`, Veta searches from the current directory upward through parent directories until it finds one of those files. You can also pass an explicit config file: ```sh veta build --config path/to/veta.yaml veta dev --config path/to/veta.yaml ``` The project root is the directory that contains the resolved config file. ## Minimal Config ```yaml build: output: dist ``` If `build.output` is omitted or blank, Veta uses `dist`. ## Full Common Config ```yaml build: output: dist clean: true html: minify: true dev: host: 127.0.0.1 port: 3000 watch: - content tailwindcss: stylesheets: - styles.css minify: true theme: source: "./theme" ``` ## `build` `build` contains build workflow settings. ```yaml build: output: dist clean: true ``` `output` is the production output directory used by `veta build`. It must be a relative project path. `clean` removes the output directory before writing a new build. ## `html` `html` contains generated HTML settings. ```yaml html: minify: true ``` `html.minify` minifies generated `.html` files only. It does not minify XML, Markdown, JSON, text, JavaScript, CSS, or files copied from `public/`. ## `dev` `dev` configures the local development server. ```yaml dev: host: 127.0.0.1 port: 3000 watch: - content ``` `host` is the network interface used by `veta dev`. `port` is the local development server port. `watch` adds project-relative files or directories to the watcher. Directories are watched recursively. Veta always watches its own project files and directories in addition to these paths. Use `watch` for content directories that Veta cannot infer, such as `content/`, `posts/`, `docs/`, or files consumed through `files.readFile`. ## `tailwindcss` `tailwindcss` enables Veta's embedded Tailwind CSS standalone integration. ```yaml tailwindcss: stylesheets: - styles.css minify: true ``` `stylesheets` lists Tailwind CSS entrypoints relative to `public/`. With the config above, Veta reads `public/styles.css` and writes the generated CSS to `dist/styles.css`. `minify` passes Tailwind's minification flag to the standalone CLI. If `tailwindcss.stylesheets` is omitted or empty, Tailwind CSS does not run. ## `theme` `theme.source` points to a local theme directory or a GitHub theme source. ```yaml theme: source: "./themes/clean" ``` Themes can provide `templates/`, `components/`, `filters/`, `functions/`, `data/`, and `public/`. Project files override theme files. ## Unknown Fields Veta rejects unknown config fields. This catches typos early and keeps configuration predictable. --- # Pages Pages are generated by JavaScript files in `pages/`. Each file must export a default function that returns an array of page objects. ```js export default function({ data, parse }) { const { html } = parse.markdown(`# ${data.site.name}`); return [ { permalink: "/", template: "base", title: "Home", content: html, }, ]; } ``` ## Directory Rules `pages/` is flat. This is valid: ```txt pages/site.js pages/posts.js ``` This is not valid: ```txt pages/blog/posts.js ``` Use multiple files when it helps organize generators, but keep them directly inside `pages/`. ## Page Object Contract Every page object must have `permalink`. ```js { permalink: "/docs/intro/", template: "base", title: "Intro", content: "

Intro

" } ``` Fields: - `permalink` is required and must be a string. - `template` is optional and must be relative to `templates/`. - `content` is optional and defaults to an empty string. - Any extra fields are preserved and exposed to templates through `page`. `layout` is not supported. Use `template`. ## Permalinks And Output Paths Veta normalizes permalinks into output paths: ```txt / -> index.html /about/ -> about/index.html /feed.xml -> feed.xml /llms.txt -> llms.txt ``` If the last permalink segment has an extension, Veta writes that exact file path. Otherwise, it writes an `index.html` file under the permalink path. ## Templated Pages When `template` is present, Veta passes `content` unchanged and trusted to the named template. It does not automatically render Markdown or resolve components. The generator must return the final format that the template expects, usually HTML. ```js export default function({ parse }) { const source = "This supports **Markdown** and components."; const { html } = parse.markdown(source); const content = parse.renderComponents(html); return [ { permalink: "/about/", template: "base", title: "About", content, }, ]; } ``` The template receives that final string as `page.content`. Use `parse.markdown(text)` and `parse.renderComponents(text)` explicitly, in the order required by the content. ## Raw Pages When `template` is omitted, Veta writes `content` directly. ```js { permalink: "/feed.xml", content: `${data.site.name}`, } ``` Raw pages are useful for feeds, sitemaps, JSON, text files, Markdown files, and any other generated asset. Because raw output is not transformed either, a generator can intentionally return Markdown, JSON, XML, or plain text without a template. ## Output Collisions Two page objects cannot write the same output path. Veta reports an error if generators produce conflicting permalinks. ## Generating Pages From Files Use the JavaScript file API to generate content-driven pages: ```js export default function({ files, parse }) { return files.listFiles("content/posts/**/*.md").map((path) => { const { frontmatter, html } = parse.markdown(files.readFile(path)); const content = parse.renderComponents(html); return { permalink: files.toPermalink(path, { stripPrefix: "content" }), template: "post", title: frontmatter.title, content, }; }); } ``` --- # Data Global data lives in `data/`. Veta loads data before page generation and exposes it as `data` in JavaScript generators, templates, components, and filters. ## Supported Formats Veta supports: ```txt .json .yaml .yml .toml .js ``` Examples: ```txt data/site.json data/navigation.yaml data/theme.toml data/github.js ``` ## Data Keys Data keys come from file paths without extensions: ```txt data/site.json -> data.site data/navigation.yaml -> data.navigation data/theme/colors.toml -> data.theme.colors ``` Data file stems must be valid JavaScript-style identifiers. Prefer names like `site.json`, `navigation.yaml`, and `theme/colors.toml`. Avoid names like `site-name.json` because hyphens do not produce ergonomic template keys. ## Site Data Convention Use `data/site.yaml` for project-level values such as site name, description, brand settings, and other values shared across templates: ```yaml name: "My Site" description: "A site built with Veta." brand: color: "purple" ``` This convention is optional, but it gives projects and themes a predictable place for site-wide settings. Reusable themes should put configurable defaults in `data/site_defaults.yaml` instead of `data/site.yaml`. Projects can then provide `data/site.yaml` with only the values they want to customize. See [Themes](/docs/guides/themes/) for the recommended theme defaults pattern. ## JSON Data ```json { "name": "Veta Docs", "description": "Documentation built with Veta." } ``` Use it in a template: ```html {{ data.site.name }} ``` ## YAML Data ```yaml main: - label: Home href: / - label: Docs href: /docs/ ``` Use it in a template: ```html {% for item in data.navigation.main %} {{ item.label }} {% endfor %} ``` YAML data files support one YAML document. Multiple YAML documents in one file are rejected. ## TOML Data ```toml name = "Clean" [colors] primary = "blue" ``` Use it in a template: ```html

{{ data.theme.colors.primary }}

``` ## JavaScript Data JavaScript data files export a default function and return a value: ```js export default function({ env, httpClient, parse }) { if (env.VETA_MODE === "development") { return { stars: 0, repo: "local/mock" }; } const response = httpClient.get( "https://api.github.com/repos/varavelio/veta", ); const repo = parse.json(response.body); return { repo: repo.full_name, stars: repo.stargazers_count, }; } ``` Data JavaScript is synchronous. Return plain JSON-compatible data. Promises are not supported. ## Duplicate Keys These files conflict because both try to define `data.site`: ```txt data/site.json data/site.yaml ``` These also conflict because one file tries to define `data.shop` while another tries to define `data.shop.products`: ```txt data/shop.json data/shop/products.json ``` Veta fails the build instead of guessing which value should win. ## Data Versus File API Use `data/` for global data that should be loaded once and shared everywhere. Use the JavaScript file API for content collections and project files you want to enumerate manually: ```js const posts = files.listFiles("content/posts/**/*.md"); ``` Templates can also load local or remote data on demand with `load_data`: ```html {% set navigation = load_data("data/navigation.yaml") | parse_yaml %} ``` Use `load_data` for data that is only needed by a specific template, include, or component. --- # Markdown Veta provides explicit Markdown parsing through JavaScript and Pongo filters. It uses GitHub Flavored Markdown features and allows inline HTML. Page content is never rendered as Markdown automatically. ## Markdown In Page Content Call `parse.markdown(text)` in a page generator and pass its `html` result to the page: ```js export default function({ parse }) { const { html } = parse.markdown("# About\n\nThis is **Markdown**."); return [ { permalink: "/about/", template: "base", title: "About", content: html, }, ]; } ``` The template outputs that HTML: ```html
{{ page.content }}
``` Templated `page.content` is passed unchanged and trusted to the selected template, so Pongo does not escape it. The generator is responsible for producing the expected final format. Template-less content is also unchanged and is written as raw output, which makes raw Markdown pages possible. ## Markdown And Components Markdown rendering and component resolution are independent operations. Call them explicitly in the order required by the source. The recommended generator flow is: ```js const { frontmatter, html } = parse.markdown(files.readFile(path)); const content = parse.renderComponents(html); return content; ``` `parse.renderComponents(text)` resolves registered component tags but does not render Markdown. For example: ```js export default function({ files, parse }) { const path = "content/about.md"; const { frontmatter, html } = parse.markdown(files.readFile(path)); const content = parse.renderComponents(html); return [ { permalink: "/about/", template: "base", title: frontmatter.title, content, }, ]; } ``` Component slot content is not given an additional Markdown pass. See [Components](/docs/guides/components/) for component behavior and context. Raw HTML and HTML-like component tags are preserved, including opening tags whose quoted attributes span multiple lines. Both paired tags and self-closing tags therefore remain available to the following component pass. Markdown and HTML files are trusted author input in Veta; this rendering step does not sanitize scripts, event attributes, or other raw HTML. ## Markdown Files Veta does not automatically discover Markdown pages. Use JavaScript generators to read files and create pages: ```js export default function({ files, parse }) { return files.listFiles("content/posts/**/*.md").map((path) => { const { frontmatter, html } = parse.markdown(files.readFile(path)); const content = parse.renderComponents(html); return { permalink: files.toPermalink(path, { stripPrefix: "content" }), template: "post", title: frontmatter.title, content, }; }); } ``` This keeps routing explicit and lets you decide how collections are sorted, filtered, paginated, or grouped. ## YAML Frontmatter YAML frontmatter uses `---` delimiters: ```md --- title: Hello World draft: false tags: - guide - intro --- # Hello World Post body. ``` ## TOML Frontmatter TOML frontmatter uses `+++` delimiters: ```md +++ title = "Release Notes" draft = false tags = ["release", "notes"] [meta] author = "Veta" +++ # Release Notes Post body. ``` ## `parse.markdown` Return Value ```js const post = parse.markdown(files.readFile("content/posts/hello.md")); ``` Returns: ```js { frontmatter: { title: "Hello World", draft: false, tags: ["guide", "intro"] }, content: "# Hello World\n\nPost body.\n", html: "

Hello World

\n

Post body.

\n" } ``` `content` is the raw Markdown body and `html` is that body rendered as Markdown. If a Markdown file has no frontmatter, `frontmatter` is an empty object and `content` is the full input. Frontmatter is detected only at the first line of the file. A `---` or `+++` line later in the document is treated as normal Markdown content. ## Pongo Markdown Filters The Pongo `parse_markdown` filter keeps its template-specific return shape, `{ content, frontmatter }`, and does not render Markdown. Pipe `content` through the separate `markdown` filter when HTML is required: ```html {% set post = load_data("content/post.md")|parse_markdown %} {{ post.content|markdown }} ``` This differs from JavaScript `parse.markdown(text)`, which also returns `html`. --- # Templates Pongo page templates and supporting files live in `templates/`. Veta does not prescribe subdirectories inside it, so each project can organize layouts, fragments, and macro libraries as needed. A page object uses a template by setting `template`: ```js export default function({ parse }) { const { html } = parse.markdown("# Welcome"); return [ { permalink: "/", template: "base", title: "Home", content: html, }, ]; } ``` Veta resolves the name relative to `templates/`. It passes the generator's `content` string unchanged and trusted to the template; Markdown rendering and component resolution are explicit generator operations. ## Template Names Veta supports any template extension, but `.j2` is the recommended convention for Pongo templates and components. Pongo uses Jinja-style syntax, and many editors and formatters already recognize `.j2` files well. ```txt templates/base.j2 templates/navigation.j2 components/card.j2 ``` You can include the file extension: ```js template: "base.j2"; ``` Or omit it: ```js template: "base"; ``` When the extension is omitted, Veta scans for a non-ignored file with the same stem. For example, `base` can resolve to `templates/base.j2`. If more than one file matches the same extensionless name, Veta reports an ambiguous template error. ## Template Context Templates receive exactly these root keys: ```txt data pages page props ``` Example: ```html {{ page.title }} - {{ data.site.name }} {% for item in pages %} {{ item.title }} {% endfor %}
{{ page.content }}
``` `props` is usually empty in page templates. It is populated when rendering components. ## Inheritance Pongo inheritance works inside `templates/`: ```html {# templates/base.j2 #} {% block title %}{{ data.site.name }}{% endblock %} {% block main %}{% endblock %} ``` ```html {# templates/pages/article.j2 #} {% extends "../base.j2" %} {% block title %} {{ page.title }} | {{ block.Super }} {% endblock %} {% block main %}
{{ page.content }}
{% endblock %} ``` Use `./` or `../` for relative paths in `extends` and `include` statements. ## Includes Pongo templates can include other files by project-relative path: ```html {% include "templates/brand.html" %} ``` Includes receive the current template context, including `data`, `pages`, `page`, and `props`. Use `with` to provide values explicitly and `only` to isolate an included file from the current context: ```html {% include "templates/user-card.j2" with user=page.author only %} ``` Page templates and components use the same loader, so both can reuse files under `templates/`. ## Macros And Imports Macros define callable template fragments. Add `export` when a macro must be imported from another file: ```html {# templates/ui.j2 #} {% macro button(text, href, tone="primary") export %} {{ text }} {% endmacro %} ``` Import the exported names that the caller needs. Imports can use aliases: ```html {% import "templates/ui.j2" button as action %} {{ action("Read the guide", "/guides/") }} ``` Macros can also be defined and called in the same file without `export`. Macro files use the normal template loader, including extensionless names and project-over-theme overrides. ## Loading Data Pongo templates and components can load local or remote data with `load_data`: ```html {% set navigation = load_data("data/navigation.yaml")|parse_yaml %} {% set site = load_data("data/site.json")|parse_json %} ``` Use `load_data` for template-specific data. Use global `data/` files for data shared across the whole site. See [Template Functions](/docs/api/template-functions/) for the full API. ## Functions Pongo templates and components can call built-in functions such as `url`, `regex_replace`, and `load_data`. Projects can add custom JavaScript functions in `functions/`: ```js // functions/excerpt.js export default function({ page }, value, length) { return String(value || page.title).slice(0, Number(length)); } ``` ```html {{ excerpt(page.content, 120) }} ``` See [Template Functions](/docs/api/template-functions/) for details. ## Filters Veta registers built-in filters and custom filters: ```html {{ page.summary|markdown }} ``` Custom JavaScript filters live in `filters/` and are documented in [Filters](/docs/guides/filters/). ## Ignored Template Files Veta ignores template files or path segments that: - start with `.` - end with `~` - end with `.tmp` This lets editors keep temporary files in the project without affecting builds. --- # Components Components are reusable templates stored in `components/`. Veta discovers their custom tags, and JavaScript can resolve those tags explicitly with `parse.renderComponents(text)`. Page content is not scanned for components automatically. ## Basic Component Create `components/note.html`: ```html ``` Resolve it in a page generator: ```js export default function({ parse }) { const { html } = parse.markdown( "Welcome to **Veta**.\n\nComponents are explicit.", ); const content = parse.renderComponents(html); return [ { permalink: "/", template: "base", content, }, ]; } ``` The component receives its slot as `props.content`. `parse.renderComponents` does not render Markdown; the example renders Markdown first and then resolves components. Calling it directly with `Use **bold**.` leaves the Markdown markers in the slot unchanged. ## Props Attributes become string props: ```js const content = parse.renderComponents( "Be careful.", ); ``` Component template: ```html ``` All attribute values are strings. Component invocations use HTML-like syntax. Opening tags and quoted attributes can span lines, and components without slot content can be self-closing: ```html This component has **Markdown slot content** when Markdown is rendered first. ``` Keep attribute values quoted. A `>` inside a quoted value does not close the tag. ## Component Names Component tags are derived from file paths: ```txt components/note.j2 -> components/ui/card.j2 -> ``` Valid component tags start with a lowercase letter and can contain lowercase letters, numbers, and hyphens. Double hyphens are rejected. ## Nested Components Components can be nested in content: ```js const content = parse.renderComponents(` Nested component content. `); ``` The resolver handles registered nested tags present in the supplied source and leaves unregistered tags unchanged. It preserves component props and slots while recursively rendering nested components. Rendered component output is final and is not scanned again for more component tags, preventing templates from accidentally creating recursive expansion loops. Component examples inside Markdown code spans or fences remain unchanged. The same applies to component-like text inside HTML attributes, comments, scripts, styles, code blocks, preformatted blocks, text areas, and titles. ## Explicit Ordering The caller controls the transformation order. For Markdown files that may contain components, use: ```js const { frontmatter, html } = parse.markdown(files.readFile(path)); const content = parse.renderComponents(html); ``` Markdown preserves multiline HTML-like component tags so the component pass can resolve both paired and self-closing forms. There is no implicit Markdown pass before or after component rendering. Do not pass component template output back through Markdown; the returned string can be assigned to a templated page as final trusted `content` or returned by a template-less page as raw output. ## Component Context Component templates use the same root keys as Pongo templates when those values exist: ```txt data pages page props ``` `props` contains string attributes from the tag plus slot content in `props.content`. When a page generator calls `parse.renderComponents`, global `data` is available to component templates. `page` and `pages` are not available yet because the generator is still creating the page list. If a context-bound JavaScript template function calls `parse.renderComponents`, its available runtime `page` and `pages` values flow into component rendering. Each resolved tag still supplies its own `props` and slot content. ## Component Inheritance Components are Pongo templates, so they can use inheritance too: ```html {# components/shell.j2 #}
{% block body %}{{ props.content }}{% endblock %}
``` ```html {# components/panel.j2 #} {% extends "./shell.j2" %} {% block class %} panel {% endblock %} ``` Use relative paths with `./` or `../` inside component templates. ## Pongo Reuse Component templates can include supporting templates or import macros through normal Pongo tags: ```html {# components/note.html #} ``` This is useful when the same markup or callable macro is needed from both page templates and content components. Supporting Pongo files can live anywhere under `templates/`; Veta does not prescribe their internal organization. Explicit component resolution does not change Pongo behavior: component inheritance, includes, macro imports, relative paths, and the component template context continue to work normally. ## Ignored Component Files Veta ignores component files or path segments that: - start with `.` - end with `~` - end with `.tmp` ## Component Conflicts If two files create the same tag, Veta picks the most specific deterministic winner and records the conflict internally. Avoid relying on conflicts. Use unique names. --- # Filters Filters transform values inside templates. Veta includes built-in filters and can load custom JavaScript filters from `filters/`. ## Built-In Filters ### `json` Serializes a value as JSON: ```html ``` ### `base64_encode` Encodes a string as Base64: ```html {{ "hello" | base64_encode }} ``` ### `base64_decode` Decodes a Base64 string: ```html {{ "aGVsbG8=" | base64_decode }} ``` Invalid Base64 input fails the build. ### `markdown` Renders a string as Markdown: ```html {{ page.summary | markdown }} ``` The output is trusted HTML. ### Parse Filters Parse filters convert strings into structured template values: ```html {% set site = load_data("data/site.json") | parse_json %} {% set navigation = load_data("data/navigation.yaml") | parse_yaml %} {% set theme = load_data("data/theme.toml") | parse_toml %} {% set post = load_data("content/post.md") | parse_markdown %} ``` `parse_markdown` parses YAML or TOML frontmatter and returns `{ content, frontmatter }`. It does not render Markdown to HTML; use `markdown` for rendering. This Pongo API is distinct from JavaScript `parse.markdown(text)`, which returns `{ frontmatter, content, html }` with the rendered body in `html`. ## Custom JavaScript Filters Create `filters/titlecase.js`: ```js export default function({ data }, input) { return String(input) .split(" ") .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(" "); } ``` Use it in a template: ```html

{{ page.title | titlecase }}

``` The filter file name becomes the filter name. `filters/titlecase.js` becomes `titlecase`. ## Filter Parameters Filters can receive one parameter: ```html {{ page.title | prefix:"Post: " }} ``` ```js export default function(runtime, input, parameter) { return `${parameter}${input}`; } ``` When a filter is called without a parameter, the third argument is `null`-like from the JavaScript side. ## Runtime Context Custom filters receive the JavaScript runtime context as the first argument: ```js export default function({ data, env }, input, parameter) { return `${data.site.name}: ${input}`; } ``` Filters are synchronous. Promises are not supported. ## Directory Rules `filters/` is flat. Nested filter directories are not supported. Every filter file must end in `.js`. --- # Assets And Tailwind CSS Static assets live in `public/`. Tailwind CSS is configured through `veta.yaml` and uses one or more stylesheets inside `public/` as entrypoints. ## Public Assets Files in `public/` are copied to the output root: ```txt public/robots.txt -> dist/robots.txt public/images/logo.svg -> dist/images/logo.svg ``` Public files are copied as-is. Veta does not minify or transform copied public assets. Use the `url` template function when linking copied assets from templates. It returns a path relative to the current page, so the generated site can be served from a domain root, a subdirectory, or static storage without a configured base URL: ```html Logo ``` ## Tailwind CSS Entrypoints The starter uses `public/styles.css`: ```css @import "tailwindcss"; ``` Configure it with: ```yaml tailwindcss: stylesheets: - styles.css minify: true ``` `stylesheets` entries are relative to `public/`, so `styles.css` means `public/styles.css`. You can configure multiple entrypoints: ```yaml tailwindcss: stylesheets: - styles.css - admin.css minify: true ``` ## Generated CSS Output Veta writes each compiled stylesheet to the build output using the same path: ```txt public/styles.css -> dist/styles.css public/admin.css -> dist/admin.css ``` Tailwind scans the materialized output directory, so classes used in generated HTML are included. If an entrypoint should scan a narrower set of files, configure that in the CSS file with Tailwind's `@source` directive. ## Minification `tailwindcss.minify: true` minifies the generated stylesheet through Tailwind CSS. This setting is separate from `html.minify`, which only affects generated `.html` files. ## Disabling Tailwind CSS Remove `tailwindcss.stylesheets` or leave it empty: ```yaml tailwindcss: stylesheets: [] minify: true ``` Without `stylesheets`, Veta does not run Tailwind CSS. ## Practical Pattern Use `public/styles.css` for your Tailwind entrypoint and keep images, fonts, and static files under `public/`: ```txt public/ styles.css fonts/inter.woff2 images/logo.svg robots.txt ``` --- # Themes Themes let you share templates, components, filters, functions, data, and public assets across projects. Configure a theme with `theme.source`: ```yaml theme: source: "./themes/clean" ``` ## What Themes Can Provide A theme can contain these top-level directories: ```txt templates/ components/ filters/ functions/ data/ public/ ``` Other top-level directories are ignored by the theme overlay. JavaScript files under `filters/`, `functions/`, and `data/` are trusted build code. Review remote themes before using them and prefer immutable tags or commit references for reproducible builds. ## Project Files Override Theme Files Veta composes the theme and project into one filesystem. Project files win over theme files. Example: ```txt theme/templates/base.j2 templates/base.j2 ``` The project's `templates/base.j2` overrides the theme template. This lets a project use most of a theme while customizing selected files. ## Local Themes Use a relative path: ```yaml theme: source: "./themes/blog" ``` The path is resolved from the project root. ## GitHub Themes Remote theme sources use a GitHub-style reference: ```yaml theme: source: "owner/repository@ref" ``` Use tags or commit references when you want reproducible builds. Veta caches remote themes under its runtime cache directory. ## Pages Stay In The Project Themes provide building blocks. Projects still declare the pages they want to output through `pages/*.js`. This keeps site structure explicit and prevents a theme from unexpectedly creating routes. ## Theme Data Themes can provide data files, but project data can override them. A common pattern is: ```txt theme/data/theme.json data/theme.json ``` The project file can customize names, colors, navigation, or other theme-facing values. Data overrides use the relative path without its extension. The project may therefore replace a theme data file while choosing a different supported format: ```txt theme: data/site.json project: data/site.yaml ``` Both files represent `data.site`, so only the project YAML file is loaded. Veta still rejects multiple files for the same data key within the project or within the theme, such as `data/site.json` and `data/site.yaml` side by side. ## Theme Configuration Defaults When building reusable themes, prefer exposing user-configurable defaults through `data/site_defaults.yaml` in the theme. Projects can then override only the values they care about with `data/site.yaml`: ```txt theme/data/site_defaults.yaml data/site.yaml ``` This keeps the theme defaults and project overrides available as separate template values: ```txt data.site_defaults data.site ``` Example theme defaults: ```yaml # theme/data/site_defaults.yaml name: "Clean Theme" description: "A clean Veta site." brand: color: "blue" logo: "/images/logo.svg" ``` Example project overrides: ```yaml # data/site.yaml name: "My Site" brand: color: "purple" ``` Then theme templates can prefer project values and fall back to theme defaults: ```html {% if data.site and data.site.name %} {{ data.site.name }} {% else %} {{ data.site_defaults.name }} {% endif %} ``` Use this pattern when you want partial project customization. If a theme and the project both provide the same logical data path, the project file replaces the theme file completely, even when their extensions differ. Veta does not deep-merge data files automatically. Keep fallbacks explicit in templates so theme behavior stays easy to understand. --- # Development Server `veta dev` starts a local development server with live reload. ```sh veta dev ``` By default, it serves at: ```txt http://127.0.0.1:3000/ ``` ## Config Configure the server in `veta.yaml`: ```yaml dev: host: 127.0.0.1 port: 3000 watch: - content ``` `host` changes the address Veta binds to. `port` changes the port. `watch` adds project-relative files or directories to the watcher. Directories are watched recursively. The only `veta dev` CLI flag is `--config`, which works like `veta build --config`: ```sh veta dev --config path/to/veta.yaml ``` ## Temporary Output The dev server does not write to `build.output`. Instead, it creates an OS temporary directory, builds the site there, serves that directory, and removes it on shutdown. This means running `veta dev` will not create or modify `dist/`. ## Rebuilds On startup, Veta performs a full build. When relevant project files change, it performs another full clean rebuild into the temporary directory. Veta watches: ```txt veta.yaml veta.yml .veta.yaml .veta.yml pages/ data/ templates/ components/ filters/ functions/ public/ ``` It also watches every path configured in `dev.watch`. The watcher is intentionally simple and predictable. It rebuilds the whole site rather than trying to cache partial work. Changes to `dev.host`, `dev.port`, or `dev.watch` require restarting `veta dev`, because those values define the running server and watcher. ## Live Reload Veta uses Server-Sent Events at: ```txt /_veta/live ``` When serving generated `.html` files, the dev server injects a small live reload script into the HTTP response. The script is not written to disk and never appears in production builds. ## Not A Production Server `veta dev` is for local development only. For production, run: ```sh veta build ``` Then deploy the output directory to a static hosting provider. --- # Build And Output Use `veta build` to create a production static site: ```sh veta build ``` Veta discovers the config file, derives the project root, loads data, and runs page generators. Generators explicitly parse Markdown or resolve components when needed. Veta then passes templated `content` unchanged to its selected template, keeps template-less `content` as raw output, writes generated files, copies public assets, and optionally runs Tailwind CSS. ## Output Directory Configure the output directory in `veta.yaml`: ```yaml build: output: dist ``` The path must be relative to the project. ## Clean Builds ```yaml build: clean: true ``` When enabled, Veta removes the output directory before writing new files. This avoids stale files from previous builds. ## Generated Files Page permalinks determine generated file paths: ```txt / -> dist/index.html /about/ -> dist/about/index.html /feed.xml -> dist/feed.xml /llms.txt -> dist/llms.txt ``` Veta validates output paths and rejects duplicate output files. ## Public Assets Files under `public/` are copied to the output root after generated files are prepared: ```txt public/robots.txt -> dist/robots.txt public/logo.svg -> dist/logo.svg ``` If a generated file and a public file claim the same output path, Veta fails the build. ## HTML Minification ```yaml html: minify: true ``` This minifies generated files with a `.html` extension. It does not minify: - `.xml` - `.md` - `.json` - `.txt` - `.js` - `.css` - copied files from `public/` ## Tailwind CSS When configured, Tailwind CSS runs after Veta writes the generated site and public files: ```yaml tailwindcss: stylesheets: - styles.css minify: true ``` This allows Tailwind to scan the final generated output and include classes used by templates, raw or explicitly parsed page content, and components explicitly resolved with `parse.renderComponents(text)`. ## Explicit Config File Build with an explicit config: ```sh veta build --config ./config/veta.yaml ``` The project root becomes the directory containing that config file. --- # Deployment Veta produces static files. There is no production server requirement. Build the site: ```sh veta build ``` Deploy the configured output directory, usually `dist/`. ## Generic Static Hosting Any host that can serve static files can serve a Veta site: ```txt dist/ index.html about/index.html styles.css robots.txt ``` Upload the contents of `dist/` to your host. ## CI Builds A typical CI job only needs to install Veta and run: ```sh veta build ``` Then publish `dist/` as the static artifact. ## npm Projects If Veta is installed as a development dependency, add scripts: ```json { "scripts": { "dev": "veta dev", "build": "veta build" }, "devDependencies": { "@varavel/veta": "latest" } } ``` ## Docker Builds You can build with Docker by mounting the project: ```sh docker run --rm -v "$PWD:/site" -w /site varavel/veta build ``` ## Production Reminder Do not run `veta dev` in production. It is a local development workflow that serves a temporary output directory and injects live reload scripts into served HTML responses. --- # Reference Use these pages when you need exact syntax or behavior. - [CLI Reference](/docs/reference/cli/) - [Config Reference](/docs/reference/config/) - [Page Generators Reference](/docs/reference/page-generators/) - [Template Context Reference](/docs/reference/template-context/) - [Troubleshooting](/docs/reference/troubleshooting/) --- # CLI Reference ## `veta` Shows help when called without arguments: ```sh veta ``` ## `veta init` Creates a starter project. ```sh veta init [PATH] ``` Examples: ```sh veta init veta init my-site ``` Flags: ```txt --force overwrite starter files that already exist ``` ## `veta dev` Starts the local development server with live reload. ```sh veta dev [--config FILE] ``` Examples: ```sh veta dev veta dev --config ./veta.yaml ``` Host, port, and additional watched paths are configured in `veta.yaml` under `dev`. ## `veta build` Builds the site for production. ```sh veta build [--config FILE] ``` Examples: ```sh veta build veta build --config ./config/veta.yaml ``` ## `veta version` Prints version information. ```sh veta version veta --version veta -v ``` ## Config Discovery `veta build` and `veta dev` search from the current directory upward for: ```txt veta.yaml veta.yml .veta.yaml .veta.yml ``` Use `--config` to bypass discovery. --- # Config Reference Veta config is YAML. Unknown fields are rejected. ## Example ```yaml build: output: dist clean: true html: minify: true dev: host: 127.0.0.1 port: 3000 watch: - content tailwindcss: stylesheets: - styles.css minify: true theme: source: "./theme" ``` ## `build.output` Type: string Default: `dist` The production output directory used by `veta build`. Must be a relative project path. ## `build.clean` Type: boolean Default: `false` When true, Veta removes the output directory before writing the build. ## `html.minify` Type: boolean Default: `false` When true, minifies generated `.html` files only. ## `dev.host` Type: string Default: `127.0.0.1` The local interface used by `veta dev`. ## `dev.port` Type: number Default: `3000` The TCP port used by `veta dev`. ## `dev.watch` Type: array of strings Default: `[]` Additional project-relative files or directories watched by `veta dev`. Directories are watched recursively. These paths are added to Veta's built-in watch set. Example: ```yaml dev: watch: - content - docs ``` ## `tailwindcss.stylesheets` Type: array of strings Default: `[]` When set, enables Tailwind CSS. Each path is relative to `public/`. Example: ```yaml tailwindcss: stylesheets: - styles.css - admin.css ``` This reads `public/styles.css` and `public/admin.css`, then writes generated CSS to `dist/styles.css` and `dist/admin.css`. ## `tailwindcss.minify` Type: boolean Default: `false` When true, passes Tailwind's minification flag to the embedded Tailwind CSS standalone CLI. ## `theme.source` Type: string Default: empty When set, resolves and composes a theme with the project. Examples: ```yaml theme: source: "./themes/blog" ``` ```yaml theme: source: "owner/veta-theme-name@v1.0.0" ``` > Note: The theme should match the owner/veta-theme-{name}@{ref} pattern. ## Supported File Names Veta checks these names in order: ```txt veta.yaml veta.yml .veta.yaml .veta.yml ``` --- # Page Generators Reference Page generators return arrays of page objects: ```js export default function({ parse }) { const { html } = parse.markdown("# Home"); return [ { permalink: "/", template: "base", title: "Home", content: html, }, ]; } ``` ## `permalink` Required: yes Type: string The public URL path for the generated page. Examples: ```txt / /about/ /feed.xml /llms.txt ``` ## `template` Required: no Type: string Template name relative to `templates/`. Examples: ```js template: "base"; template: "pages/article.j2"; ``` Do not prefix with `templates/`. If omitted, the page is written as raw content. ## `content` Required: no Type: string Defaults to an empty string. For templated pages, content is passed unchanged and trusted to the selected template. Veta does not automatically render Markdown or resolve component tags. The generator must return the final format expected by the template, usually HTML. Use `parse.markdown(text)` and `parse.renderComponents(text)` explicitly when needed. For template-less pages, content is written unchanged as raw output. It can contain HTML, Markdown, JSON, XML, text, or any other generated format. A common content-file flow is: ```js const { frontmatter, html } = parse.markdown(files.readFile(path)); const content = parse.renderComponents(html); return { permalink: files.toPermalink(path, { stripPrefix: "content" }), template: "post", title: frontmatter.title, content, }; ``` ## Extra Fields Any extra fields are preserved and exposed as `page` in templates: ```js { permalink: "/posts/hello/", template: "post", title: "Hello", date: "2026-06-30", tags: ["guide"], content: "

Hello

", } ``` Template: ```html ``` ## Normalized Fields Veta also exposes normalized fields on `page`: ```txt content generator index outputPath permalink template ``` `generator` is the page generator file path. `index` is the page's index in that generator's returned array. `outputPath` is the generated file path inside the output directory. ## Removed Field: `layout` `layout` is rejected. Use `template` instead. --- # Template Context Reference Veta templates receive a small root context: ```txt data pages page props ``` ## `data` Global data loaded from `data/`. ```html {{ data.site.name }} {{ data.navigation.main }} ``` ## `pages` Array of all normalized pages. ```html {% for item in pages %} {{ item.title }} {% endfor %} ``` Each item includes the original page fields plus normalized fields such as `permalink`, `outputPath`, `template`, `generator`, and `index`. The complete `pages` list exists after page generators have returned. It is not available while a generator is still creating that list. ## `page` The current normalized page. ```html

{{ page.title }}

{{ page.content }} ``` For templated pages, `page.content` is the generator's unchanged, trusted string. Veta does not automatically render Markdown or resolve components before template rendering. ## `props` Component props. In page templates, `props` is usually empty. In component templates, `props` contains tag attributes and `props.content`: ```html ``` Component context depends on where `parse.renderComponents(text)` is called. Page generators provide global `data`, but not `page` or `pages` because those pages are still being created. A context-bound JavaScript template function can pass its available runtime `page` and `pages` values into component rendering. Tag attributes and slot content always supply the rendered component's `props`. ## Template Helpers Pongo templates and components can call built-in and custom template functions. `load_data` reads local or remote data: ```html {% set navigation = load_data("data/navigation.yaml")|parse_yaml %} {% set site = load_data("data/site.json")|parse_json %} ``` See [Template Functions](/docs/api/template-functions/) for details. They can also call `url` to generate current-page-relative links: ```html {{ page.title }} ``` Custom functions from `functions/*.js` are available by file stem: ```html {{ excerpt(page.content, 120) }} ``` --- # Troubleshooting This page explains common errors and the shortest path to fixing them. ## Config File Not Found Veta looks for: ```txt veta.yaml veta.yml .veta.yaml .veta.yml ``` It searches from the current directory upward. Fixes: ```sh veta init veta build --config ./veta.yaml veta dev --config ./veta.yaml ``` ## Invalid Page Object Every page must be an object with a string `permalink`. Valid: ```js { permalink: "/about/", template: "base", content: "About" } ``` If you see an error about `layout`, rename it to `template`. ## Template Not Found Page templates are relative to `templates/`: ```js template: "base"; ``` Do not write: ```js template: "templates/base.html"; ``` If extensionless lookup is ambiguous, include the extension: ```js template: "base.j2"; ``` ## Duplicate Output Path Two pages cannot generate the same output file. These conflict: ```js { permalink: "/about/"; } { permalink: "/about/index.html"; } ``` Change one permalink. ## Tailwind Input Missing If `tailwindcss.stylesheets` includes `styles.css`, Veta expects: ```txt public/styles.css ``` Create the file or remove it from `tailwindcss.stylesheets` to disable that Tailwind CSS entrypoint. ## Public Asset Collision Generated files and public files share the same output namespace. These conflict: ```txt page permalink: /robots.txt public/robots.txt ``` Move one of them. ## JavaScript Promise Error Veta JavaScript is synchronous. Do not use `async` default exports or return promises. Use synchronous `httpClient` calls instead: ```js export default function({ httpClient, parse }) { const response = httpClient.get("https://example.com/data.json"); return parse.json(response.body); } ``` ## Frontmatter Error Frontmatter must start on the first line and close with the same delimiter: ```md --- title: Hello --- # Hello ``` Use `---` for YAML and `+++` for TOML. --- # API Veta exposes a small synchronous JavaScript API to data files, page generators, and filters. It also registers template helpers for Pongo templates and components. - [JavaScript API](/docs/api/javascript/) - [File API](/docs/api/files/) - [HTTP Client](/docs/api/http-client/) - [Parse API](/docs/api/parse/) - [Template Functions](/docs/api/template-functions/) - [Environment And Console](/docs/api/environment-and-console/) - [Markdown Frontmatter](/docs/api/frontmatter/) --- # JavaScript API Veta uses JavaScript for three kinds of project files: ```txt data/*.js -> global data producers pages/*.js -> page generators filters/*.js -> template filters functions/*.js -> template functions ``` JavaScript files are self-contained and synchronous. They do not use imports, module loading, or asynchronous promises. Each file must export one default function. ## Runtime Context The default export receives a context object as its first argument. Common context keys: ```txt files httpClient parse env ``` Additional context keys depend on where the file runs. ## `data/*.js` Data files run while global data is being loaded, so they do not receive `data`. ```js export default function({ env, files, httpClient }) { return { mode: env.VETA_MODE || "production", }; } ``` Return any JSON-compatible value. The value becomes part of `data` using the file path as its key. Example: ```txt data/github.js -> data.github ``` ## `pages/*.js` Page generators receive loaded global data: ```js export default function({ data, parse }) { const { html } = parse.markdown("# Home"); return [ { permalink: "/", template: "base", title: data.site.name, content: html, }, ]; } ``` Return an array of page objects. Templated content is passed unchanged and trusted to its template, while template-less content is written unchanged as raw output. Use `parse.markdown(text)` and `parse.renderComponents(text)` explicitly when a generator needs those transformations. ## `filters/*.js` Filters receive the runtime context, the input value, and one optional parameter: ```js export default function({ data }, input, parameter) { const prefix = parameter || data.site.name; return `${prefix}: ${input}`; } ``` ## `functions/*.js` Template functions receive the runtime context followed by explicit template arguments: ```js export default function({ page, data, files, parse }, value, length) { return String(value || page.title).slice(0, Number(length)); } ``` Use the file stem as a function in Pongo templates and components: ```html {{ excerpt(page.content, 120) }} ``` Use it in a template: ```html {{ page.title|prefix:"Post" }} ``` ## No Global `Veta` Veta does not expose runtime APIs through a global `Veta` object. Always use the context argument: ```js export default function({ files }) { return files.listFiles("content/**/*.md"); } ``` ## Console The `console` object is available as a JavaScript global: ```js export default function() { console.log("Generating pages"); return []; } ``` Supported methods are `debug`, `error`, `info`, `log`, and `warn`. ## Execution Model Veta executes JavaScript synchronously. Promise-like return values are rejected. Use synchronous calls only: ```js export default function({ httpClient, parse }) { const response = httpClient.get("https://example.com/data.json"); return parse.json(response.body); } ``` Do not return a Promise: ```js export default async function() { return []; } ``` ## API Pages - [File API](/docs/api/files/) - [HTTP Client](/docs/api/http-client/) - [Parse API](/docs/api/parse/) - [Environment And Console](/docs/api/environment-and-console/) - [Frontmatter](/docs/api/frontmatter/) --- # File API The file API is available as `files` in JavaScript context objects. ```js export default function({ files }) { return files.listFiles("content/**/*.md"); } ``` All paths are relative to the project root. Absolute paths and `..` path traversal are rejected. ## `files.listFiles(pattern)` Returns sorted project-relative file paths matching a glob pattern. ```js const posts = files.listFiles("content/posts/**/*.md"); ``` Use `files.listFiles(".")` to list every file in the project. Empty patterns are rejected. ## `files.readFile(path)` Reads a file as a UTF-8 string. ```js const robots = files.readFile("public/robots.txt"); const site = parse.json(files.readFile("data/site.json")); const { frontmatter, html } = parse.markdown( files.readFile("content/posts/hello.md"), ); const content = parse.renderComponents(html); ``` `files.readFile` performs no parsing or rendering. In this example, `frontmatter` contains metadata, `html` is the rendered Markdown body, and `content` is the result of explicitly resolving registered components. See [Parse API](/docs/api/parse/) and [Frontmatter](/docs/api/frontmatter/) for details. ## `files.toPermalink(path, options)` Converts a project-relative path into a pretty permalink. ```js files.toPermalink("content/posts/hello.md", { stripPrefix: "content" }); // "/posts/hello/" ``` If the source file is an `index` file, the last segment is removed: ```js files.toPermalink("content/docs/index.md", { stripPrefix: "content" }); // "/docs/" ``` Options: ```js { stripPrefix: "content"; } ``` `stripPrefix` is optional. When present, Veta removes it as a complete path segment before generating the permalink. The source path must have that prefix. ## Security Rules The file API rejects: - empty file paths - absolute paths - Windows drive paths - paths containing `..` - symlink escapes outside the configured root This keeps JavaScript file access confined to the project root. --- # HTTP Client The HTTP client is available as `httpClient` in JavaScript context objects. It is synchronous and supports HTTP and HTTPS URLs only. ## Shortcut Methods ```js httpClient.get(url, options); httpClient.post(url, options); httpClient.put(url, options); httpClient.patch(url, options); httpClient.delete(url, options); httpClient.head(url, options); ``` Example: ```js export default function({ httpClient, parse }) { const response = httpClient.get( "https://api.github.com/repos/varavelio/veta", { headers: { Accept: "application/vnd.github+json", }, }, ); if (!response.ok) { throw new Error(`GitHub returned ${response.status}`); } return parse.json(response.body); } ``` ## Explicit Request Method ```js httpClient.request("GET", "https://example.com/data.json"); ``` The method is trimmed and uppercased. Empty methods or methods containing whitespace are rejected. ## Options ```js { headers: { "Accept": "application/json", "X-Trace": ["one", "two"] }, body: "raw body", timeoutMs: 5000 } ``` `body` must be a string. For JSON request bodies, use `JSON.stringify` and set the content type yourself: ```js httpClient.post("https://example.com/api", { body: JSON.stringify({ message: "hello" }), headers: { "Content-Type": "application/json" }, }); ``` `timeoutMs` must be a positive number. The default timeout is 30 seconds. ## Response Shape ```js { body: "...", headers: { "Content-Type": ["application/json"] }, ok: true, status: 200, statusText: "OK", url: "https://example.com/data.json" } ``` `ok` is `true` for status codes from 200 through 299. `body` is always a string. Use `parse.json`, `parse.yaml`, or another parser when you need structured data. ## Development Advice `veta dev` performs a full rebuild on file changes. If a data script fetches slow remote APIs, it will fetch them again on rebuild. For development, consider using `env` to return local mock data. --- # Parse API The parse API is available as `parse` in JavaScript context objects. It parses structured text, renders Markdown bodies, and explicitly resolves component tags. File and HTTP APIs return text; call the required operations in the order your output needs. ```js export default function({ files, parse }) { const { frontmatter, html } = parse.markdown( files.readFile("content/posts/hello.md"), ); const content = parse.renderComponents(html); return [ { permalink: "/posts/hello/", template: "post", title: frontmatter.title, content, }, ]; } ``` ## `parse.json(text)` Parses one JSON value. Multiple JSON values are rejected. ```js const site = parse.json("{\"title\":\"Veta\"}"); ``` ## `parse.yaml(text)` Parses one YAML document. Multiple YAML documents are rejected. ```js const navigation = parse.yaml("items:\n - label: Docs\n"); ``` ## `parse.toml(text)` Parses one TOML document. ```js const theme = parse.toml("name = \"Clean\"\n"); ``` ## `parse.markdown(text)` Parses optional YAML or TOML frontmatter and renders the Markdown body to HTML. ```js const post = parse.markdown(files.readFile("content/posts/hello.md")); ``` Return shape: ```js { frontmatter: { title: "Hello" }, content: "# Hello\n\nPost body.\n", html: "

Hello

\n

Post body.

\n" } ``` - `frontmatter` is the parsed object. - `content` is the raw body after frontmatter is removed. - `html` is the Markdown-rendered body. Without frontmatter, `frontmatter` is `{}`, `content` is the full input, and `html` is the full input rendered as Markdown. Raw HTML is preserved as trusted author content. HTML-like opening tags may span lines and can end with either `>` or `/>`; quoted `>` characters inside attributes do not end the tag. This allows paired and self-closing component invocations to survive until an explicit `parse.renderComponents` call. ## `parse.renderComponents(text)` Resolves registered component tags in any supplied string and returns the transformed string: ```js const content = parse.renderComponents( "Check the configuration.", ); ``` Only registered tags are resolved; other tags remain unchanged. Props, slot content, nested components, Pongo component context, includes, and inheritance work as they do elsewhere. This operation does not render Markdown. Component-like text remains unchanged inside HTML attributes, comments, raw-text and code elements such as `script`, `style`, `code`, `pre`, `textarea`, and `title`, as well as Markdown inline code and fenced code blocks. Component template output is not scanned again, which keeps rendering a bounded, one-pass transformation. Excessively deep input or recursive calls from component template functions fail with a controlled render-limit error. The caller controls ordering. For a Markdown file that may contain component tags, the recommended page-generator flow is: ```js const { frontmatter, html } = parse.markdown(files.readFile(path)); const content = parse.renderComponents(html); return { permalink: files.toPermalink(path, { stripPrefix: "content" }), template: "post", title: frontmatter.title, content, }; ``` When called by a page generator, component templates receive global `data`; `page` and `pages` do not exist yet because the generator is creating the page list. When a context-bound JavaScript template function calls it, available runtime `page` and `pages` values can flow into component rendering. Props come from each tag's attributes and slot content. ## Pongo Filter Distinction The Pongo `parse_markdown` filter is unchanged: it returns `{ content, frontmatter }` and does not render Markdown. Use Pongo's separate `markdown` filter to render that `content`. JavaScript `parse.markdown(text)` returns the additional `html` field described above. Parsed values are normalized into JavaScript-compatible values. Dates are exposed as strings. --- # Template Functions Veta registers template functions for Pongo templates and components. Built-in functions are always available: - `url` - `regex_replace` - `load_data` Custom functions live in `functions/*.js` and use the file stem as the template function name. ## `url` `url` returns a portable URL for an internal root-relative path from the current page. ```html Logo Current page ``` From `/docs/intro/`, `url("/styles.css")` returns `../../styles.css`. From `/`, it returns `styles.css`. External URLs, fragment-only URLs, and already-relative URLs are returned unchanged. ## `regex_replace` `regex_replace` replaces text with a Go regular expression: ```html {{ regex_replace("World Hello", "(\\w+) (\\w+)", "$2 $1") }} ``` The result can be rendered directly or assigned with `set`: ```html {% set slug = regex_replace(page.title, "[^a-zA-Z0-9]+", "-") %}

{{ page.title }}

``` Invalid regular expressions fail the build. ## `load_data` `load_data` reads a local project file or a remote URL as text from a template, include, or component. Use `load_data` inside native Pongo expressions. Assign values with Pongo's built-in `set` tag: ```html {% set navigation = load_data("data/navigation.yaml")|parse_yaml %} {% for item in navigation.items %} {{ item.label }} {% endfor %} ``` Without a parse filter, `load_data` returns a string: ```html {% set readme = load_data("content/readme.md") %} {{ readme|markdown }} ``` ### Local Files Local paths are project-relative and can read files from the composed project and theme filesystem: ```html {% set badge = load_data("data/badge.toml")|parse_toml %} {{ badge.label }} ``` Local paths must be relative. Absolute paths, Windows drive paths, empty paths, and paths containing `..` are rejected. ### Remote URLs Remote URLs use HTTP `GET`: ```html {% set repo = load_data("https://api.github.com/repos/varavelio/veta")|parse_json %} {{ repo.stargazers_count }} ``` Only `http` and `https` URLs are allowed. Non-2xx responses fail the build. ### Parse Filters Use parse filters to convert loaded text into structured values: - `parse_json` - `parse_yaml` - `parse_toml` - `parse_markdown` ```html {% set message = load_data("content/message.txt") %} {% set site = load_data("data/site.json")|parse_json %} {% set navigation = load_data("data/navigation.yaml")|parse_yaml %} {% set theme = load_data("data/theme.toml")|parse_toml %} ``` Parsed values return normal template values: ```html {{ site.title }} {{ navigation.items.0.label }} {{ theme.colors.primary }} ``` ## Custom Functions Custom functions are synchronous JavaScript files in `functions/`. Each file must export one default function: ```js // functions/excerpt.js export default function({ page }, value, length) { console.log("excerpt", page.permalink); return String(value).slice(0, Number(length)); } ``` Use the file stem as the function name: ```html {{ excerpt(page.content, 120) }} ``` The first argument is the JavaScript runtime context. Template functions receive `data`, `pages`, `page`, `props`, `files`, `httpClient`, `parse`, and `env`. `console` is available as a JavaScript global, not as `context.console`. If a template function calls `parse.renderComponents(text)`, its context-bound `data`, `page`, and `pages` values can flow into component templates. Each component tag still supplies its own attributes and slot content through `props`. This differs from page-generator calls, where `page` and `pages` are not available because the generator is still creating the page list. Function files are flat. Nested directories under `functions/` are not supported. A custom function can override a built-in function by using the same file stem. --- # Environment And Console Veta exposes environment variables and console methods to JavaScript files. ## `env` `env` is an object containing string environment variables captured from the process running Veta. ```js export default function({ env }) { return { mode: env.VETA_MODE || "production", }; } ``` Use environment variables for secrets, deployment settings, branch names, or development toggles. Do not commit secrets into `data/` or `pages/`. ## `console` The console API is available as a JavaScript global. It is not part of the default export context object. Supported methods: ```txt console.debug console.error console.info console.log console.warn ``` Example: ```js export default function() { console.info("Generating pages"); return []; } ``` CLI output is prefixed with the log level: ```txt [js info] Generating pages ``` Objects and arrays are rendered as JSON-like output. --- # Markdown Frontmatter `parse.markdown(text)` in JavaScript and `parse_markdown` in Pongo templates support optional frontmatter at the start of a Markdown string. Their return shapes differ: JavaScript also renders the body into an `html` field, while the Pongo filter keeps `{ content, frontmatter }`. Supported delimiters: ```txt --- YAML +++ TOML ``` Frontmatter is detected only when the first line is exactly `---` or `+++`. ## YAML Frontmatter ```md --- title: Hello draft: false tags: - guide - intro --- # Hello Body. ``` ## TOML Frontmatter ```md +++ title = "Hello" draft = false tags = ["guide", "intro"] [meta] author = "Veta" +++ # Hello Body. ``` ## Return Shape ```js const post = parse.markdown(files.readFile("content/posts/hello.md")); ``` ```js { frontmatter: { title: "Hello", draft: false, tags: ["guide", "intro"] }, content: "# Hello\n\nBody.\n", html: "

Hello

\n

Body.

\n" } ``` `content` is the raw body, and `html` is the Markdown-rendered body. One blank line immediately after the closing delimiter is removed from `content` before `html` is rendered. ## Files Without Frontmatter ```md # Plain Markdown No frontmatter. ``` Returns: ```js { frontmatter: {}, content: "# Plain Markdown\n\nNo frontmatter.\n", html: "

Plain Markdown

\n

No frontmatter.

\n" } ``` Without frontmatter, `content` is the full input. ## Pongo `parse_markdown` The Pongo filter remains a frontmatter parser only: ```html {% set post = load_data("content/posts/hello.md")|parse_markdown %} {{ post.content|markdown }} ``` It returns `{ content, frontmatter }`; use the separate `markdown` filter to produce HTML. ## Validation Veta rejects: - missing closing delimiters - malformed YAML - malformed TOML - frontmatter that does not parse to an object - multiple YAML documents - non-finite numbers such as `NaN` or `Inf` - maps with non-string keys Parsed values are normalized into JavaScript-compatible values. Dates are exposed as strings.