diff --git a/docs/site.json b/docs/site.json
index 89193d5e29..f478492450 100644
--- a/docs/site.json
+++ b/docs/site.json
@@ -6,6 +6,16 @@
"codeTheme": "light"
},
"pages": [
+ {
+ "src": "userGuide/customFileTypes/sampleJson.md",
+ "fileExtension": ".json",
+ "searchable": "no"
+ },
+ {
+ "src": "userGuide/customFileTypes/sampleTxt.md",
+ "fileExtension": ".txt",
+ "searchable": "no"
+ },
{
"glob": ["*.md", "userGuide/*.md", "userGuide/components/*.md", "devGuide/*.md", "devGuide/*/*.md"]
},
diff --git a/docs/userGuide/addingPages.md b/docs/userGuide/addingPages.md
index 38e31524eb..b36d460e37 100644
--- a/docs/userGuide/addingPages.md
+++ b/docs/userGuide/addingPages.md
@@ -94,5 +94,56 @@ in your `site.json` file. See the [`pagesExclude` attribute](siteJsonFile.html#p
+## Generating Custom File Types
+
+You can also use MarkBind to generate non-HTML files, such as `.json` or `.txt`, from your source `.md` files. This is useful for generating dynamic configuration files or other text-based assets that can benefit from MarkBind's variable system.
+
+To do this, specify the `fileExtension` property for the page configuration in your `site.json`, as explained [here](siteJsonFile.html#pages).
+
+
+
+{{ icon_example }} Generating a `sampleJson.json` from `sampleJson.md`:
+
+**site.json:**
+```json
+{
+ "pages": [
+ {
+ "src": "userGuide/customFileTypes/sampleJson.md",
+ "fileExtension": ".json",
+ "searchable": "no"
+ }
+ ]
+}
+```
+**sampleJson.md:**
+
+{% raw %}
+```md
+{
+ "title": "globalVariables",
+ "description": "This file is served as a .json file, but is sourced from a .md file in the source code.",
+ "baseUrl": "{{baseUrl}}",
+ "timestamp": "{{timestamp}}",
+ "MarkBind": "{{MarkBind}}"
+}
+```
+{% endraw %}
+
+
+**Examples of generated custom files:**
+* [sampleJson]({{baseUrl}}/userGuide/customFileTypes/sampleJson.json){no-validation} - A JSON file generated from Markdown, utilizing default global nunjucks variables available in MarkBind sites.
+* [sampleTxt]({{baseUrl}}/userGuide/customFileTypes/sampleTxt.txt){no-validation} - A plain text file generated from Markdown, also utilizing global variables.
+
+
+
+
+**Key Points:**
+* **Nunjucks Variables**: You can use Nunjucks global variables (like `baseUrl`, `timestamp`) and site variables within these files, just like in your HTML pages.
+* **No Frontmatter or Scripts**: Unlike standard MarkBind pages, custom file types **do not support frontmatter or scripts**. Frontmatter and script tags will be treated as plain text if included.
+* **Search**: By default, these files are searchable if `enableSearch` is true for the site. You can disable this by setting `"searchable": "no"` (or `false`) in the page config.
+* **Intra-site validation**: If you are linking such files in other pages in your site, you can use the `{no-validation}` tag, e.g. `[sampleTxt]({{baseUrl}}/userGuide/customFileTypes/sampleTxt.txt){no-validation}` to disable intra-site validation warnings for the link, as MarkBind currently does not support intra-site validation for custom file type links.
+
+
{% from "njk/common.njk" import previous_next %}
{{ previous_next('authoringContents', 'markBindSyntaxOverview') }}
diff --git a/docs/userGuide/customFileTypes/sampleJson.md b/docs/userGuide/customFileTypes/sampleJson.md
new file mode 100644
index 0000000000..c3c2130bd3
--- /dev/null
+++ b/docs/userGuide/customFileTypes/sampleJson.md
@@ -0,0 +1,7 @@
+{
+ "title": "globalVariables",
+ "description": "This file is served as a .json file, but is sourced from a .md file in the source code.",
+ "baseUrl": "{{baseUrl}}",
+ "timestamp": "{{timestamp}}",
+ "MarkBind": "{{MarkBind}}"
+}
\ No newline at end of file
diff --git a/docs/userGuide/customFileTypes/sampleTxt.md b/docs/userGuide/customFileTypes/sampleTxt.md
new file mode 100644
index 0000000000..8911c3f117
--- /dev/null
+++ b/docs/userGuide/customFileTypes/sampleTxt.md
@@ -0,0 +1,8 @@
+This file is served as a .txt file, sourced from a .md file.
+
+The content below demonstrates nunjucks variables usage for custom file types:
+
+
+{% raw %} {{ baseUrl }} {% endraw %}: "{{ baseUrl }}"
+{% raw %} {{ timestamp }} {% endraw %}: "{{ timestamp }}"
+{% raw %} {{ MarkBind }} {% endraw %}: ”{{ MarkBind }}“
diff --git a/docs/userGuide/siteJsonFile.md b/docs/userGuide/siteJsonFile.md
index 1ab30aed76..d1a740baea 100644
--- a/docs/userGuide/siteJsonFile.md
+++ b/docs/userGuide/siteJsonFile.md
@@ -138,6 +138,7 @@ _(Optional)_ **The styling options to be applied to the site.** This includes:
* **`searchable`**: Specifies that the page(s) should be excluded from searching. Default: `yes`.
* **`externalScripts`**: An array of external scripts to be referenced on the page. Scripts referenced will be run before the layout script.
* **`frontmatter`**: Specifies properties to add to the frontmatter of a page or glob of pages. Overrides any existing properties if they have the same name, and overrides any frontmatter properties specified in `globalOverride`.
+* **`fileExtension`**: A string that specifies the output file extension (e.g., `".json"`, `".txt"`) for the generated file. If not specified, defaults to `".html"`. Note that non-HTML files do not support frontmatter or scripts.
diff --git a/packages/core/src/Page/PageConfig.ts b/packages/core/src/Page/PageConfig.ts
index 82a67b3031..6d37f73134 100644
--- a/packages/core/src/Page/PageConfig.ts
+++ b/packages/core/src/Page/PageConfig.ts
@@ -65,6 +65,11 @@ export class PageConfig {
*/
src: string;
title?: string;
+ /**
+ * The compiled Nunjucks template for the page wrapper (page.njk).
+ * This is used to generate the HTML version of the page, handling the global structure
+ * (html, head, body, scripts) wrapping the processed content.
+ */
template: Template;
variableProcessor: VariableProcessor;
addressablePagesSource: string[];
diff --git a/packages/core/src/Page/PageVueServerRenderer.ts b/packages/core/src/Page/PageVueServerRenderer.ts
index 3d42897f41..b2f52e0786 100644
--- a/packages/core/src/Page/PageVueServerRenderer.ts
+++ b/packages/core/src/Page/PageVueServerRenderer.ts
@@ -138,7 +138,7 @@ Bundle is regenerated by webpack and built pages are re-rendered with the latest
Object.values(pageEntries).forEach(async (pageEntry) => {
const { page, renderFn } = pageEntry;
const renderedVuePageContent = await renderVuePage(renderFn);
- page.outputPageHtml(renderedVuePageContent);
+ page.writeOutputFile(renderedVuePageContent);
});
}
diff --git a/packages/core/src/Page/index.ts b/packages/core/src/Page/index.ts
index f4a0db76c1..47804da091 100644
--- a/packages/core/src/Page/index.ts
+++ b/packages/core/src/Page/index.ts
@@ -505,6 +505,16 @@ export class Page {
return '';
}
+ /**
+ * Generates the page content by processing variables, nodes, frontmatter, and plugins.
+ *
+ * If the file extension is not .html, it simply processes variables and writes the content.
+ *
+ * For HTML files (usual case), it also builds the page navigation, handles layout,
+ * collects headings/keywords, and compiles the page into a Vue application for server-side rendering.
+ * Finally, it writes the rendered HTML to the output file.
+ * @param externalManager to manage external dependencies and configuration
+ */
async generate(externalManager: ExternalManager) {
this.resetState(); // Reset for live reload
@@ -531,6 +541,19 @@ export class Page {
pluginManager, siteLinkManager, this.pageUserScriptsAndStyles);
let content = variableProcessor.renderWithSiteVariables(this.pageConfig.sourcePath, pageSources);
+
+ if (path.extname(this.pageConfig.resultPath) !== '.html') {
+ const hasFrontmatterLike = /^\s*---\s*[\s\S]*?---/m.test(content);
+ const hasScriptTagLike = /