The data model

Understanding the Lume data model for pages

The content

When Lume loads a page, all its content is converted to a Data object. This object may have the content variable, with the file content. For example, the following markdown file:

**Content** of the page

is converted to this object:

{
  content: "**Content** of the page",
}

If the file has additional variables, like a front matter, they are added to the Data object:

---
title: Title of the page
---

**Content** of the page

Now the title is added to the page data:

{
  title: "Title of the page",
  content: "**Content** of the page",
}

This model is the same for any file that Lume can load. Even for images: let's say you have the transform_images plugin to load and transform the data. The file content is loaded and stored in the content variable:

{
  content: Uint8Array(...)
}

If you use JavaScript modules for your pages, they are also translated to this model. For example:

export const title = "Title of the page";

export default function ({ title }) {
  return `<h1>${title}</h1>`;
}

This is converted to:

{
  title: "Title of the page",
  content: function ({title}) {
    return `<h1>${title}</h1>`;
  }
}

Note that the default export is stored in the content variable (the same as the markdown example above). Instead of exporting the function as default, you could export it as content, but not both:

// This works
export function content({ title }) {
  return `<h1>${title}</h1>`;
}
// Mixing content and default won't work!!
export const content = "Content of the page";

export default function ({ title }) {
  return `<h1>${title}</h1>`;
}

The content variable does not always exist. Some data formats, like .yaml or .json may not export the content variable. For example, the following page defined in a YAML file:

layout: main.vto
title: Page title
description: Page description

is converted to:

{
  layout: "main.vto",
  title: "Page title",
  description: "Page description",
}

In this example, there's no content variable and that is fine. The content variable is only a convention used by formats that can export a nameless variable, like the default exports in ES modules or files with front matter and content below.

Extra variables

Once the file is loaded and converted to the Data model, Lume adds automatically 3 variables:

  • url: Define the output URL of the page.
  • date: Define the date of the page.
  • basename: Define the basename of the page (only in Lume 2).

You can define the url as a absolute or relative path or even a function (see URLs documentation for more info). Lume will resolve this value to a string, and if the url is false, the page is ignored.

The date variable can be defined in the filename (like 2023-11-30_hello-world.md), using a page variable, etc. Lume will try to resolve it, using the file creation date as a fallback.

The basename is like the filename. It can be used to change the last part of the URL.

For example, the following markdown file is saved in the posts/2023-11-30_hello-world.md file:

Hello **world**

This is converted to:

{
  url: "/posts/hello-world/",
  date: Date("2023-11-30 00:00:00"),
  basename: "hello-world",
  content: "Hello **world**"
}

Data inheritance

Once a page is loaded, converted to Data and the special variables url, date and basename are assigned, it's merged with other data defined in _data files. Components stored in the _components folders are added to the page under the comp variable. Other variables defined by plugins like search or paginate are added too. So the markdown file:

Hello **world**

Is converted to:

{
  url: "/posts/hello-world/",
  date: Date("2023-11-30 00:00:00"),
  basename: "hello-world",
  content: "Hello **world**",
  search: Searcher(),
  paginate: Paginate(),
  comp: {}
  // etc...
}

Generators

If the content variable is a Generator function, Lume will generate the pages at this point. More info about generating pages.

For example, let's say we have the following generator page:

export const layout = "main.vto";

export default function* () {
  const numbers = [1, 2, 3];

  for (const number of numbers) {
    yield {
      url: `/page-${number}/`,
      content: `This is the page number ${number}`,
    };
  }
}

That generates the three following pages:

{
  layout: "main.vto",
  url: "/page-1/",
  content: "This is the page number 1",
  date: Date(),
  basename: "page-1",
  search: Searcher(),
  paginate: Paginate(),
  comp: {}
}

{
  layout: "main.vto",
  url: "/page-2/",
  content: "This is the page number 2",
  date: Date(),
  basename: "page-2",
  search: Searcher(),
  paginate: Paginate(),
  comp: {}
}

{
  layout: "main.vto",
  url: "/page-3/",
  content: "This is the page number 3",
  date: Date(),
  basename: "page-3",
  search: Searcher(),
  paginate: Paginate(),
  comp: {}
}

Preprocessors

If you've defined any preprocessor in your _config file, it's executed at this point. Preprocessors allow modification of the Data object before rendering. The preprocessors receive the Page instance and the Data object is stored in the Page.data property.

// _config.js

site.preprocess([".html"], (pages) => {
  for (const page of pages) {
    const data = page.data; // Get the Data object
    data.title += " - My site name"; // Modify the title variable
  }
});

Rendering

This is the process of rendering the Data object and saving the result in the Page.content property. Any variable defined in the Data object will be available in the template engine.

<!-- Render the title variable -->
<h1>{{ title }}</h1>

Processing

While Page.data returns the Data object of the page, Page.content returns the result of rendering the data or, in other words, the content that will be output to the dest folder. You can use processors to modify this content:

site.process([".html"], (page) => {
  // Get the rendered content
  const content = page.content;

  // Modify the content
  page.content = content + "\n<!-- Created by Óscar Otero -->";
});

If the page content is HTML code, there's the document property to use the DOM API to modify the content:

site.process([".html"], (page) => {
  // Get the DOM
  const document = page.document;

  // Modify the content
  document.querySelectorAll('a[href^="http"]').forEach((link) => {
    link.setAttribute("target", "_blank");
  });
});

Data conventions

In the Data object you can store all variables that you want with the structure of your choice. But there are some special variables that Lume understands (See Standard variables documentation) and other variables considered good practices or common conventions. This is a list with all of them:

url string
Created by Lume automatically if it's missing.
date Date
Created by Lume automatically if it's missing.
basename string
Created by Lume automatically if it's missing.
tags string[]
Normalized by Lume automatically. Used to assign tags or to pages.
draft boolean
If it's true, the page will be ignored. Use the env variable LUME_DRAFTS=true to show draft pages.
renderOrder number
To configure the rendering order of a page.
content string | Uint8Array | function | object
The raw content of the page.
children string | Uint8Array | function | object
The rendered content before being wrapped into layouts.
layout string
The layout file used to render the page.
templateEngine string | string[]
Configure different template engines to render the page.
mergedKeys Record<string, "array" | "stringArray" | "object">
Configure how some data keys will be merged with the parent data.
onDemand boolean
Whether render this page on demand or not.
lang string
The language of the page.
type string
The type of the page (post, article, etc). Used to group pages in different collections.
id string | number
The id of the page. Used to identify a page inside a type.
comp object
The components available for this page (from _components folders).
page Page
The current Page instance.
alternates Data[]
Other pages with the same content translated to other languages.
search Searcher
A utility class to search pages and files.
paginate function
A function to paginate the result of searching pages.