{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://schemas.zeropress.dev/build-pages-config/v0.7/schema.json",
  "title": "ZeroPress Build Pages Config v0.7",
  "description": "Optional source-directory configuration for @zeropress/build-pages workflows.",
  "markdownDescription": "Optional source-directory configuration for `@zeropress/build-pages` workflows.",
  "type": "object",
  "additionalProperties": false,
  "required": ["version"],
  "properties": {
    "$schema": {
      "type": "string",
      "description": "Optional JSON Schema URI or path used by editors and tooling.",
      "markdownDescription": "Optional JSON Schema URI or path used by editors and tooling."
    },
    "version": {
      "type": "string",
      "const": "0.7",
      "description": "ZeroPress Build Pages config format version.",
      "markdownDescription": "ZeroPress Build Pages config format version."
    },
    "site": {
      "$ref": "#/$defs/site"
    },
    "markdown": {
      "$ref": "#/$defs/markdown"
    },
    "front_page": {
      "$ref": "#/$defs/frontPage"
    },
    "custom_html": {
      "$ref": "#/$defs/customHtml"
    },
    "menus": {
      "$ref": "#/$defs/menus"
    },
    "collections": {
      "$ref": "#/$defs/collections"
    }
  },
  "$defs": {
    "site": {
      "type": "object",
      "additionalProperties": false,
      "description": "User-facing site metadata used by Build Pages.",
      "markdownDescription": "User-facing site metadata used by Build Pages.",
      "properties": {
        "title": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S"
        },
        "description": {
          "type": "string"
        },
        "url": {
          "type": "string",
          "anyOf": [
            {
              "const": ""
            },
            {
              "format": "uri",
              "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://(?:\\[[0-9A-Fa-f:.]+\\]|[^/?#:@]+)(?::[0-9]+)?/?$"
            }
          ],
          "allOf": [
            { "not": { "pattern": "[\\\\\\s\\p{Cc}]" } },
            { "not": { "pattern": "%(?![0-9A-Fa-f]{2})" } },
            { "not": { "pattern": "%(?:0[0-9A-Fa-f]|1[0-9A-Fa-f]|7[Ff]|5[Cc])" } }
          ],
          "x-zeropress-runtime-url-validation": "WHATWG URL parsing validates hostname, port range, and IP-address syntax.",
          "description": "Canonical HTTP(S) site URL at the origin root. Paths, queries, fragments, and subdirectory hosting are not supported. Use an empty string or omit this field when the deployment URL is not known.",
          "markdownDescription": "Canonical HTTP(S) site URL at the origin root. Paths, queries, fragments, and subdirectory hosting are not supported. Use an empty string or omit this field when the deployment URL is not known."
        },
        "logo": {
          "$ref": "#/$defs/siteLogo"
        },
        "locale": {
          "type": "string",
          "minLength": 2,
          "pattern": "^\\S+$",
          "description": "Site locale used for generated preview-data language metadata, such as html lang and feed language.",
          "markdownDescription": "Site locale used for generated preview-data language metadata, such as HTML `lang` and feed language."
        },
        "expose_generator": {
          "type": "boolean",
          "default": true,
          "description": "Whether generated HTML should expose the ZeroPress generator meta tag. Set false for white-label sites.",
          "markdownDescription": "Whether generated HTML should expose `<meta name=\"generator\" content=\"ZeroPress\">`. Set `false` for white-label sites."
        },
        "search": {
          "type": "boolean",
          "default": true,
          "description": "Whether native ZeroPress search should be enabled when the selected theme supports search UI. Set false to omit native search artifacts and hide theme search UI.",
          "markdownDescription": "Whether native ZeroPress search should be enabled when the selected theme supports search UI. Set `false` to omit native search artifacts and hide theme search UI."
        },
        "robots": {
          "$ref": "#/$defs/siteRobots"
        },
        "footer": {
          "$ref": "#/$defs/siteFooter"
        },
        "meta": {
          "$ref": "#/$defs/previewMeta"
        }
      }
    },
    "siteRobots": {
      "type": "object",
      "additionalProperties": false,
      "required": ["allow_indexing"],
      "properties": {
        "allow_indexing": {
          "type": "boolean",
          "default": true,
          "description": "Fallback robots.txt indexing policy. Missing site.robots means true; false disallows all agents unless the source directory provides robots.txt.",
          "markdownDescription": "Fallback `robots.txt` indexing policy. Missing `site.robots` means `true`; `false` disallows all agents unless the source directory provides `robots.txt`."
        }
      }
    },
    "siteLogo": {
      "type": "object",
      "additionalProperties": false,
      "required": ["src"],
      "description": "Optional site logo data copied into generated preview-data.",
      "markdownDescription": "Optional site logo data copied into generated preview-data.",
      "properties": {
        "src": {
          "type": "string",
          "minLength": 1,
          "pattern": "^(?!\\s)(?!.*\\s$).+$",
          "anyOf": [
            {
              "pattern": "^/(?!/)"
            },
            {
              "format": "uri",
              "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://(?:[^/?#@]+@)?(?:\\[[0-9A-Fa-f:.]+\\]|[^/?#:@]+)(?::[0-9]+)?(?:[/?#].*)?$"
            }
          ],
          "allOf": [
            { "not": { "pattern": "[\\\\\\s\\p{Cc}]" } },
            { "not": { "pattern": "%(?![0-9A-Fa-f]{2})" } },
            { "not": { "pattern": "%(?:0[0-9A-Fa-f]|1[0-9A-Fa-f]|7[Ff]|5[Cc])" } }
          ],
          "x-zeropress-runtime-url-validation": "WHATWG URL parsing validates hostname, port range, and IP-address syntax.",
          "description": "Logo URL copied into preview-data. Use a root-relative public path such as /logo.svg, or an absolute HTTP(S) URL. Document-relative paths are not supported.",
          "markdownDescription": "Logo URL copied into preview-data. Use a root-relative public path such as `/logo.svg`, or an absolute HTTP(S) URL. Document-relative paths such as `./logo.svg` and `../logo.svg` are not supported."
        },
        "alt": {
          "type": "string",
          "description": "Optional logo alternative text. Themes may fall back to site.title when omitted.",
          "markdownDescription": "Optional logo alternative text. Themes may fall back to `site.title` when omitted."
        }
      }
    },
    "siteFooter": {
      "type": "object",
      "additionalProperties": false,
      "description": "Optional footer display data copied into generated preview-data.",
      "markdownDescription": "Optional footer display data copied into generated preview-data.",
      "properties": {
        "copyright_text": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S",
          "description": "Footer copyright or legal text. ZeroPress does not add a copyright symbol automatically.",
          "markdownDescription": "Footer copyright or legal text. ZeroPress does not add a copyright symbol automatically."
        },
        "attribution": {
          "type": "boolean",
          "description": "When false, bundled themes hide the Published with ZeroPress attribution. Missing or true means attribution may be shown.",
          "markdownDescription": "When `false`, bundled themes hide the Published with ZeroPress attribution. Missing or `true` means attribution may be shown."
        }
      }
    },
    "markdown": {
      "type": "object",
      "additionalProperties": false,
      "description": "Markdown source processing options.",
      "markdownDescription": "Markdown source processing options.",
      "properties": {
        "updated_at": {
          "type": "string",
          "enum": ["none", "git"],
          "default": "none",
          "description": "Optional page update timestamp enrichment. none does not generate updated_at_iso values; git reads the latest commit date for each Markdown file.",
          "markdownDescription": "Optional page update timestamp enrichment. `none` does not generate `updated_at_iso` values; `git` reads the latest commit date for each Markdown file."
        },
        "link_output": {
          "type": "string",
          "enum": ["clean", "html"],
          "default": "clean",
          "description": "Controls how source-relative Markdown links are rewritten. clean uses generated clean URLs; html uses explicit HTML file URLs.",
          "markdownDescription": "Controls how source-relative Markdown links are rewritten. `clean` uses generated clean URLs; `html` uses explicit HTML file URLs."
        }
      }
    },
    "frontPage": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type"],
      "description": "Source file or theme template used for the site root.",
      "markdownDescription": "Source file or theme template used for the site root.",
      "properties": {
        "type": {
          "type": "string",
          "enum": ["theme_index", "markdown", "html"],
          "default": "theme_index",
          "description": "Front page source mode. theme_index uses theme/index.html. markdown and html read a source file.",
          "markdownDescription": "Front page source mode. `theme_index` uses `theme/index.html`. `markdown` and `html` read a source file."
        },
        "file": {
          "$ref": "#/$defs/sourceFilePath",
          "description": "Optional source-root relative file path. Defaults to index.md for markdown and .zeropress/index.html for html.",
          "markdownDescription": "Optional source-root relative file path. Defaults to `index.md` for `markdown` and `.zeropress/index.html` for `html`."
        },
        "layout": {
          "type": "boolean",
          "default": true,
          "description": "Only applies to html front pages. true renders the file as page body content through the ZeroPress layout; false treats it as a full standalone HTML document.",
          "markdownDescription": "Only applies to `html` front pages. `true` renders the file as page body content through the ZeroPress layout; `false` treats it as a full standalone HTML document."
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "type": { "const": "theme_index" }
            },
            "required": ["type"]
          },
          "then": {
            "not": {
              "anyOf": [
                { "required": ["file"] },
                { "required": ["layout"] }
              ]
            }
          }
        },
        {
          "if": {
            "properties": {
              "type": { "const": "markdown" }
            },
            "required": ["type"]
          },
          "then": {
            "not": {
              "required": ["layout"]
            },
            "properties": {
              "file": {
                "type": "string",
                "pattern": "\\.md$"
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "type": { "const": "html" }
            },
            "required": ["type"]
          },
          "then": {
            "properties": {
              "file": {
                "type": "string",
                "pattern": "^\\.zeropress/.+\\.html$"
              }
            }
          }
        }
      ]
    },
    "sourceFilePath": {
      "type": "string",
      "minLength": 1,
      "pattern": "^(?!\\s)(?!.*\\s$)(?!.*\\p{Cc})(?!/)(?!.*\\\\)(?!.*//)(?!.*(?:^|/)\\.{1,2}(?:/|$))(?!.*[?#]).+$",
      "description": "Exact source-root relative path. Leading or trailing whitespace, backslashes, empty or dot path segments, query strings, hash fragments, and parent-directory traversal are not allowed.",
      "markdownDescription": "Exact source-root relative path. Leading or trailing whitespace, backslashes, empty or dot path segments, query strings, hash fragments, and parent-directory traversal are not allowed. File-extension matching is case-sensitive."
    },
    "menuUrl": {
      "description": "Credential-free absolute HTTP(S) URL or safe single-slash root-relative Web path. Query and fragment are allowed.",
      "markdownDescription": "Credential-free absolute HTTP(S) URL or safe single-slash root-relative Web path. Query and fragment are allowed. Bare, dot-relative, protocol-relative, dot-segment, whitespace, backslash, control, and malformed percent-escape values are not allowed.",
      "x-zeropress-runtime-url-validation": "WHATWG URL parsing validates hostname, port range, and IP-address syntax.",
      "anyOf": [
        {
          "type": "string",
          "format": "uri",
          "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://(?:\\[[0-9A-Fa-f:.]+\\]|[^/?#:@]+)(?::[0-9]+)?(?:[/?#].*)?$",
          "allOf": [
            { "not": { "pattern": "[\\\\\\s\\p{Cc}]" } },
            { "not": { "pattern": "%(?![0-9A-Fa-f]{2})" } },
            { "not": { "pattern": "%(?:0[0-9A-Fa-f]|1[0-9A-Fa-f]|7[Ff]|5[Cc])" } }
          ]
        },
        {
          "type": "string",
          "minLength": 1,
          "pattern": "^(?!//)/(?!\\.{1,2}(?:/|[?#]|$))(?!.*(?:^|/)\\.{1,2}(?:/|[?#]|$))(?!.*[\\\\\\s\\p{Cc}])(?!.*%(?![0-9A-Fa-f]{2}))(?!.*%(?:0[0-9A-Fa-f]|1[0-9A-Fa-f]|7[Ff]|5[Cc]))[^?#]*(?:\\?[^#]*)?(?:#.*)?$"
        }
      ]
    },
    "customHtml": {
      "type": "object",
      "additionalProperties": false,
      "minProperties": 1,
      "description": "Trusted HTML source files materialized as raw preview-data custom_html strings. Each file must be nonblank and no longer than 65,536 Unicode code points.",
      "markdownDescription": "Trusted HTML source files materialized as raw preview-data `custom_html` strings. Each file must be nonblank and no longer than 65,536 Unicode code points.",
      "properties": {
        "head_end": {
          "$ref": "#/$defs/customHtmlSlot",
          "description": "HTML file injected before </head> by ZeroPress build.",
          "markdownDescription": "HTML file injected before `</head>` by ZeroPress build."
        },
        "body_end": {
          "$ref": "#/$defs/customHtmlSlot",
          "description": "HTML file injected before </body> by ZeroPress build.",
          "markdownDescription": "HTML file injected before `</body>` by ZeroPress build."
        }
      }
    },
    "customHtmlSlot": {
      "type": "object",
      "additionalProperties": false,
      "required": ["file"],
      "properties": {
        "file": {
          "allOf": [
            { "$ref": "#/$defs/sourceFilePath" },
            {
              "type": "string",
              "pattern": "^\\.zeropress/.+\\.html$"
            }
          ],
          "description": "Prebuild-only HTML file inside .zeropress/. Its raw content is preserved after nonblank and 65,536-code-point validation.",
          "markdownDescription": "Prebuild-only HTML file inside `.zeropress/`. Its raw content is preserved after nonblank and 65,536-code-point validation."
        }
      }
    },
    "menus": {
      "type": "object",
      "description": "Menu definitions copied into generated preview-data. Omitting menus materializes the built-in primary menu containing Home; an explicit empty object materializes no menus.",
      "markdownDescription": "Menu definitions copied into generated preview-data. Omitting `menus` materializes the built-in `primary` menu containing Home; an explicit `{}` materializes no menus.",
      "propertyNames": {
        "type": "string",
        "pattern": "^[a-z][a-z0-9_-]{0,63}$"
      },
      "additionalProperties": {
        "$ref": "#/$defs/menu"
      }
    },
    "previewMeta": {
      "type": "object",
      "additionalProperties": {
        "type": ["string", "number", "boolean", "null"]
      }
    },
    "menu": {
      "type": "object",
      "additionalProperties": false,
      "required": ["items"],
      "description": "A menu definition. When name is omitted, the menu id is materialized as its name in resolved config and Preview Data.",
      "markdownDescription": "A menu definition. When `name` is omitted, the menu id is materialized as its name in resolved config and Preview Data.",
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S",
          "description": "Display name. Omission materializes the containing menu id."
        },
        "items": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/menuItem"
          }
        }
      }
    },
    "menuItem": {
      "type": "object",
      "additionalProperties": false,
      "required": ["title", "url"],
      "description": "Menu item copied into generated preview-data.",
      "markdownDescription": "Menu item copied into generated preview-data.",
      "properties": {
        "title": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S"
        },
        "url": {
          "$ref": "#/$defs/menuUrl"
        },
        "target": {
          "type": "string",
          "enum": ["_self", "_blank"],
          "default": "_self"
        },
        "meta": {
          "$ref": "#/$defs/previewMeta"
        },
        "children": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/menuItem"
          },
          "default": []
        }
      }
    },
    "collections": {
      "type": "object",
      "description": "Reading-order collections generated from Markdown source paths. Omission and an explicit empty object both materialize no collections.",
      "markdownDescription": "Reading-order collections generated from Markdown source paths. Omission and an explicit `{}` both materialize no collections.",
      "propertyNames": {
        "type": "string",
        "pattern": "^[a-z][a-z0-9_-]{0,63}$"
      },
      "additionalProperties": {
        "$ref": "#/$defs/collection"
      }
    },
    "collection": {
      "type": "object",
      "additionalProperties": false,
      "required": ["items"],
      "description": "A reading-order collection. When title is omitted, the collection id is materialized as its title in resolved config and Preview Data.",
      "markdownDescription": "A reading-order collection. When `title` is omitted, the collection id is materialized as its title in resolved config and Preview Data.",
      "properties": {
        "title": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S",
          "description": "Display title. Omission materializes the containing collection id."
        },
        "description": {
          "type": "string"
        },
        "items": {
          "type": "array",
          "uniqueItems": true,
          "items": {
            "allOf": [
              { "$ref": "#/$defs/sourceFilePath" },
              {
                "type": "string",
                "pattern": "\\.md$"
              }
            ]
          }
        }
      }
    }
  },
  "examples": [
    {
      "$schema": "https://schemas.zeropress.dev/build-pages-config/v0.7/schema.json",
      "version": "0.7",
      "site": {
        "title": "ZeroPress Public Docs",
        "description": "Public documentation.",
        "url": "https://zeropress.dev",
        "logo": {
          "src": "/logo.svg",
          "alt": "ZeroPress Public Docs"
        },
        "locale": "en-US",
        "expose_generator": true,
        "search": true,
        "robots": { "allow_indexing": true },
        "meta": {
          "issue": "Spring 2026"
        }
      },
      "front_page": {
        "type": "markdown"
      },
      "markdown": {
        "updated_at": "git",
        "link_output": "clean"
      },
      "custom_html": {
        "head_end": {
          "file": ".zeropress/head-end.html"
        },
        "body_end": {
          "file": ".zeropress/body-end.html"
        }
      },
      "menus": {
        "primary": {
          "name": "Primary Menu",
          "items": [
            { "title": "Home", "url": "/" },
            {
              "title": "Docs",
              "url": "/docs/",
              "meta": {
                "icon": "book-open",
                "badge": "New"
              }
            }
          ]
        }
      },
      "collections": {
        "guides": {
          "title": "Guides",
          "items": [
            "getting-started/index.md",
            "deployment/index.md"
          ]
        }
      }
    }
  ]
}
