Custom styles

Create custom output from your Data Package metadata.

While Flower includes several built-in styles, you can also create custom styles for your particular use case. A custom style consists of Jinja templates and configuration files that define the structure of the documentation generated from a datapackage.json. When you run the build command, Flower uses these files to generate the documentation in your custom style.

Note

Custom styles only work with build, not view. Since view can be run from anywhere on your computer, there is no reliable way for it to find a custom style. If you would like your style to be available to view, consider contributing it as a built-in style.

In this guide, you will create a custom style that generates one Quarto Markdown file for the metadata describing the Data Package as a whole and separate Markdown files for each data resource. The guide uses a Data Package about a (fake) study on flowers and their growing conditions. To work through the steps yourself, copy the datapackage.json content below into a local directory.

{
  "name": "flora",
  "title": "Observations of flora species and seasonal development",
  "id": "29b1b5e4-3f4c-4d2a-9f8e-123456789abc",
  "description": "A dataset containing flora species records and their observed growth stages across different environments.",
  "version": "1.2.3",
  "created": "2025-11-07T11:12:56+01:00",
  "keywords": [
    "flora",
    "species",
    "growth stages",
    "observations",
    "seasonal development"
  ],
  "licenses": [
    {
      "title": "CCO 1.0 UNIVERSAL",
      "path": "https://creativecommons.org/publicdomain/zero/1.0/legalcode"
    },
    {
      "title": "Academic Free License 3.0",
      "path": "https://opensource.org/licenses/AFL-3.0"
    }
  ],
  "contributors": [
    {
      "title": "Jane Dölitz",
      "roles": [
        "creator"
      ],
      "email": "jane-doelitz@example.com",
      "organization": "Example University"
    },
    {
      "title": "John Smith",
      "roles": [
        "author",
        "data collector"
      ],
      "email": "john-smith@example.com",
      "organization": "Example Institute"
    }
  ],
  "sources": [
    {
      "title": "Example Data Source",
      "version": "1.0",
      "path": "https://example.com/data-source",
      "email": "source@example.com"
    }
  ],
  "resources": [
    {
      "name": "species_catalog",
      "title": "Flora Species",
      "description": "This resource contains a catalog of flora species.",
      "path": "resources/species_catalog/data.parquet",
      "format": "parquet",
      "encoding": "utf-8",
      "schema": {
        "primaryKey": "species_id",
        "foreignKeys": [
          {
            "fields": [
              "parent_species_id"
            ],
            "reference": {
              "fields": [
                "species_id"
              ]
            }
          }
        ],
        "fields": [
          {
            "title": "Species ID",
            "name": "species_id",
            "type": "integer",
            "description": "The unique identifier for each species."
          },
          {
            "title": "Scientific name",
            "name": "scientific_name",
            "type": "string",
            "description": "The Latin name of the species."
          },
          {
            "title": "Common name",
            "name": "common_name",
            "type": "string",
            "description": "The common name of the species."
          },
          {
            "title": "Family",
            "name": "family",
            "type": "string",
            "description": "The Latin family to which the species belongs."
          },
          {
            "title": "Parent Species",
            "name": "parent_species_id",
            "type": "integer",
            "description": "The parent species of this species."
          }
        ]
      },
      "sources": [
        {
          "title": "Flora Species Source",
          "version": "1.0",
          "path": "https://example.com/data-source/flora-species",
          "email": "source@example.com"
        }
      ]
    },
    {
      "name": "location_catalog",
      "title": "Flora Species Locations",
      "description": "This resource contains a catalog of flora species locations.",
      "path": "resources/location_catalog/data.parquet",
      "format": "parquet",
      "encoding": "utf-8",
      "schema": {
        "primaryKey": [
          "region",
          "country"
        ],
        "fields": [
          {
            "title": "Region",
            "name": "region",
            "type": "string",
            "description": "The region the location is in."
          },
          {
            "title": "Country",
            "name": "country",
            "type": "string",
            "description": "The country the location is in."
          },
          {
            "title": "Local Name",
            "name": "local_name",
            "type": "string",
            "description": "The name of the location in the local language."
          }
        ]
      },
      "sources": [
        {
          "title": "Flora Species Locations Source",
          "version": "1.0",
          "path": "https://example.com/data-source/locations",
          "email": "source@example.com"
        }
      ]
    },
    {
      "name": "growth_records",
      "title": "Growth Stage Records",
      "description": "This resource contains records of observed growth stages for various flora species.",
      "path": "resources/growth_records/data.parquet",
      "format": "parquet",
      "encoding": "utf-8",
      "schema": {
        "primaryKey": [
          "record_id"
        ],
        "foreignKeys": [
          {
            "fields": "species_id",
            "reference": {
              "resource": "species_catalog",
              "fields": "species_id"
            }
          },
          {
            "fields": [
              "location_region",
              "location_country"
            ],
            "reference": {
              "resource": "location_catalog",
              "fields": [
                "region",
                "country"
              ]
            }
          }
        ],
        "fields": [
          {
            "title": "Record ID",
            "name": "record_id",
            "type": "integer",
            "description": "The unique identifier for each growth record."
          },
          {
            "title": "Species",
            "name": "species_id",
            "type": "integer",
            "description": "The species the observation was made for."
          },
          {
            "title": "Observation date",
            "name": "observation_date",
            "type": "date",
            "description": "The date when the observation was made."
          },
          {
            "title": "Growth Stage",
            "name": "growth_stage",
            "type": "string",
            "description": "The observed growth stage of the species."
          },
          {
            "title": "Location Region",
            "name": "location_region",
            "type": "string",
            "description": "The region of the location."
          },
          {
            "title": "Location Country",
            "name": "location_country",
            "type": "string",
            "description": "The country of the location."
          }
        ]
      }
    }
  ]
}

In this guide, the directory is called flora/. Once you’ve completed the guide, your file structure will look like the below with the custom-style/ folder containing the files for your custom style, and the docs/ folder containing the generated documentation files:

flora/
├── datapackage.json
├── .flower.toml
├── custom-style/
│   ├── sections.toml
│   ├── package.qmd.jinja
│   ├── contributors.qmd.jinja
│   └── resource.qmd.jinja
└── docs/
    ├── index.qmd
    └── resources/
        ├── species_catalog.qmd
        └── growth_records.qmd

Creating the templates

The first step is to set up the files within the custom-style/ folder. With Flower, you define a custom layout for your metadata (or part of your metadata) using one or more Jinja2 templates. In these templates, you write out the structure of the desired output in your target language (e.g., HTML, markdown, or YAML), using placeholder values for the metadata elements that will come from your datapackage.json.

In this guide, you will create template files containing the Data Package’s general metadata, the contributor metadata, and metadata for each resource contained in the Data Package. The general metadata and contributor metadata templates will be combined into a single index.qmd file, while the resource metadata template will split resources into separate files.

First, create a new directory for the templates in the root of your project. You can store your templates wherever it makes sense for your project, but for this guide, call the folder custom-style/:

flora/
├── datapackage.json
└── custom-style/

Inside the template folder, create three files, one for each type of metadata listed above.

  • package.qmd.jinja for the general metadata.
  • contributors.qmd.jinja for the contributor metadata.
  • resource.qmd.jinja for each resource’s metadata.

The .qmd.jinja extension indicates that the file is a Jinja template that will create a qmd file. If you wanted to create an html file instead, the extension would be .html.jinja.

Next, you will set the layout for the generated documentation in each template. Since Flower uses Jinja2 as its templating engine, you need to use Jinja syntax when accessing values from the Data Package metadata.

package.qmd.jinja

Start with the package.qmd.jinja template that will include metadata about the Data Package as a whole. Since the output will be a Quarto Markdown file, you should use Markdown syntax to structure the content. First, add a YAML header to the template to set the title, subtitle, and date of the generated document, using Jinja syntax to access the relevant metadata properties.

custom-style/package.qmd.jinja
---
title: "{{ package.title or package.name }}"
subtitle: "Version: {{ package.version }}"
date: "{{ package.created }}"
---

Jinja syntax uses the double curly brackets {{...}} to output the value of a variable or expression. In the above, {{ package.title or package.name }} sets the title of the generated document to the title property of the package metadata, or to the name property if title is not available. You can use any name for the metadata object passed to the template (e.g., root or data_package instead of package); this variable name will be defined in the sections.toml configuration file described in the sections.toml section below.

Jinja provides built-in filters and functions for processing values, as well as control flow statements such as conditionals and loops. Below, you extend package.qmd.jinja to include the package description and a list of keywords that describe the Data Package:

custom-style/package.qmd.jinja
---
title: "{{ package.title or package.name }}"
subtitle: "Version: {{ package.version }}"
date: "{{ package.created }}"
---

1{{ package.description }}

2{% if package.keywords %}
**Keywords:** {{ package.keywords | join(", ") }}
{% endif %}
1
{{ package.description }} outputs the value of the description property of the package metadata.
2
The if statement checks if the keywords property exists in the package metadata. If it does, it will output a line with the keywords joined by commas using the built-in Jinja join filter. You can learn more about Jinja features in the Jinja documentation.

contributors.qmd.jinja

In the contributors.qmd.jinja template, you will define the layout for the contributors (using the variable name contributors). You could include the contributors in the package.qmd.jinja template, but, to show how to split content across templates, you will create a separate template. The content of these templates will be combined via the sections.toml configuration file described in the sections.toml section below.

As with the previous template, you will use Jinja syntax to access the relevant metadata properties and to include control flow statements. In this case, you will use a for loop to iterate over the list of contributors and output their metadata. You will also include a conditional to check if the organization and email properties exist for each contributor:

custom-style/contributors.qmd.jinja
## Contributors

{%- if contributors %}
{% for contributor in contributors %}
### {{ contributor.title }}

{%- if contributor.organization %}
**Organization:** {{ contributor.organization }}
{% endif %}

{% if contributor.email -%}
**Email:** <{{ contributor.email }}>
{%- endif %}
{% endfor %}

{% else %}
No contributors listed.
{% endif %}

Notice that the template includes a conditional to check if there are any contributors listed in the metadata. If there are no contributors, it will output “No contributors listed.” instead of leaving an empty section.

resource.qmd.jinja

A Data Package can contain multiple data resources and you may want to display each resource’s metadata in a separate file with the same layout. To do this, you can create a template that defines the layout for a single resource and then use the sections.toml configuration file to indicate that this template should be applied to each resource in the Data Package. In the template, the variable name resource is used to refer to the metadata of any single resource that will be rendered with the template:

custom-style/resource.qmd.jinja
---
title: "{{ resource.title or resource.name }}"
---

{{ resource.description }}

## Overview

- **Name:** `{ resource.name }`
{% if resource.path %}
- **Path:** `{ resource.path }`
{% endif %}

{%- if resource.schema and resource.schema.fields %}
- **Number of fields:** {{ resource.schema.fields | length }}
{% else %}
- **Number of fields:** 0
{% endif %}

Creating the configuration files

Main configuration file

When you define a custom style, you must set the template and output folder in the main configuration file. Flower can read the main configuration from the .flower.toml or the pyproject.toml file in the root of your Data Package. In this guide, you will use .flower.toml.

Create a .flower.toml file in the root of your Data Package and add the following settings:

.flower.toml
template-dir = "custom-style/"

This line points Flower to the folder containing your templates. The output-dir is not set here, so the generated documentation will be written to the default docs/ directory.

Tip

If you want to save the generated documentation files to a different folder than docs/, you can set the output-dir in the main configuration file as well.

If you have a pyproject.toml file in the root of your Data Package, you can include the same settings there instead of in a separate .flower.toml file. The only difference is that the settings in pyproject.toml need to be nested under a header like so:

pyproject.toml
[tool.seedcase-flower]
template-dir = "custom-style/"
Tip

The main configuration also includes a style field. This specifies a built-in style and should not be used when creating a custom style. If you want to use a built-in style, leave template-dir unset and set style to the name of your chosen built-in style.

For examples of built-in styles, see the examples page.

sections.toml configuration file

The sections.toml file defines how many output files will be created and which metadata will be displayed in each file. Flower splits documentation into two section types: one and many.

  • one is for one output file (e.g., an index file).
  • many is for a group of similar output files (a file for each resource or a file for each resource field).
Note

The sections.toml file must be stored in the same folder as the Jinja templates.

Adding [[one]] section(s)

Create a sections.toml file in the custom-style/ folder. Inside, add a [[one]] section for each output file you want to create. For this guide, add just one [[one]] section to save an index.qmd file:

custom-style/sections.toml
[[one]]
output-path = "index.qmd"

This will generate an index.qmd file in the output directory (docs/ in this example, since this is the default output directory).

Each [[one]] section contains one or more content items, each linking a specific part of the metadata to a template. Content items in the same section are combined into one output file in the order they are defined.

The section below outputs index.qmd, the “landing page” for the Data Package documentation, with content items for the general package properties (like title and description) and the contributors.

Each content item requires the following fields:

  • jsonpath: A JSON path pointing to the metadata property or properties to send to the template.
  • template-path: The path to the Jinja template file, relative to the template folder.
  • jinja-variable: The variable name used in the template to reference the metadata selected by the jsonpath.

Update the sections.toml file to include the content items for the [[one]] section:

custom-style/sections.toml
[[one]]
output-path = "index.qmd"

[[one.contents]]
1jsonpath = "$"
2template-path = "package.qmd.jinja"
3jinja-variable = "package"

4[[one.contents]]
jsonpath = "$.contributors"
template-path = "contributors.qmd.jinja"
jinja-variable = "contributors"
1
Select the entire metadata object from datapackage.json.
2
Render the selected metadata with this template.
3
Reference the selected metadata with this variable name.
4
Append this section to the previous section’s output in docs/index.qmd.

You can add more [[one]] sections if you want to create more output files that contain different metadata. Or you can keep adding content items to the same [[one]] section. Each [[one.contents]] item belongs to the [[one]] section defined above it in the file.

Adding [[many]] section(s)

The [[many]] section type creates a group of similar output files, for example, one file per resource. It works similarly to [[one]], but instead of jsonpath, it uses a content type (either "resources" or "fields"), with the other Content attributes defined directly in the [[many]] section.

Note

The content field in a [[many]] section is restricted to "resources" or "fields" because these are the only Data Package metadata properties structured as lists, making them the only ones that benefit from generating one file per item.

To create one file for each resource in a resources/ folder using the resource.qmd.jinja template, add the following [[many]] section to the sections.toml file:

custom-style/sections.toml
[[many]]
output-path = "resources/"
content = "resources"
template-path = "resource.qmd.jinja"
jinja-variable = "resource"

The created files will be placed in the resources/ folder inside the output directory defined in the main configuration (docs/ in this example). Each created file will be named using the resource’s name property (in this case species_catalog.qmd and growth_records.qmd).

Tip

You can either set a folder or a file in the output-path of [[many]]. If you use a file path, you need to include the {} placeholder for the file name. In the case above, it would be resources/{resource-name}.qmd.

See more about the [[one]] and [[many]] section Python classes in the design documentation.

Build the custom style documentation

To generate your custom style documentation, run the build command in the terminal from the root of your Data Package:

Terminal
seedcase-flower build

Flower then reads the configuration files (.flower.toml and sections.toml), populates the templates (in the custom-style/ folder) with metadata from datapackage.json, and outputs the generated documentation files to the specified output directory.

Your file structure should now look like:

flora/
├── datapackage.json
├── .flower.toml
├── custom-style/
│   ├── sections.toml
│   ├── package.qmd.jinja
│   ├── contributors.qmd.jinja
│   └── resource.qmd.jinja
└── docs/
    ├── index.qmd
    └── resources/
        ├── species_catalog.qmd
        └── growth_records.qmd

You can now use your custom documentation files in the docs/ folder as you would with any other Quarto Markdown files, such as generating a website or PDF.