Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

nexmap documentation

Administrators

Deploying, configuring and running a nexmap server.

  • Installation: container image, Coolify, first start
  • Configuration: every environment variable and flag of nexmapd
  • Security: TLS, the encryption key, the first administrator, accounts and tokens

Users

Working with nexmap in the browser.

  • Concepts: products, structure, requirements, tasks, constraints, defects, tests, roles
  • Getting started: creating a product, settings, team, structure
  • Backlog: requirements, tasks, constraints, defects, boards and sprints
  • Testing: test cases, campaigns, recording results

CLI users

Developers, CI pipelines and AI agents working with the nexmap command line client.

  • Setup: installing, logging in, connecting a repository, configuration, commands
  • Scripting: output formats, exit codes, raw API calls
  • CI integration: recording automated test results with nexmap utest
  • AI agents: agent guide, MCP server, what agents may change

API

  • REST API: authentication, response format, endpoints

Contributing

nexmap is developed in its repository, which also holds the material for contributors that this documentation leaves out:

  • the specification: what nexmap shall do, with requirement identifiers. It describes targets, not necessarily the current behaviour;
  • the design decisions and their reasoning;
  • CONTRIBUTING.md: building, testing, and how this documentation is organised and published.

Concepts

nexmap keeps everything about a product in one place: what it is made of, what it must do, the work to get there, how it is tested and what is broken. This page explains the building blocks and how they relate.

Products

Everything in nexmap belongs to a product. Each product has its own team, settings, structure, backlog and tests; nothing is shared between products. The dashboard shows the products you are a member of, and the product switcher in the header moves between them while staying in the same section.

Structure: components and capabilities

  • Components are the parts a product is built from, such as the server, the mobile app or the billing module.
  • Capabilities are what the product can do, such as “export reports” or “sign in with 2FA”. A capability belongs to a component, or directly to the product if it spans several.

Structure is optional, but it is what everything else hangs off: requirements, constraints, test cases and defects can all be linked to capabilities, which tells you, per feature area, what is required, how it is tested and what is broken.

Requirements, tasks and constraints

These three look alike, but they are different things:

RequirementTaskConstraint
What it isA statement of what the product must doWork that says nothing about the product: maintenance, tooling, researchA rule the product must keep following
Example“Users can export a report as PDF”“Upgrade the PDF library”“All traffic is encrypted with TLS”
Done whenIts definition of done holdsIt is finishedNever: it is met or violated, continuously
Moves through the workflowYesYesNo; it has a compliance state instead
Linked to capabilities and test casesYesNoCapabilities only

Each one gets an identifier from its category: FR-12 is the 12th item in the product’s numbering, in the category with prefix FR. Requirements and tasks share one counter, so FR-12 and MT-12 can never both exist. Use identifiers in conversation, commit messages and code comments.

New products start with these categories, which the product owner can change:

  • Requirements: Functionality (FR), Architecture (AR), Security (SR), UX (UR)
  • Tasks: Maintenance (MT), Tooling (TL), Spike (SP)
  • Constraints: Architecture (AC), Security (SC), UX (UC)

Requirements and tasks can be broken down: a requirement can have child requirements or tasks, and a task can have child tasks. A requirement never sits below a task. Items that still need breaking down can be marked coarse.

Together, the open requirements and tasks are the product’s backlog. See Backlog.

Defects

A defect is something that does not work as it should. It has a criticality (low, medium, high, critical), moves through the same kind of workflow as the backlog, and can be linked to the components, capabilities, requirements and test cases it affects.

Test cases and testing campaigns

  • A test case describes one check. It is manual (a person carries it out) or automatic (a test in code, identified by its test identifier, such as a Go test function name). It can run in several runtime environments, such as operating systems or browsers, and sit at a level of the test pyramid (by default Unit, Integration, End-to-End).
  • A testing campaign is one round of testing: a selection of test cases, each with a result per environment. Results start as pending and become passed, failed, blocked, skipped or re-test. A failed result always names the defect that explains it.

See Testing.

Milestones and sprints

  • Milestones mark points on the product’s roadmap. Their type says what they are: planning, release or internal. Components, capabilities, requirements, constraints, defects and sprints can be assigned to a milestone; an item belongs to at most one.
  • Sprints are time boxes of work, used when a product works in scrum mode. See Backlog.

Comments, attachments and history

Requirements, tasks, constraints, defects, test cases, components, capabilities and milestones all have comments and attachments, and every change to them is recorded in their History tab: who changed what, and when. Descriptions and comments are written in Markdown.

Roles

What you can do in a product depends on your role in it. Each role includes everything the roles below it can do:

RoleCan
Product ownerEverything in the product: settings, team, structure, backlog, campaigns
DeveloperCreate and edit test cases; comment; change the status of requirements, tasks and defects once they are activated
TesterRecord campaign results; create defects; comment on defects and test cases
ViewerRead everything, change nothing

Administrators manage the whole installation (users and instance settings) and can act in every product.

Getting started

This page walks a product owner through setting up a product: creating it, adjusting its settings, bringing in the team and describing its structure. The terms are explained in Concepts.

Create a product

On the dashboard, choose Create Product (/products/new). Any user can do this if the administrator has enabled product creation; administrators always can. You become the product’s owner.

FieldMeaning
Name, descriptionShown on the dashboard and the product overview
Estimation modeHow backlog items are estimated: story points, t-shirt sizes, hours, or disabled
Workflow modeKanban (one continuous board) or scrum (work planned into sprints); see Backlog

Adjust the settings

Settings in the product’s sidebar has five tabs. Changes are saved as you make them.

  • General: name, description, estimation mode and workflow mode.
  • Categories: the categories of requirements, tasks and constraints, each with a name and an identifier prefix. The defaults are listed in Concepts. Settle the prefixes early, before identifiers like FR-12 appear in commit messages.
  • Statuses: the workflow columns. New products start with New, In Progress, In Review and Done. One status is the default for new items; statuses marked closed (like Done) count as finished, so their items drop out of “open” lists. At first, requirements, tasks and defects share one set of statuses. If defects need a different workflow, split the sets; splitting cannot be undone.
  • Environments: the runtime environments tests run in, such as linux, windows or firefox. Name them the way your CI jobs will (see CI integration).
  • Pyramid levels: the levels of the test pyramid, by default Unit, Integration and End-to-End.

Bring in the team

On the Team page, search for existing users, add them with a role, and change or remove roles later. The roles are explained in Concepts.

People who have no account yet can be invited:

  1. Under Invite New User, enter their email address and the role they should get, and choose Send Invitation.
  2. nexmap shows an invitation link. It does not send emails, so pass the link on yourself, through a channel only that person reads: whoever opens it can use it.
  3. The link opens a page where they choose a username and password. Their account gets the invited email address, joins the product with the invited role, and they are signed in right away.

An invitation link works once and expires after 7 days. Accounts created from an invitation never need an administrator’s approval.

If the administrator has enabled self-registration, people can also create an account themselves on the sign-in page (Register) and then be added to the product by you. With moderation enabled, such an account waits for an administrator’s approval under /admin/users before it can sign in.

To hand the product over, use Transfer Ownership next to a member. They become the owner and you become a developer.

Describe the structure

Under Structure in the sidebar:

  1. Create the components the product consists of.
  2. Create capabilities, each under a component or at product level.
  3. Optionally, lay out the roadmap as milestones.

The structure can grow over time; nothing else depends on it being complete.

Fill the backlog and the test catalogue

  • Write down the rules that always apply as constraints, and what the product must do as requirements, each with a definition of done. Link both to the capabilities they concern. See Backlog.
  • Describe how requirements are verified as test cases, linked to the requirements they cover. For automated tests, nexmap test sync can create the test cases from a test run instead; see CI integration.
  • When it’s time to test, run a testing campaign; see Testing.

Your own account

Under your user menu:

  • Profile (/user/profile): display name, email address, password, theme, two-factor authentication, and a request to delete your account.
  • API Tokens (/user/tokens): personal access tokens for the CLI.

Backlog

The backlog holds the product’s requirements, tasks and defects, and the boards and sprints you work them off with. Constraints live next to them. The differences between these are explained in Concepts.

Requirements and tasks

Requirements and Tasks in the sidebar list them separately. Product owners create and edit them.

FieldMeaning
CategoryDecides the identifier prefix, such as FR or MT
Title, descriptionThe description is written in Markdown
Definition of doneRequirements only: when the requirement counts as fulfilled. Test cases can be linked to it
ParentBreaks work down: a requirement under a requirement, a task under either
StatusPosition in the workflow; new items get the product’s default status
MilestoneThe milestone this item contributes to
SprintScrum mode only: the sprint the item is planned into
PriorityA number; lower numbers mean higher priority
EstimateOnly when the product uses estimation, in the product’s unit
CoarseMarks an item that still needs breaking down

A requirement’s detail page shows its links in tabs: the capabilities it concerns, the test cases that verify it, and the defects reported against it. Tasks have no such tabs, because they make no statement about the product to verify.

Constraints

Constraints are rules that always apply, such as “all traffic is encrypted”. Instead of a workflow status, each has a compliance state: not assessed, met or violated. Review constraints when designing a change, and set a constraint to violated when the product breaks it, so the problem stays visible until it is fixed. Constraints can be linked to capabilities and assigned to a milestone.

Defects

Defects records what is broken. Testers, developers and product owners can report defects, testers usually from a failed test result (see Testing). Editing a defect’s fields is up to product owners; developers change its status (see Activation).

A defect has a criticality (low, medium, high, critical), a status, optionally the runtime environment it occurs in, and links to the components, capabilities, requirements and test cases it affects. From a defect’s Test Cases tab you can link an existing test case or create a regression test for it.

Moving a defect into a closed status (such as Done) puts every failed result it explains back to re-test in its testing campaign; see Testing.

Working with lists

All lists work the same way:

  • The search box matches the text columns; filters narrow down columns with a fixed set of values, such as status or category. A single control resets search and filters.
  • Clicking a column header sorts ascending, then descending, then back to the default order. Statuses sort in workflow order and criticalities by severity.
  • Search, filters, sorting and the page are part of the URL, so a filtered view can be bookmarked or shared.

Kanban or scrum

The product’s workflow mode decides how work is organised.

Kanban

Board shows all requirements, tasks and defects in one column per status. Drag a card to another column to change its status. If the product’s statuses have been split, requirements and defects get separate boards. Defects also has a board of its own.

Scrum

Work is planned into sprints, each with a start and end date and optionally a milestone it leads to. A sprint’s detail page has four tabs:

  • Board: the sprint’s items in status columns, as on the kanban board.
  • Planning: the product backlog next to the sprint’s contents; drag items between them. Only product owners can plan.
  • Summary: how many items are in which status, progress, and estimate totals.
  • History: the sprint’s changes.

A sprint is planned until its owner chooses Start Sprint, and completed after Close Sprint. Only one sprint per product can be active at a time. Closing a sprint does not move unfinished items; plan them into the next sprint.

Activation: what developers may move

Developers may change the status of a requirement, task or defect only once a product owner has activated it. This keeps the product owner in charge of what gets worked on. Product owners can always change statuses.

  • In scrum mode, planning an item into a sprint activates it, and taking it out of the sprint deactivates it again.

  • In kanban mode, items are meant to be activated directly, but the web UI has no control for this yet. Until it does, a product owner can activate an item with the CLI:

    nexmap api PATCH /products/{product}/requirements/FR-12 --data '{"is_active": true}'
    

The detail page shows whether an item is active.

Testing

Testing in nexmap has two parts: a catalogue of test cases that describes how the product is verified, and testing campaigns that record the results of one round of testing.

Test cases

Test Cases in the sidebar lists the catalogue. Developers and product owners create and edit them.

FieldMeaning
Title, descriptionWhat is checked and how; for manual tests, the steps to follow
Execution modeManual (a person carries it out) or automatic (a test in code)
Test identifierAutomatic tests only: the name that test results report, such as a Go test function name. It connects CI results to the test case
Component, capabilityWhat part of the product the test case covers
Pyramid levelUnit, Integration, End-to-End, or the product’s own levels
EnvironmentsThe runtime environments the test runs in. A campaign tracks one result per selected environment; with none selected, there is a single result

On a test case’s Requirements tab, link the requirements it verifies, either the requirement as a whole or its definition of done. The Campaigns tab shows how the test case fared across campaigns.

Automated tests can be registered in bulk from a test run with nexmap test sync; see CI integration.

Testing campaigns

A campaign moves through three states:

  1. Draft: a product owner creates it under Campaigns with a name, an optional description and planned dates, and selects the test cases in scope. Filters for component, capability, pyramid level and execution mode, plus a select-all over the filtered list, help to pick them. The scope can still change while the campaign is a draft.
  2. Active: the product owner activates it. The scope is now fixed: test cases created later do not join. Results can only be recorded while a campaign is active, and only one campaign per product can be active at a time.
  3. Closed: the product owner closes it, which freezes all results. Closing a campaign with results that are still pending, blocked or awaiting re-test asks for confirmation first.

Recording results

The campaign’s Matrix tab shows test cases in rows and environments in columns, with each result’s status in its cell. Rows can be grouped by component or by pyramid level. Testers, developers and product owners click a cell to set its result:

StatusMeaning
PendingNot run yet; every result starts here
PassedThe check succeeded
FailedThe check failed; requires a defect
BlockedThe check could not be carried out, for example because something it depends on is broken
SkippedDeliberately not run in this campaign
Re-testThe defect behind a failure was fixed; run the check again

A small amber dot on a cell marks a result whose status has changed more than once in this campaign.

Marking a result as failed always requires a defect that explains it: link an existing one, or create one on the spot. The defect is linked to the test case automatically.

Automated tests record their results from CI instead; see CI integration.

The defect loop

When a defect that explains failed results is moved into a closed status, those results move to re-test. They are not marked as passed automatically: a tester runs the check again and records the outcome. For automated tests, the next green CI run does this.

Progress and verdict

The Progress tab shows how many results are in which status and how that changed over time; the History tab lists every result change with who made it and when.

A campaign is complete when no result is pending, blocked or awaiting re-test. It passes when every result that was not skipped has passed.

Installation

Container image

The Dockerfile’s default stage is the server image: nexmapd alone on a distroless base, running as a non-root user, listening on port 8080 on all interfaces, with a HEALTHCHECK (nexmapd healthcheck). It keeps the SQLite database and attachments in /data, so mount persistent storage there. Migrations run automatically when the server starts.

When building with podman, pass --format docker: the default OCI image format drops the HEALTHCHECK.

Coolify

Create the application with the Dockerfile build pack and:

  • add persistent storage (a volume) with destination path /data;
  • set the environment variables, at least NEXMAP_ENCRYPTION_KEY (see generating a key), and NEXMAP_ADMIN_USER / NEXMAP_ADMIN_PASSWORD for the first start; for PostgreSQL, NEXMAP_DB_DRIVER=postgres and NEXMAP_DB_DSN. All variables are listed in the configuration reference;
  • nothing for the version: the build reads it from the repository’s git tags, as go build does locally (v0.1.0 on a tagged commit, v0.1.1-0.<date>-<commit> after it). If the checkout has no tags (a shallow clone), it reports v0.0.0-<date>-<commit>; set the build variable VERSION to override it.

Ports (8080) and the health check come from the image. Coolify’s proxy terminates TLS; see Security for what the server expects from it.

After the first start

Sign in as the administrator created from NEXMAP_ADMIN_USER / NEXMAP_ADMIN_PASSWORD (or with the generated password from the log, see the first administrator). Then change the administrator’s email address on the profile page, and review the instance settings under /admin/settings: self-registration, moderation, product creation, enforced 2FA and password rules.

Server configuration

nexmapd is configured through environment variables, most of which also have a command line flag. Each setting is taken from the first source that has it: flag, then environment variable, then the built-in default. There is no configuration file.

The container image sets some variables differently from the built-in defaults; both are listed below.

Reference

Network

VariableFlagDefaultImageMeaning
NEXMAP_HOST--hostlocalhost0.0.0.0Address to listen on. 0.0.0.0 listens on all interfaces
NEXMAP_PORT--port80808080Port to listen on

nexmapd serves plain HTTP. Put it behind a reverse proxy that terminates TLS; see Security.

Database

VariableFlagDefaultImageMeaning
NEXMAP_DB_DRIVER--db-driversqlitesqlitesqlite or postgres
NEXMAP_DB_DSN--db-dsnnexmap.db/data/nexmap.dbFor SQLite, the database file path. For PostgreSQL, a connection URL (postgres://user:password@host:5432/nexmap?sslmode=require) or a key/value string (host=… user=… dbname=…)

SQLite suits small installations: one file, no separate server, run in WAL mode with a single connection. PostgreSQL suits larger ones; nexmapd opens up to 25 connections. Either way, the database is created or migrated automatically at startup, and the server refuses to start if it cannot reach it.

Encryption

VariableFlagDefaultMeaning
NEXMAP_ENCRYPTION_KEY—none, requiredKey ring for encrypting personal data, as JSON. See Security

The server does not start without it.

First administrator

VariableFlagDefaultMeaning
NEXMAP_ADMIN_USER—adminUsername of the administrator created on first start
NEXMAP_ADMIN_PASSWORD—generatedPassword of that administrator

These are only read when the database has no users yet, i.e. on the very first start. See Security.

Sessions and passwords

VariableFlagDefaultMeaning
NEXMAP_SESSION_TTL--session-ttl24hHow long a web UI session lasts, as a Go duration (8h, 30m, 168h)
NEXMAP_COOKIE_SECURE--cookie-securetrueMark session cookies Secure, so browsers send them over HTTPS only. Set to false only for local development over plain HTTP
NEXMAP_PBKDF2_ITERATIONS--pbkdf2-iterations600000PBKDF2-SHA256 iterations for password hashes

Each password hash records its own iteration count, so changing NEXMAP_PBKDF2_ITERATIONS applies to passwords set from then on; existing passwords keep working.

Attachments

VariableFlagDefaultImageMeaning
NEXMAP_UPLOAD_DIR--upload-dir./uploads/data/uploadsDirectory for uploaded attachments
NEXMAP_MAX_UPLOAD_BYTES--max-upload-bytes10485760 (10 MiB)Largest accepted attachment, in bytes
NEXMAP_ALLOWED_UPLOAD_TYPES--allowed-upload-typesempty: all typesComma-separated presets and/or file extensions

The presets are images (.png, .jpg, .jpeg, .gif, .webp), logs (.txt, .log, .json, .xml, .csv, .md, .yaml, .yml) and documents (.pdf). For example, NEXMAP_ALLOWED_UPLOAD_TYPES=images,logs,zip allows those presets plus .zip files.

Besides the extension, the server checks the file content: a file named .png must really be an image, and a text extension rejects content recognised as an archive, PDF or media file. This stops mislabelled uploads but is not a malware scanner. Attachments are always served as downloads, never rendered or executed by the browser.

Logging

VariableFlagDefaultMeaning
NEXMAP_LOGLEVEL--log-levelinfodebug, info, warn or error
NEXMAP_LOGFILE--log-fileempty: stdoutWrite the log to this file (appended, mode 0600) instead of stdout

Logs are JSON, one entry per line.

Development

VariableFlagDefaultMeaning
NEXMAP_DEV_MODE—falseEnables the component catalogue at /dev/catalogue and fills a newly created database with demo products and users. Never enable it in production

Value formats

  • Boolean variables (NEXMAP_COOKIE_SECURE, NEXMAP_DEV_MODE) are true for true or 1; any other value means false.
  • An invalid duration or number (NEXMAP_SESSION_TTL, NEXMAP_PBKDF2_ITERATIONS, NEXMAP_MAX_UPLOAD_BYTES) is ignored and the default is used, without a warning. Check the values carefully.
  • An unknown log level or database driver stops the server with an error.

Health check

nexmapd healthcheck requests /api/v1/health from the server running with the same configuration and exits with 0 if it answers. The container image uses it as its HEALTHCHECK; outside a container, the endpoint can be polled directly.

Minimal production setup

NEXMAP_ENCRYPTION_KEY='[{"id":"2026-01","key":"<base64 key>"}]'
NEXMAP_ADMIN_USER=jdoe
NEXMAP_ADMIN_PASSWORD='<long random password>'
# for PostgreSQL instead of SQLite:
NEXMAP_DB_DRIVER=postgres
NEXMAP_DB_DSN='postgres://nexmap:<password>@db:5432/nexmap?sslmode=require'

Behind a TLS-terminating proxy, the remaining defaults are suitable for production.

Security

This page covers what an administrator has to set up and look after to run nexmap safely. Every setting mentioned is described in the configuration reference.

TLS and the reverse proxy

nexmapd speaks plain HTTP. Run it behind a reverse proxy that terminates TLS (on Coolify, the built-in Traefik does this), and do not expose its port to the network directly:

  • Leave NEXMAP_COOKIE_SECURE at true. Browsers then send the session cookie over HTTPS only.
  • The server records the X-Forwarded-For header, without checking it, as the client address of sessions and failed logins. Only a proxy in front of the server makes that address trustworthy, and the proxy should replace a header sent by the client rather than append to it.

The encryption key

Personal data (email addresses and display names) is encrypted in the database with AES-256-GCM. Usernames are treated as pseudonyms and are not encrypted. The key comes from NEXMAP_ENCRYPTION_KEY, a JSON list of keys with an ID and a base64-encoded 32-byte key each:

[{"id": "2026-01", "key": "<32 random bytes, base64-encoded>"}]

To generate one:

printf '[{"id":"%s","key":"%s"}]\n' "$(date +%Y-%m)" "$(openssl rand -base64 32)"

Treat the key like a database password: keep it in your platform’s secret store, not in a repository.

Back up the key separately from the database. Without it, the personal data in a database backup cannot be decrypted, and nexmap has no way to recover it. Conversely, a database backup stored together with its key protects nothing.

Rotating the key

The first key in the list encrypts new data; the others are kept only to decrypt data written with them. To switch to a new key, put it first and keep every older key in the list:

[{"id": "2027-01", "key": "<new key>"}, {"id": "2026-01", "key": "<old key>"}]

Never remove an old key: data encrypted with it would become unreadable.

Rotation is incomplete today, so rotate only when you have a reason to, such as a leaked key:

  • Existing data is not re-encrypted. It stays encrypted with the old key until the record is next changed, so a leaked old key still exposes it.
  • Email addresses also get a lookup hash derived from the first key, which is how the database prevents two accounts from sharing an address. After a rotation, that check only compares addresses hashed with the same key, so a duplicate of an address stored under the old key is not caught.

The first administrator

On the very first start, when the database has no users, nexmapd creates an administrator from NEXMAP_ADMIN_USER and NEXMAP_ADMIN_PASSWORD. If the username is unset it is admin; if the password is unset, a random one is generated and written to the log.

Set both variables for the first start. If you rely on the generated password, read it from the log, sign in, change the password right away, and keep in mind that the log still contains it. The administrator’s email address starts out as admin@localhost; change it on the profile page.

Once users exist, both variables are ignored and can be removed.

Accounts and sign-in

  • Two-factor authentication: users can set up TOTP on their profile page. Enforce 2FA in the instance settings (/admin/settings) requires it for everyone. An administrator can reset a user’s 2FA from the user’s detail page, after which that user can sign in with the password alone until they set it up again.
  • Passwords are stored as salted PBKDF2-SHA256 hashes (NEXMAP_PBKDF2_ITERATIONS). The instance settings can require uppercase and lowercase letters, digits and special characters in new passwords.
  • Email is not implemented yet: there are no password reset or invitation emails. Administrators set new passwords for users from the user’s detail page.
  • Sessions expire after NEXMAP_SESSION_TTL (24 hours by default).

New accounts

People get an account in one of two ways:

  • Invitation: a product owner invites someone to their product with a role (on the product’s Team page), or an administrator invites someone to the installation (/admin/invitations). nexmap shows a link, which the inviter passes on. Whoever holds the link can create the account, so treat it as a secret: it works once, expires after 7 days, and is never written to the log. Accounts created from an invitation skip moderation.
  • Self-registration, if self-registration is enabled in the instance settings: anyone who can reach the server can create an account on /register. With moderation enabled, such accounts wait for an administrator’s approval under /admin/users before they can sign in. A self-registered account belongs to no product until a product owner adds it.

Only product owners (and administrators) can invite to a product, and only administrators can invite to the installation without a product.

Access tokens

Personal access tokens, which the CLI, CI jobs and AI agents use, carry every right of the user who created them; they cannot be restricted to reading or to one product yet. For automation, create a dedicated user with the lowest product role that does the job (usually tester for CI), and revoke tokens that are no longer used under API Tokens (/user/tokens).

Attachments

By default, every file type can be uploaded. To restrict this, set NEXMAP_ALLOWED_UPLOAD_TYPES; the server then also checks that the content matches the extension. Attachments are always served as downloads and never rendered by the browser, but nexmap does not scan them for malware.

Development mode

NEXMAP_DEV_MODE fills a new database with demo users whose password is publicly known (it is in the source code and the log). Never enable it on a server that others can reach.

Setting up the CLI

nexmap is the command line client for a nexmap server. It talks to the server only through the REST API, so it needs nothing but the server’s URL and a personal access token.

Installing

Where your Go module proxy can reach the module:

go install nexmap.net/cmd/nexmap@<version>

Alternatively, build it from a checkout (just build, or just container-build for release binaries; see CONTRIBUTING.md). The binary has no runtime dependencies.

Logging in

  1. In the web UI, open API Tokens in the user menu (/user/tokens) and create a token.

  2. Store it:

    nexmap login --server https://nexmap.example.com
    

    On a terminal, login prompts for the token without echoing it. Otherwise it reads the first line of stdin, which suits scripts: printf '%s\n' "$TOKEN" | nexmap login --server …. There is deliberately no flag for the token itself, as it would end up in shell history and process listings.

login checks the token with the server and stores it per server in the user configuration file (config.json in the user configuration directory, see below), readable only by you. nexmap logout removes it again; nexmap whoami shows the user, server and product in effect and where each value came from.

A token carries all the rights of the user who created it. For CI jobs and AI agents, create a dedicated user with only the product role the job needs.

Connecting a repository

In the repository, run:

nexmap init

This writes .nexmap.json at the repository root (or into --dir) with the server and product:

{
  "server": "https://nexmap.example.com",
  "product": "<product id>"
}

The product comes from --product if given; if you are a member of only one product, that one is used; otherwise init lists the products and asks. Without a terminal it fails and lists the choices instead, so pass --product in scripts. --force overwrites an existing file, and --agents additionally sets up AI agents (see AI agents).

Commit .nexmap.json: it never contains a token, and every command run anywhere in the repository finds it by walking up from the current directory.

Configuration

Each setting is taken from the first source that has it, in this order:

  1. Command line flags
  2. Environment variables
  3. .nexmap.json in the current directory or the nearest parent
  4. The user configuration file
SettingFlagEnvironment.nexmap.jsonUser configuration
Server URL--serverNEXMAP_SERVERserver—
Product ID--productNEXMAP_PRODUCTproduct—
Output format-o, --outputNEXMAP_OUTPUT—output
Token—NEXMAP_TOKEN—stored per server by login
Plain HTTP--allow-httpNEXMAP_ALLOW_HTTP——

NEXMAP_TOKEN takes precedence over a stored token; login warns when it is set. Boolean variables accept 1, true, yes or on.

The user configuration directory is nexmap inside the operating system’s configuration directory: $XDG_CONFIG_HOME/nexmap (usually ~/.config/nexmap) on Linux, ~/Library/Application Support/nexmap on macOS, %AppData%\nexmap on Windows. Set NEXMAP_CONFIG_DIR to use another directory.

Plain HTTP

The CLI refuses http:// servers unless they run on the local machine (localhost, *.localhost or a loopback address), so a token never crosses the network unencrypted. --allow-http (or NEXMAP_ALLOW_HTTP=1) lifts this deliberately, e.g. on a trusted internal network.

Commands

CommandPurpose
login, logout, whoamiManage the stored token, show what is in effect
initWrite .nexmap.json; with --agents, set up AI agents
versionClient version and, if a server is configured, the server version
contextProduct briefing: structure, constraints, work in progress, defects, testing status
req, taskRequirements and tasks: list, show, status, create
constraintlist, show
component, capabilityProduct structure
defectlist, show, status, create
testTest cases: list, show, sync
campaignstatus of the active testing campaign
commentadd a comment to any commentable item
utestRecord test results from CI, see CI integration
apiRaw REST call, see Scripting
agent-guide, mcpFor AI agents, see AI agents

nexmap help <command> (e.g. nexmap help req show) lists a command’s arguments and flags.

Requirements, tasks and constraints are referenced by their identifier (FR-12, MT-3, AC-42), defects and test cases by their short ID (the first 8 characters of the ID, shown in lists), test cases also by their test identifier, and capabilities by name.

Scripting with the CLI

The output formats and exit codes below are a stable contract for scripts and AI agents.

Output formats

FormatUse
-o tablePlain tables and records for people. The default
-o mdCompact markdown with the same headings every time; best for pasting into an AI agent’s context
-o jsonMachine-readable, with stable field names; use it for anything that parses the output

The default is table whether or not stdout is a terminal, so a command’s output never changes with the environment it runs in: scripts must ask for -o json explicitly. To change your personal default, set NEXMAP_OUTPUT or output in the user configuration file (see Setup).

Errors and exit codes

Exit codeMeaning
0Success
1Usage or configuration error (bad arguments, no server configured, ambiguous reference)
2API error: server unreachable, authentication failed, or not permitted for your role
3Not found: the item does not exist or is not visible to you
4Tests failed (only utest --exit-code)

Errors go to stderr as a single line, nexmap: <message>, followed by a hint for authentication and role errors. With -o json, stderr carries {"error": {"code": "…", "message": "…"}} instead.

A role refusal (exit 2) is final: retrying will not help; the user needs a different role in the product.

Long texts

Commands that take a description or comment body accept it from a file or stdin (--body-file -), which avoids shell quoting problems with markdown:

nexmap comment add FR-12 --body-file - <<'EOF'
Decided to keep the old endpoint until **v2**.
EOF

Raw API calls

For anything without a dedicated command, nexmap api sends an authenticated request to the REST API and prints the response:

nexmap api /products/{product}/requirements -o json
nexmap api POST /products/{product}/requirements --data @new.json
generate-body | nexmap api POST /products/{product}/requirements --data @-

The method defaults to GET; {product} in the path is replaced with the configured product; --data takes JSON text, @file or @- for stdin.

Retries

Read requests (GET, HEAD, OPTIONS) are retried automatically after network errors and 429, 502, 503 and 504 responses. Requests that change data are sent once, so a failed write is never applied twice behind your back.

CI Integration

nexmap utest records automated test results in the product’s active testing campaign (FR-018, FR-409). It reads a test report from stdin or from files, maps each test to an automatic test case by its test identifier, and uploads the run in one request.

go test -json ./... | nexmap utest --env linux-amd64
go test -v ./...    | nexmap utest --identifier-style name   # format auto-detected
nexmap utest --format junit reports/*.xml

What happens to a run

SituationEffect
Test passes / is skippedIts campaign result is set to passed / skipped; a defect on the result is cleared
Test failsResult set to failed. An open defect already on the result is reused; otherwise a defect Automated test failed: <identifier> is created with the run metadata and the test’s output (last 64 KiB), linked to the test case
Status unchanged since the last runNothing is written, so repeated CI runs do not flood the result history
Subtests (TestX/case) without their own test caseRolled up into TestX: any failure fails it, the failing subtests’ output goes into the defect
Test case runs per environmentNeeds --env NAME matching a runtime environment of the product; other environments’ results are untouched
No automatic test case has the identifier, or the test case is not in the campaignNot recorded, no defect; failures are still listed in the build log
No active campaignThe upload is accepted, nothing is written (no results, events or defects), and the CLI prints no active campaign — results not recorded

The FR-504 loop works with automation: closing the defect moves the result to re-test, and the next green run marks it passed. A later failure files a new defect, since the old one is closed.

Each status change is recorded in the result history with the token’s user, the report format (go-test, junit), and the revision and branch.

Setting up

  1. Create a CI user with the tester role in the product (tokens carry their user’s full rights, so do not use a personal token), sign in as that user and create a personal access token under API Tokens in the user menu (/user/tokens).
  2. Register the tests as automatic test cases with a test identifier. nexmap test sync creates the missing ones from a report (developer role):
    go test -json ./... | nexmap test sync --dry-run   # review first
    go test -json ./... | nexmap test sync
    
    New test cases join campaigns created afterwards; an active campaign keeps the scope it was created with.
  3. Check the mapping before wiring CI: go test -json ./... | nexmap utest --dry-run shows how each test maps to a test case and what would change.
  4. Install the CLI in the job (see Setup): a release binary, or go install nexmap.net/cmd/nexmap@<version> where your Go proxy can reach the module (the snippets below assume the latter).
  5. Configure the job with NEXMAP_SERVER, NEXMAP_PRODUCT and the secret NEXMAP_TOKEN (or commit .nexmap.json from nexmap init and only set the token; see Setup).

Options

FlagMeaning
--format auto|go-json|go-text|junitReport format; auto sniffs the input (JSON events → go-json, XML → junit, else go test -v text)
--identifier-style package|namepackage (default): pkg/path.TestName for Go, classname.name for JUnit; name: the bare test name. Match whatever your test cases use
--env NAMERuntime environment of this run (case-insensitive); unknown names are rejected
--campaign NAMERecord in this campaign instead of the active one (must be active)
--revision, --branchDefault from GITHUB_SHA/GITHUB_REF_NAME, CI_COMMIT_SHA/CI_COMMIT_REF_NAME, … or git rev-parse
--dry-runEvaluate and print the mapping; write nothing
--exit-codeExit 4 if any test (or a package build) failed
--quietDo not copy stdin to stdout

When reading stdin, utest copies its input to stdout so the test log stays in the build output, then prints a summary. With -o json it prints only the JSON report.

Exit status. In go test -json ./... | nexmap utest, the shell reports the exit status of nexmap, not of go test. Either let nexmap fail the job with --exit-code, or use set -o pipefail (bash) so a failing go test still fails it. Without either, the job passes even when tests fail.

GitHub Actions

name: test
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: stable
      - name: Install nexmap
        run: go install nexmap.net/cmd/nexmap@latest
      - name: Test
        env:
          NEXMAP_SERVER: https://nexmap.example.com
          NEXMAP_PRODUCT: ${{ vars.NEXMAP_PRODUCT }}
          NEXMAP_TOKEN: ${{ secrets.NEXMAP_TOKEN }}
        run: go test -json ./... | nexmap utest --env linux-amd64 --exit-code

For a build matrix, pass the matrix value: --env ${{ matrix.os }}, with one runtime environment per OS in the product.

GitLab CI

test:
  image: golang:1.26
  variables:
    NEXMAP_SERVER: https://nexmap.example.com
    NEXMAP_PRODUCT: "<product id>"
    # NEXMAP_TOKEN: set as a masked CI/CD variable
  script:
    - go install nexmap.net/cmd/nexmap@latest
    - go test -json ./... | nexmap utest --env linux-amd64 --exit-code

Forgejo / Gitea Actions

Forgejo Actions sets the same GITHUB_* variables, so revision and branch are picked up automatically.

on: [push]
jobs:
  test:
    runs-on: docker
    container:
      image: golang:1.26
    steps:
      - uses: https://code.forgejo.org/actions/checkout@v4
      - run: go install nexmap.net/cmd/nexmap@latest
      - env:
          NEXMAP_SERVER: https://nexmap.example.com
          NEXMAP_PRODUCT: ${{ vars.NEXMAP_PRODUCT }}
          NEXMAP_TOKEN: ${{ secrets.NEXMAP_TOKEN }}
        run: go test -json ./... | nexmap utest --env linux-amd64 --exit-code

Other ecosystems

Anything that writes JUnit XML works: pytest (--junitxml=report.xml), jest (jest-junit), Maven Surefire (target/surefire-reports/*.xml), gotestsum (--junitfile).

pytest --junitxml=report.xml; status=$?
nexmap utest --format junit report.xml
exit $status

With the default package style, pytest identifiers look like tests.test_calc.test_add and Surefire ones like com.example.CalcTest.testAdd; jest-junit’s default (classname equal to name) yields the test title.

The upload endpoint behind nexmap utest is described in the REST API reference.

AI agents

AI coding agents working in a repository can read the product definition, backlog and QA state from nexmap, and, within their user’s role, update it. They use either the CLI or, for agents that speak the Model Context Protocol (Claude Code and others), the same operations as MCP tools.

Setting up a repository

After connecting the repository and logging in:

nexmap init --agents

This does two things on top of nexmap init (an existing .nexmap.json is kept):

  • registers the MCP server in the repository’s .mcp.json, keeping any other servers in it:

    {
      "mcpServers": {
        "nexmap": {"command": "nexmap", "args": ["mcp", "--read-only"]}
      }
    }
    

    An existing nexmap entry is left alone unless you pass --force.

  • appends the agent guide (nexmap agent-guide) to CLAUDE.md if that exists, otherwise to AGENTS.md, once.

The MCP server uses the same configuration and token as the CLI, so nothing else is needed. If the configuration or token is missing, the server still starts and every tool call returns the reason (e.g. “run nexmap login”), which the agent can pass on to you.

What agents can do

nexmap agent-guide prints the guide agents are given: the commands and tools, how items are referenced, and working rules such as treating constraints as binding. Run it to see exactly what an agent is told. In short:

  • Read: nexmap context -o md (or the get_context tool) for a product briefing, then requirements, tasks, constraints, structure, defects, test cases and the campaign status as needed.
  • Write: set the status of requirements, tasks and defects, add comments, report defects, and create tasks, each only as far as the user’s role allows (see below).

Allowing writes

init --agents registers the MCP server with --read-only, which leaves out all tools that change data. To let agents write through MCP, remove --read-only from the entry in .mcp.json. The CLI’s write commands are always available to an agent that can run shell commands.

Writes are checked by the server against the token user’s role in the product, exactly as in the web UI:

ActionRole needed
Set the status of a requirement, task or defectdeveloper, and only on items a product owner has activated (directly or by planning them into a sprint)
Commentdeveloper; tester on defects and test cases
Report a defecttester
Create a taskproduct owner

Each role includes the ones below it (product owner, developer, tester, viewer), and administrators may do everything.

A token carries every right of its user, and tokens cannot be narrowed yet. Give agents their own user with the lowest role that covers what they should do, rather than your personal token.

REST API

The REST API under /api/v1 is the interface for the nexmap CLI and third-party systems. The web UI uses its own endpoints and does not go through it.

Authentication

Requests authenticate with a personal access token, created in the web UI under /user/tokens:

Authorization: Bearer <token>

A token carries the full rights of the user who created it. For automation, create a dedicated user with the role the job needs rather than using a personal token.

Responses

Successful responses wrap their payload in data; errors carry a machine-readable code and a human-readable message:

{"data": { … }}
{"error": {"code": "…", "message": "…"}}

Endpoints

Test runs

POST /api/v1/products/{productId}/test-runs (tester role) takes

{
  "source": "go-test",
  "campaign_id": "",
  "environment": "linux-amd64",
  "revision": "4161949",
  "branch": "main",
  "dry_run": false,
  "results": [
    {"test_identifier": "pkg.TestA", "status": "passed", "duration_ms": 12},
    {"test_identifier": "pkg.TestB", "status": "failed", "output": "b_test.go:9: got 1, want 2"}
  ]
}

and answers with recorded, campaign, environment, a summary (counts per status, per outcome, defects_created, defects_reused, total) and per-test results with outcome (updated, unchanged, unknown_test_case, not_in_campaign, environment_required, not_recorded) and defect (created, reused). Uploads are limited to 32 MiB and 50,000 results; statuses are passed, failed and skipped.