Needs

The [needs] section is the heart of your ubproject.toml. It tells ubCode how to identify, validate, and relate the need objects in your reStructuredText files — types, link types, custom fields, statuses, tags, and external needs.

See also

Key Concepts

Minimal example

[needs]
id_required = true
id_regex = "^[A-Z]{3}_[0-9]{3}$"

[[needs.types]]
directive = "requirement"
title = "Requirement"
prefix = "REQ_"

[needs.links.implements]
outgoing = "implements"
incoming = "implemented by"

[needs.fields.priority]
description = "Priority level"

Core options

id_required

Type: boolean (default: false)

If true, every need must have an explicit ID. If false, IDs will be auto-generated when not provided.

id_regex

Type: string (default: "^[A-Z0-9_]{5,}")

Regular expression pattern to validate need IDs against. Only applies when id_required is true.

id_length

Type: integer (default: 5)

Length of auto-generated IDs when not explicitly provided.

id_from_title

Type: boolean (default: false)

When true, auto-generated IDs will be based on the need title instead of being random.

title_optional

Type: boolean (default: false)

When true, allows needs to be created without a title.

title_from_content

Type: boolean (default: false)

When true, auto-generates titles from the first line of need content when no title is provided.

Need types

The types array defines the available need directives in your project:

[[needs.types]]
directive = "requirement"  # The RST directive name
title = "Requirement"     # Human-readable title
prefix = "REQ_"          # Prefix for auto-generated IDs
color = "#BFD8D2"        # Color for diagrams
style = "node"           # Style for diagrams

Each type supports:

  • directive (required): The reStructuredText directive name

  • title (required): Human-readable title for the need type

  • prefix (required): Prefix used for auto-generated need IDs

  • color (optional, default: "#000000"): Color used in need diagrams

  • style (optional, default: "node"): Style used in need diagrams

Fields

Every need carries fields — named pieces of metadata such as status, tags, or any custom attribute you define. Some fields are built in (id, title, status, tags); you can add your own through the configuration.

ubCode supports two formats: the dict-based [needs.fields] format (aligned with sphinx-needs ≥ 7) and the list-based extra_options format (for sphinx-needs < 7).

Dict-based format (recommended)

Note

The dict-based [needs.fields] format requires sphinx-needs ≥ 7.0. It replaces the older list-based needs_extra_options and also allows specializing core fields (e.g. status, tags) by narrowing their type constraints.

Use [needs.fields.<name>] to define each custom field:

[needs.fields.priority]
description = "Priority level"
default = "medium"

[needs.fields.assignee]
description = "Assigned team member"

[needs.fields.effort]
description = "Story points"
schema = {type = "integer", minimum = 1, maximum = 13}

Each field supports:

  • description (optional): Human-readable description

  • default (optional): Default value if not specified on a need

  • schema (optional): JSON Schema to validate values (see Field schemas below). Schema support for fields was introduced in sphinx-needs 6.0 (via ``needs_extra_options``) and carried forward to ``needs_fields`` in sphinx-needs 7.0.

  • nullable (optional): Whether the field accepts null values. sphinx-needs ≥ 7.0.

  • predicates (optional): Conditional defaults (see Global options). sphinx-needs ≥ 7.0.

  • parse_variants (optional): Whether to parse variant functions (<<...>>) and variant data references (<{ ... }>, see Variant data) in this field’s values. sphinx-needs ≥ 7.0.

  • parse_dynamic_functions (optional): Whether to parse dynamic functions in this field’s values. sphinx-needs ≥ 6.3.

Once declared, these fields can be used on any need:

.. req:: Encrypt data at rest
   :id: REQ_002
   :status: open
   :priority: high
   :assignee: alice
   :effort: 5

List-based format (sphinx-needs < 7)

Deprecated since version 0.25.0: The list-based extra_options format (needs_extra_options in sphinx-needs) was deprecated in sphinx-needs 7.0 in favour of the dict-based format. It remains supported for backward compatibility. For new projects, prefer the dict-based [needs.fields] format.

Simple format (string list):

extra_options = ["priority", "effort", "assignee", "component"]

Detailed format (with descriptions and schemas):

extra_options = [
    {name = "priority", description = "Priority level"},
    {name = "effort", description = "Story points", schema = {type = "integer", minimum = 1, maximum = 13}},
    {name = "assignee", description = "Assigned team member"},
]

Field schemas

The schema key on a field applies JSON Schema validation to every value entered for that field. Invalid data is caught at index time, before the docs are built.

String schemas:

[needs.fields.contact]
schema = {type = "string", format = "email"}

[needs.fields.id_pattern]
schema = {type = "string", pattern = "^[A-Z]{3}-[0-9]{4}$"}

[needs.fields.severity]
schema = {type = "string", enum = ["low", "medium", "high", "critical"]}

String format options:

The format property supports various predefined formats:

  • "date": RFC 3339 date format (e.g., 2023-10-22)

  • "date-time": RFC 3339 date-time format (e.g., 2023-10-22T14:30:00Z)

  • "time": RFC 3339 time format (e.g., 14:30:00Z)

  • "email": RFC 5322 email address (e.g., user@example.com)

  • "hostname": RFC 1123 hostname (e.g., example.com)

  • "uri": RFC 3986 URI (e.g., https://example.com/path)

  • "uuid": RFC 4122 UUID (e.g., 550e8400-e29b-41d4-a716-446655440000)

  • "regex": ECMAScript regular expression

Integer and number schemas:

[needs.fields.priority]
schema = {type = "integer", minimum = 1, maximum = 10}

[needs.fields.rating]
schema = {type = "number", minimum = 0.0, maximum = 5.0}

[needs.fields.fibonacci]
schema = {type = "integer", enum = [1, 2, 3, 5, 8, 13, 21]}

Boolean schemas:

[needs.fields.approved]
schema = {type = "boolean"}

Array schemas:

[needs.fields.reviewers]
schema = {type = "array", items = {type = "string", format = "email"}, minItems = 1, maxItems = 3}

[needs.fields.categories]
schema = {type = "array", items = {type = "string"}, uniqueItems = true}

Usage in need directives:

Once defined, these fields can be used in your need directives:

.. requirement:: User Authentication
    :priority: 5
    :effort: 8
    :assignee: developer@company.com
    :due_date: 2024-12-31
    :tags: security, authentication
    :component: auth, backend
    :active: true

    Users must be able to authenticate using their corporate credentials.

Statuses and tags

You can constrain the allowed values for a need’s status and tags fields. The recommended approach depends on your sphinx-needs version.

Legacy list-based constraints (sphinx-needs < 7)

Deprecated since version 0.25.0: The [[needs.statuses]] and [[needs.tags]] list formats (needs_statuses / needs_tags in sphinx-needs) were deprecated in sphinx-needs 7.0 in favour of schema enum constraints on the status and tags fields (see above). They remain supported for backward compatibility.

[[needs.statuses]]
name = "draft"
description = "Work in progress"

[[needs.statuses]]
name = "approved"
description = "Reviewed and approved"

[[needs.tags]]
name = "security"
description = "Security-related needs"

External needs

Load needs from external sources (JSON files or URLs):

# From local file
[[needs.external_needs]]
json_path = "external/project_needs.json"
id_prefix = "EXT"
base_url = "https://external-project.com"
target_url = "docs/{{ id }}.html"

# From remote URL
[[needs.external_needs]]
json_url = "https://api.example.com/needs.json"
id_prefix = "REMOTE"

Each external needs source supports:

  • json_path: Path to a local JSON file

  • json_url: URL to a remote JSON file

  • id_prefix: Prefix prepended to external need IDs

  • base_url: Base URL for linking back to the source project

  • target_url: URL template for individual need links (supports {{ id }} placeholder)

  • css_class: CSS class applied to external needs

  • version: Version string for the external needs source

  • skip_in_ubcode (default: false): When true, skip loading these external needs in ubCode analysis

Variants

The [needs.variants] section defines named filter expressions that can be referenced from need fields using the <<name: value, fallback>> syntax. This implements the Sphinx-Needs variant functions feature.

[needs.variants]
is_open = "status == 'open'"
is_html = "'html' in build_tags"
is_production = "environment == 'production'"

With these variants defined, need fields can use them:

.. req:: My requirement
   :id: REQ_001
   :status: <<is_open: active, inactive>>
   :extra: <<is_html: web_only, general>>

You can also use inline bracketed expressions without pre-defining a variant:

.. req:: Another requirement
   :id: REQ_002
   :status: <<[status == 'open']: active, inactive>>

The last comma-separated value is always the fallback when no expression matches.

Note

Variant syntax is only parsed for fields that opt in. In sphinx-needs ≥ 7.0, set parse_variants = true on individual fields in [needs.fields]. For older versions, use the deprecated variant_options list (see Deprecated needs options).

Parse and evaluation errors are surfaced as needs.variant diagnostics.

See also

Variant data for the related var.* namespace and the <{ ... }> reference syntax, which inject build-wide data rather than selecting between values conditionally.

Variant data

sphinx-needs ≥ 8.2.

Variant data is a nested, read-only key-value store that ubCode exposes under the var.* namespace inside filter expressions. It lets a single “150%” model — one set of needs that describes every product variant — be filtered, extended, and rendered differently for each build, without duplicating content.

See also

Variant builds walks through the end-to-end workflow — including building and diffing each variant from the command line. This section is the configuration reference for the options it uses.

For example, a project that targets several CPU architectures and build configurations can keep one copy of its requirements and select the data relevant to the current build:

[needs.variant_data]
platform = "arm"
debug = false
opt_level = 2
archs = ["arm", "x86"]

[needs.variant_data.build]
compiler = "clang"
features = ["networking", "logging"]

With this configuration the following values are available in any filter expression:

var.platform == "arm"                 # scalar string
var.opt_level == 2                    # scalar integer
var.debug == False                    # scalar boolean
"x86" in var.archs                    # membership in an array
var.build.compiler == "clang"         # nested scalar
"networking" in var.build.features    # membership in a nested array

Note

var.* resolves only against the configured variant data. Unlike need fields or filter_data keys, it does not depend on the need currently being evaluated, which makes it suitable for global, build-wide switches. Looking up an unknown key (e.g. var.missing) is a needs.variant diagnostic, not a silent false.

Defining variant data

Variant data can be provided inline, from a JSON file, or both.

variant_data

Type: object (default: {})

An inline, nested table of variant data, declared directly in ubproject.toml under [needs.variant_data] (as shown above).

variant_data_file

Type: string (default: "")

Path to a JSON file containing variant data, resolved relative to the ubproject.toml that declares it. This is convenient when the values are generated by a build system or shared across several projects.

[needs]
variant_data_file = "variant_data.json"
{
  "platform": "linux",
  "build": {"compiler": "gcc", "features": ["networking"]}
}

When both are set, the file is loaded first as the base, and the inline [needs.variant_data] values are deep-merged on top, with the inline values winning:

  • At nested maps, keys are merged recursively.

  • At scalars and arrays, the inline value replaces the file value entirely.

[needs]
# file sets platform = "linux", build.compiler = "gcc", build.features = [...]
variant_data_file = "variant_data.json"

[needs.variant_data]
platform = "windows"   # overrides the file's scalar value

[needs.variant_data.build]
compiler = "clang"     # overrides build.compiler;
                       # build.features is preserved from the file

Because inline values take precedence, you can keep shared defaults in the file under version control and override individual switches per build.

Value types

Variant data values may be:

  • Scalars — string, integer, number (float), or boolean.

  • Uniform arrays of scalars — every element must share the same type (mixed-type arrays are reported as a needs.variant diagnostic).

  • Nested maps — arbitrarily deep tables, accessed with further dots (var.build.sanitizers.asan).

Using var.* in filters

The var.* namespace is available anywhere ubCode evaluates a filter expression against your needs configuration.

Variant functions — select a field value per build (see Variants):

.. req:: Bootloader
   :id: REQ_001
   :status: <<[var.platform == "windows"]: active, inactive>>

Conditional defaults — vary a field’s default via predicates:

[needs.fields.compiler_note]
default = "unknown-compiler"
predicates = [
    ['var.build.compiler == "clang"', "using-clang"],
    ['var.build.compiler == "gcc"', "using-gcc"],
]

needextend — extend only the needs relevant to the current build:

.. needextend:: var.platform == "windows"
   :status: supported

needimport — import only the needs that match:

.. needimport:: external.json
   :filter: var.platform == "windows"

External needs — filter needs loaded from another project:

[[needs.external_needs]]
json_path = "imported.json"
filter = 'var.platform == "windows"'

Referencing variant data in field values (<{ ... }>)

In addition to filtering, variant data can be injected directly into a field or link value using the <{ ... }> syntax. The text between the delimiters is a var.* path, and ubCode substitutes the stored value at index time.

Unlike variant functions (<<...>>), which choose between candidate values using conditional logic, a <{ ... }> reference is a direct lookup — there is no condition, just a substitution.

This syntax is only parsed for fields and links that opt in with parse_variants = true (the same flag that enables <<...>>):

[needs.variant_data]
platform = "arm"
links = ["REQ_001", "REQ_002"]

[needs.variant_data.build]
opt_level = 2

[needs.fields.arch]
schema = {type = "string"}
parse_variants = true

[needs.fields.opt]
schema = {type = "integer"}
parse_variants = true

[needs.links.derived_from]
parse_variants = true

The references can then be used on any need:

.. req:: Scalar reference
   :id: REQ_001
   :arch: <{ var.platform }>
   :opt: <{ var.build.opt_level }>

.. req:: Embedded in a string
   :id: REQ_002
   :arch: built for <{ var.platform }>

.. req:: Within an array and a link field
   :id: REQ_003
   :tags: a, <{ var.platform }>, c
   :derived_from: <{ var.links }>

A few rules apply:

  • For string fields the reference may be embedded inside surrounding text; for non-string scalar fields (integer, number, boolean) the reference must be the entire value.

  • The resolved value is type-checked against the field’s schema.

  • If the field does not set parse_variants = true, the <{ ... }> text is kept literally.

The following situations are reported as needs.variant diagnostics (and leave the value unset):

  • Invalid expression — the content is not a dotted var.* path, e.g. <{ platform }> (missing the var. root).

  • Unknown key — the path does not exist in the variant data, e.g. <{ var.nonexistent }> or <{ var.build.missing }>.

  • Non-leaf value — the path resolves to a nested map rather than a scalar or array, e.g. <{ var.build }>.

  • Type mismatch — the resolved value’s type does not match the field, e.g. injecting a string into an integer field.

See also

The corresponding sphinx-needs feature is documented under variant data.

Dynamic functions

ubCode resolves some built-in dynamic functions using the [[...]] syntax from sphinx-needs.

Four built-in functions are supported:

  • echo("text") — returns the given string unchanged

  • test(*args, **kwargs) — returns a debug string with the need ID and arguments

  • copy("field") — copies a field value from the current need (also supports lower and upper kwargs)

  • links_from_content() — returns the needs referenced via :need: roles in the need’s own content (the need_id and filter arguments are not yet supported)

For example:

.. req:: My requirement
   :id: REQ_001
   :status: [[copy("priority")]]

.. spec:: My specification
   :id: SPEC_001
   :links: [[links_from_content()]]

   This specification realises :need:`REQ_001`.

Dynamic function parsing is enabled by default and can be controlled globally or per-field/per-link:

[needs]
parse_dynamic_functions = false  # disable globally (sphinx-needs ≥ 7.0)

[needs.fields.my_field]
parse_dynamic_functions = true   # re-enable for this field (sphinx-needs ≥ 6.3)

[needs.links.implements]
parse_dynamic_functions = true   # re-enable for this link (sphinx-needs ≥ 6.3)
parse_dynamic_functions

Type: boolean (default: true)

Whether dynamic functions are parsed by default. Applies to extra fields and links unless overridden per-field/per-link. Core fields are not affected by this global setting.

The global parse_dynamic_functions option was added in sphinx-needs 7.0 (needs_parse_dynamic_functions). Per-field and per-link parse_dynamic_functions was added in sphinx-needs 6.3.

filter_data

Type: object (default: {})

Additional context variables that can be referenced in filter expressions throughout your needs configuration. This allows you to define custom variables for conditional logic in multiple contexts.

Where filter_data can be used:

  1. Predicate default filters — in global_options predicates (or predicates on individual fields/links in sphinx-needs ≥ 7.0) to conditionally set default values

  2. needimport item filters — to filter which needs are imported from external sources

  3. needextend filters — to conditionally extend or modify needs based on context

[needs]
filter_data = { environment = "production", team = "backend", version = "2.0" }

# Using predicates on a field directly (sphinx-needs ≥ 7.0, recommended)
[needs.fields.priority]
default = "medium"
predicates = [
  ["environment == 'production'", "high"],
  ["team == 'backend'", "high"]
]

# Or via global_options (sphinx-needs < 7, deprecated)
# [needs.global_options.priority]
# default = "medium"
# predicates = [
#   ["environment == 'production'", "high"],
#   ["team == 'backend'", "high"]
# ]

Example with needimport:

.. needimport:: external_needs.json
   :filter: environment == 'production' and version == '2.0'

Example with needextend:

.. needextend:: team == 'backend'
   :status: critical

Common use cases:

  • Setting environment-specific defaults (development, staging, production)

  • Filtering imported needs based on release version or team ownership

  • Applying team or project-specific configurations

  • Conditional formatting or validation based on build context

  • Dynamic behaviour in CI/CD pipelines

import_keys

Type: object (default: {})

Mapping of keys for needimport directives, allowing short keys to reference longer file paths. Available since sphinx-needs 4.2.

[needs.import_keys]
backend = "/path/to/backend_needs.json"
frontend = "/path/to/frontend_needs.json"