Config File Reference
settings.json is the single structured description of a SeqDesk deployment. It
is the recommended place for non-secret settings you want to be reproducible.
Two things to understand before you edit it:
- Some sections are read by the running application (
pipelines,site,notifications,telemetry,runtime). Change them and restart, and the behaviour changes. - Other sections are install-time inputs (
ena,access,auth,sequencingFiles,moduleSettings). They are applied into the database by the installer,--reconfigure, a hosted install profile, or Settings → Infrastructure → Import settings.json. Editing them on a running install changes what/api/admin/config/statusreports and nothing else.
Each section below is labelled accordingly. The mechanism behind the split is explained in Configuration Sources & Priority.
File discovery
SeqDesk searches the working directory it starts in (the directory holding
package.json — in a packaged install that is <install-dir>/current) in this
order:
settings.json(canonical)seqdesk.config.json(legacy/development fallback).seqdeskrc.seqdeskrc.json
The first file found is used and the rest are ignored. If no config file exists, only environment variables, database settings, and built-in defaults apply.
In a packaged install the real file lives one level up, at
<install-dir>/settings.json, written chmod 600; each release directory holds
a relative symlink (releases/<version>/settings.json -> ../../settings.json),
so an update never leaves two divergent config files behind. Replacing that
symlink with a real file inside a release directory is the most common way to
lose configuration at the next update.
A config file that is not valid JSON is not a fatal error. The
loader logs a warning to the server log and carries on with environment
variables, the database, and defaults — so a syntax error looks like “my whole
config file is being ignored”. Validate before restarting:
node -e “JSON.parse(require(‘fs’).readFileSync(‘settings.json’,‘utf8’))”.
Older installs may still keep their runtime config under the legacy
seqdesk.config.json filename. SeqDesk continues to read it as a
fallback, so existing deployments keep working — no rename is required, and the
installer deliberately keeps whichever of the two names already exists so an
upgrade never splits the live config in two.
Complete structure
{
"app": {
"port": 8000
},
"site": {
"name": "My Sequencing Facility",
"dataBasePath": "/mnt/sequencing/data",
"contactEmail": "facility@example.com"
},
"pipelines": {
"enabled": true,
"databaseDirectory": "/mnt/seqdesk/pipeline-databases",
"execution": {
"mode": "local",
"runDirectory": "./pipeline_runs",
"conda": {
"path": "/opt/conda",
"environment": "seqdesk-pipelines",
"cacheDir": "/net/shared/conda-cache"
},
"slurm": {
"enabled": false,
"queue": "cpu",
"cores": 4,
"memory": "64GB",
"timeLimit": 12
},
"pipelineOverrides": {
"mag": {
"mode": "slurm",
"slurm": {
"queue": "long",
"cores": 16,
"memory": "128GB",
"timeLimit": 48
}
},
"metaxpath": {
"mode": "slurm"
}
}
},
"mag": {
"enabled": true,
"version": "3.4.0",
"stubMode": false,
"skipProkka": true,
"skipConcoct": true
}
},
"ena": {
"testMode": true,
"username": "",
"password": "",
"brokerAccount": false,
"centerName": ""
},
"sequencingFiles": {
"extensions": [".fastq.gz", ".fq.gz", ".fastq", ".fq"],
"scanDepth": 2,
"ignorePatterns": ["**/tmp/**", "**/undetermined/**"],
"autoAssign": false,
"activeWriteMinAgeMs": 30000,
"simulationMode": "auto",
"simulationTemplateDir": "/opt/seqdesk/simulation-templates"
},
"access": {
"departmentSharing": false,
"allowDeleteSubmittedOrders": false,
"allowUserAssemblyDownload": false,
"orderNotesEnabled": true,
"postSubmissionInstructions": ""
},
"auth": {
"allowRegistration": true
},
"moduleSettings": {
"account-validation": {
"allowedDomains": ["example.org"],
"enforceValidation": false
},
"billing-info": {
"pspEnabled": false,
"costCenterEnabled": false
}
},
"telemetry": {
"enabled": false,
"endpoint": "https://seqdesk.org/api/telemetry/heartbeat",
"intervalHours": 24
},
"notifications": {
"enabled": false,
"inApp": {
"enabled": true
},
"provider": "seqdesk-relay",
"relayUrl": "https://seqdesk.org/api/notifications/relay",
"events": {
"order": {
"submitted": true,
"statusChanged": true,
"samplesSent": true
},
"ticket": {
"created": true,
"reply": true
}
},
"userDefaults": {
"orders": true,
"support": true
}
},
"runtime": {
"databaseUrl": "postgresql://seqdesk:replace-with-password@127.0.0.1:5432/seqdesk?schema=public",
"directUrl": "postgresql://seqdesk:replace-with-password@127.0.0.1:5432/seqdesk?schema=public",
"nextAuthUrl": "http://localhost:8000",
"nextAuthSecret": "your-secret-here"
}
}You do not need every section. Omitted keys fall back to their built-in defaults; the file is deep-merged over those defaults rather than replacing them.
Section reference
app — Application runtime
Install-time input. The running server does not read app.port; it is used
when the installer generates start.sh and the PM2 configuration, and by
Settings → Infrastructure → Import settings.json, which warns that a restart
is required.
| Key | Type | Default | Description |
|---|---|---|---|
port | integer | 8000 | App listen port written into the generated start scripts |
site — Facility information
Mixed. dataBasePath is read at runtime; name and contactEmail are
applied into SiteSettings and read from there.
| Key | Type | Default | Description |
|---|---|---|---|
name | string | "SeqDesk" | Display name shown in the UI (stored in SiteSettings.siteName) |
dataBasePath | string | "./data" | Base directory for sequencing data |
contactEmail | string | — | Contact email shown to researchers |
dataBasePath is special: the resolved value is used only when its source is
file or env. Otherwise the SiteSettings.dataBasePath column applies, which
is what Settings → Infrastructure → Data Storage edits. See
Data Storage.
pipelines — Pipeline configuration
Mixed. pipelines.execution.* and pipelines.databaseDirectory are read at
runtime, and this is where the config file has the most direct effect: any value
set there overrides the corresponding admin-saved setting.
pipelines.enabled and pipelines.mag.* are install-time inputs.
| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Install-time. Decides whether the installer sets up Conda and Nextflow. false also clears the install-profile pipeline allowlist when imported. |
databaseDirectory | string | "" | Optional shared root directory for large pipeline database assets |
Which pipelines a running SeqDesk offers is not decided by
pipelines.enabled. It comes from the per-pipeline
PipelineConfig rows edited under Settings →
Pipelines, falling back to the install-profile allowlist
(SiteSettings.extraSettings.installProfilePipelineAllowlist) and,
if neither exists, to “all pipelines enabled”. A hosted profile or an import
can set that allowlist with a pipelines.enable array of pipeline
IDs.
pipelines.execution — Execution settings
| Key | Type | Default | Description |
|---|---|---|---|
mode | "local" | "slurm" | "local" | Where pipelines run |
runDirectory | string | "./pipeline_runs" | Output directory for pipeline runs |
pipelineOverrides | object | {} | Per-pipeline execution defaults keyed by pipeline ID |
The built-in default for runDirectory in the configuration schema
is ./pipeline_runs, but when nothing is configured anywhere the
pipeline executor falls back to /data/pipeline_runs. Set this
explicitly rather than relying on either default.
pipelines.execution.conda — Conda settings
| Key | Type | Default | Description |
|---|---|---|---|
path | string | "/opt/conda" | Conda installation path |
environment | string | "seqdesk-pipelines" | Environment name, or an absolute environment prefix |
cacheDir | string | "" | Shared Nextflow conda cacheDir, so per-process environments are built once and reused across runs |
enabled | boolean | false | Accepted for compatibility and ignored — the pipeline runtime mode is always Conda |
cacheDir earns its place on clusters: a head node with network access
pre-builds each per-process environment into the shared cache, and
network-isolated compute nodes reuse it by content hash instead of reaching
conda-forge themselves.
pipelines.execution.slurm — SLURM settings
| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Submit jobs to SLURM |
queue | string | "cpu" | Partition/queue name |
cores | integer | 4 | CPUs per job |
memory | string | "64GB" | Memory per job |
timeLimit | integer | 12 | Time limit in hours |
options | string | — | Additional sbatch options — not read from this file, see below |
timeLimit is hours, not minutes. It is written verbatim into the job script as
#SBATCH -t <N>:0:0, so a value like 60 asks for sixty hours and will sit
PENDING forever on a partition with a shorter limit.
options has no built-in default, so it never receives a
configuration source label and the pipeline executor discards it. Extra
sbatch flags placed here are silently ignored, and there is no
SEQDESK_SLURM_OPTIONS environment variable either. Set them in
Settings → Infrastructure → Pipeline Runtime, or at install
time with SEQDESK_EXEC_SLURM_OPTIONS.
pipelines.execution.pipelineOverrides — Per-pipeline runtime policy
Each override is keyed by pipeline ID. Use this to keep global execution local
while sending large workflows such as mag or metaxpath to SLURM, or to give
one heavy pipeline a bigger queue than the facility default.
| Key | Type | Description |
|---|---|---|
mode | "inherit" | "local" | "slurm" | Execution target for this pipeline |
slurm | object | Optional per-pipeline queue, cores, memory, timeLimit, options |
nextflowProfile | string | Optional Conda-compatible Nextflow profile for this pipeline |
Flat aliases are also accepted for the nested slurm keys —
slurmQueue, slurmCores, slurmMemory, slurmTimeLimit, slurmOptions, and
clusterOptions (an alias for slurmOptions). Values are normalised on read:
an unrecognised mode, a non-positive core or time-limit value, or an empty
string is dropped rather than applied, and an override that ends up empty is
removed entirely. Unlike the global slurm.options, an override’s options is
read normally, because pipelineOverrides is not filtered by source.
SeqDesk uses Conda-based pipeline execution. Container profiles such as
docker, singularity, apptainer, and podman are rejected when a run
starts.
pipelines.mag — MAG pipeline
Install-time input, applied by a hosted install profile into the
PipelineConfig row for mag. On a running install, edit these under
Settings → Pipelines, or per run when you launch MAG.
| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable nf-core/mag |
version | string | "3.4.0" | Pipeline version |
stubMode | boolean | false | Test mode (no real analysis) |
skipProkka | boolean | true | Skip Prokka annotation |
skipConcoct | boolean | true | Skip CONCOCT binning |
ena — ENA submission
Install-time input. ENA submissions read SiteSettings.enaUsername,
SiteSettings.enaPassword, and SiteSettings.enaTestMode from the database, not
this section. Setting ena.username here on a running install will show up in
/api/admin/config/status but will not be used by a submission.
| Key | Type | Default | Storage | Description |
|---|---|---|---|---|
testMode | boolean | true | SiteSettings.enaTestMode | Use the ENA test server |
username | string | — | SiteSettings.enaUsername | Webin username |
password | string | — | SiteSettings.enaPassword | Webin password (encrypted at rest) |
brokerAccount | boolean | false | SiteSettings.extraSettings.ena.brokerAccount | Webin account has ENA broker permissions |
centerName | string | "" | SiteSettings.extraSettings.ena.centerName | Submission center name |
Do not store ENA credentials in the config file. Enter them in
Settings → Data Upload (/admin/ena), where the
password is encrypted before it is written to the database. See
ENA Credentials.
sequencingFiles — File discovery
Install-time input. File discovery reads
SiteSettings.extraSettings.sequencingFiles; this section seeds it.
| Key | Type | Default | Description |
|---|---|---|---|
extensions | string[] | [".fastq.gz", ".fq.gz", ".fastq", ".fq"] | Allowed file extensions |
allowedExtensions | string[] | — | Alias for extensions, accepted by hosted install profiles; this is the key name used in the database |
scanDepth | integer | 2 | Directory scan depth (1–10) |
ignorePatterns | string[] | ["**/tmp/**", "**/undetermined/**"] | Glob patterns to skip |
autoAssign | boolean | false | Automatically link discovered files to samples when names match |
activeWriteMinAgeMs | integer | 30000 | Minimum file age in milliseconds before a file is considered stable rather than still being written |
simulationMode | "auto" | "synthetic" | "template" | "auto" | Read-simulation mode (auto uses templates if available) |
simulationTemplateDir | string | "" | Directory of realistic FASTQ pairs for template-based simulation |
allowSingleEnd | boolean | forced true | Accepted for compatibility and ignored — single-end reads are always allowed |
See File Discovery for what these settings do to a scan.
access — Researcher access policy
Install-time input. The running application reads these from
SiteSettings.extraSettings (and SiteSettings.postSubmissionInstructions).
| Key | Type | Default | Edited in the UI at |
|---|---|---|---|
departmentSharing | boolean | false | Settings → Accounts → Access & Sharing (“Department Sharing”) |
allowDeleteSubmittedOrders | boolean | false | Settings → Sequencing Order Form (“Allow Deletion of Submitted Sequencing Orders”) |
allowUserAssemblyDownload | boolean | false | Settings → Accounts → Access & Sharing (“User Assembly Downloads”) |
orderNotesEnabled | boolean | true | Settings → Info (“Sequencing Order notes”) |
postSubmissionInstructions | string | — | Settings → Sequencing Order Form (“Post-Submission Instructions”) |
departmentSharing is the one to think hardest about: it lets every researcher
in a department view and edit each other’s sequencing orders. Turn it on for a
small shared lab, not for a facility serving unrelated groups.
allowDeleteSubmittedOrders is useful while testing and dangerous in
production — it lets facility admins delete sequencing orders after submission.
auth — Authentication
Install-time input, read at runtime from
SiteSettings.extraSettings.auth.allowRegistration.
| Key | Type | Default | Description |
|---|---|---|---|
allowRegistration | boolean | true | Public registration enabled |
The schema also accepts auth.requireEmailVerification and auth.sessionTimeout
so older files still parse, but the authentication runtime implements neither.
Do not use them to express a security requirement — there is currently no
supported way to enforce email verification or a session lifetime. If you need
to restrict who may register, enable the Account Validation module and set
moduleSettings.account-validation.allowedDomains instead.
moduleSettings — Optional module settings
Install-time input, keyed by module ID. Only modules that are enabled under Settings → Modules consult their settings.
moduleSettings.account-validation
Stored as SiteSettings.extraSettings.accountValidationSettings.
| Key | Type | Default | Description |
|---|---|---|---|
allowedDomains | string[] | [] | Email domains accepted for automatic account validation |
enforceValidation | boolean | true | Block registrations from other domains |
An empty allowedDomains with enforceValidation: true is the trap here — set
the domains at the same time you enable the module.
moduleSettings.billing-info
Stored as SiteSettings.extraSettings.billingSettings.
| Key | Type | Default | Description |
|---|---|---|---|
pspEnabled | boolean | true | Enable PSP (project) number capture |
pspPrefixRange | object | { "min": 1, "max": 9 } | Allowed range for the PSP prefix |
pspMainDigits | integer | 7 | Number of main PSP digits |
pspSuffixRange | object | { "min": 1, "max": 99 } | Allowed range for the PSP suffix |
pspExample | string | "1-1234567-99" | Example PSP shown in the form |
costCenterEnabled | boolean | true | Enable cost-center capture |
costCenterPattern | string | — | Regex the cost center must match |
costCenterExample | string | "12345678" | Example cost center shown in the form |
telemetry — Operational telemetry
Read at runtime, with inverted precedence. A value stored in
SiteSettings.extraSettings.telemetry wins over this section, so a facility
admin can always turn the heartbeat off from the UI.
| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Opt-in anonymous heartbeat reporting |
endpoint | string | https://seqdesk.org/api/telemetry/heartbeat | Heartbeat URL |
intervalHours | integer | 24 | Minimum hours between automatic heartbeats |
SEQDESK_TELEMETRY_DISABLED=true overrides all of the above. See
Operational Telemetry for what is
reported.
notifications — Notifications
Read at runtime. Controls the in-app notification panel and the optional hosted email relay.
| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Master switch for the hosted SeqDesk email notification relay |
inApp.enabled | boolean | true | Master switch for the in-app notification panel/channel |
provider | "seqdesk-relay" | "seqdesk-relay" | Notification provider (only the hosted relay is supported) |
relayUrl | string | https://seqdesk.org/api/notifications/relay | Hosted relay endpoint |
relayToken | string | — | Scoped notification relay token — set via env, never committed |
events | object | (all on) | Which events trigger notifications |
userDefaults | object | { "orders": true, "support": true } | Default per-user opt-in for the orders and support channels |
notifications.enabled: true is necessary but not sufficient. Email
notifications are only dispatched when the setting is on, the provider is
seqdesk-relay, and the Email
Notifications module is enabled under
Settings → Modules. A relay token is additionally required
before anything is actually delivered.
notifications.events — Event toggles
| Key | Type | Default | Description |
|---|---|---|---|
order.submitted | boolean | true | Notify when a sequencing order is submitted |
order.statusChanged | boolean | true | Notify when a sequencing order status changes |
order.samplesSent | boolean | true | Notify when samples are marked sent |
ticket.created | boolean | true | Notify when a support ticket is created |
ticket.reply | boolean | true | Notify on a ticket reply |
The relay token is a secret. Set relayToken through
SEQDESK_NOTIFICATION_RELAY_TOKEN — never commit it to a config
file. It is masked in /api/admin/config/status responses, and the
Admin UI refuses to persist it to the database.
runtime — Process environment
Applied to process.env at startup, and only for variables that are not
already set. An environment variable of the same name always wins, and every
value here needs a restart to take effect.
| Key | Maps to | Description |
|---|---|---|
databaseUrl | DATABASE_URL | PostgreSQL runtime connection string |
directUrl | DIRECT_URL | Direct PostgreSQL connection for Prisma migrations |
nextAuthUrl | NEXTAUTH_URL | NextAuth.js callback URL |
nextAuthSecret | NEXTAUTH_SECRET | Session signing secret, and the default key material for encrypting stored secrets |
anthropicApiKey | ANTHROPIC_API_KEY | API key for the optional field-validation routes |
adminSecret | ADMIN_SECRET | Release publishing admin secret used by scripts |
blobReadWriteToken | BLOB_READ_WRITE_TOKEN | Vercel Blob token for release publishing scripts |
updateServer | SEQDESK_UPDATE_SERVER | Update server URL |
DIRECT_URL is derived rather than merely copied: if DATABASE_URL is supplied
by the environment but DIRECT_URL is not, the environment’s DATABASE_URL is
used for migrations too, so a runtime override cannot silently leave migrations
pointing at a different database.
Changing nextAuthSecret on an existing install invalidates every session and,
unless SEQDESK_ENCRYPTION_KEY is set separately, makes previously stored
encrypted secrets (such as the ENA password) undecryptable. Set
SEQDESK_ENCRYPTION_KEY if you need to rotate the auth secret independently.
Sections written by the installer
Two sections are produced by the installer rather than hand-written, and they are
not part of the runtime configuration schema the app reads through
/api/admin/config/status:
| Section | Written by | Purpose |
|---|---|---|
installProfile | --profile installs | Hosted install-profile id, name, version, and appliedAt stamp |
bootstrap.users | --interactive and unattended account settings | The admin/researcher accounts the seed creates on first run; bootstrap.users.researcher: false skips the researcher account |
Leave both alone unless you are scripting installs. installProfile.id is also
sent with notification-relay requests, so editing it by hand can break relay
authorisation.
Validation
The loader is deliberately forgiving: a file that is not valid JSON is skipped
with a console warning, and individual out-of-range values are not rejected at
load time. Nothing stops you from writing scanDepth: 400.
A separate validateConfig() helper — used by tooling and the install path
rather than by the loader — checks:
site.dataBasePathmust be a stringpipelines.execution.modemust be one oflocal,slurm,kubernetes(kubernetesis accepted only so older files still parse; there is no Kubernetes execution backend — uselocalorslurm)ena.testModemust be a booleansequencingFiles.scanDepthmust be a number between 1 and 10telemetry.enabledmust be a boolean,telemetry.endpointan http/https URL, andtelemetry.intervalHoursa number between 1 and 168notifications.enabledandnotifications.inApp.enabledmust be booleans,notifications.providermust beseqdesk-relay, andnotifications.relayUrlmust be an http/https URL
Related
- Configuration Sources & Priority — why a key in this file may lose to an environment variable or be ignored entirely
- Environment Variables — the
SEQDESK_*equivalents of the keys above - Runtime Settings — the admin screens that own the install-time sections after installation
- settings.json at install time — writing the first file and applying it unattended