Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Learn Jadpo: all 14 pages

Settings

Page 9 of 14 in Learn Jadpo

By the end you can read a limit from the environment, and keep secrets safe.

On this page

Some values differ between your machine and the server the app runs on: the site's address, a mail server's password, a limit you want to tune without a release. On this page you add the app's first setting, how far ahead a todo may be due, read from an environment variable. Page 11 then adds the settings sign-in needs.

Declare it once

Every setting is a field of the one config { } block, in config.jadpo next to app.jadpo. jadpo new wrote it with no fields; replace the whole file:

config.jadpo:

jadpo
config {    /// How far ahead a todo may be due.    max_due_ahead: Duration {        default: 365d        test: 30d    }}
  • The field's name gives its environment variable, in capitals: max_due_ahead reads MAX_DUE_AHEAD.
  • Its type checks the value before the app starts. Duration is a standard type for a length of time, written like 365d, 12h or 30m.
  • default: is the value when the variable isn't set. A setting with no default is required, and the app won't start without it.
  • test: is the value tests use, whatever the environment says, so tests don't change from one machine to the next.

Code reads it as config.max_due_ahead, anywhere.

Use it

A due date too far ahead is a new failure:

domains/todos/failures/due_date_too_far.jadpo:

jadpo
/// A todo can't be due further ahead than the app allows.type DueDateTooFar = Failure {    kind: FailureKind.invalid_value    message: "That due date is too far ahead."}

and add_todo checks it beside the first one:

domains/todos/actions/add_todo.jadpo, changed. DueDateTooFar is new in the use line, the fails list and a second if.

jadpo
use todos/entities { Todo }use todos/types { NewTodo, TodoStatus, TodoView }use todos/failures { DueDateInPast, DueDateTooFar }/// Adds an open todo, refusing a due date that has passed or is too far/// ahead.action add_todo(input: NewTodo) fails DueDateInPast, DueDateTooFar -> TodoView {    match input.due_at {        some(due) -> {            if (due <= clock.now) {                reject DueDateInPast            }            if (due > clock.now + config.max_due_ahead) {                reject DueDateTooFar            }        }        none -> {}    }    var todo: Todo = Todo.create {        set: {            title: input.title            status: TodoStatus.open            due_at: input.due_at        }    }    return TodoView {        id: todo.id        title: todo.title        status: todo.status        due_at: todo.due_at        created_at: todo.created_at    }}

clock.now + config.max_due_ahead adds a length of time to a moment, which gives a moment. Jadpo knows which sums make sense: adding two moments is an error.

Add a test for it to add_todo.test.jadpo, the last one here. Its due date is 44 days ahead, past the test: value of 30 days:

domains/todos/actions/add_todo.test.jadpo, changed. The last test is new, and so is DueDateTooFar in the use line.

jadpo
use todos/types { NewTodo, TodoStatus, TodoView }use todos/failures { DueDateInPast, DueDateTooFar }test "a new todo is open" {    var todo: TodoView = attempt add_todo(        NewTodo {            title: "Buy milk"        }    )    assert todo.status == TodoStatus.open    assert todo.due_at == none}test "a due date that has passed is refused" {    clock: "2026-11-01T12:00:00Z"    assert add_todo(        NewTodo {            title: "Buy milk"            due_at: "2026-11-01T11:59:59Z"        }    ) fails DueDateInPast}test "a due date further ahead than the setting allows is refused" {    clock: "2026-11-01T12:00:00Z"    assert add_todo(        NewTodo {            title: "Renew passport"            due_at: "2026-12-15T12:00:00Z"        }    ) fails DueDateTooFar}

Update the record for the new failure. Then:

shell
jadpo checkjadpo test
text
checked: 20 files in 0.1 stested: 6 passed, 3 action tests, 3 route tests in 0.9 s

Set it

jadpo config check lists every setting, whether it is set, and the variable it reads. It never prints a value:

text
Values  max_due_ahead  default  MAX_DUE_AHEADconfig ready: 1 setting

The columns are the setting, where its value comes from (default here; set when the environment or .env.local gives one; missing when a required one has none), and the variable it reads.

Run jadpo dev, and add a todo due in 2099. With the default limit of a year, it's too far ahead:

shell
curl -X POST http://localhost:3000/todos \  -H 'content-type: application/json' \  -d '{"title": "Plant a tree", "due_at": "2099-11-02T09:00:00Z"}'
text
{"error":{"code":"due_date_too_far","message":"That due date is too far ahead.","request_id":"req_be315e671ffe4dd5"}}

Stop jadpo dev, and start it again with the limit set to a hundred years in the variable:

shell
MAX_DUE_AHEAD=36500d jadpo dev
text
jadpo dev, started 21:54:29listening on http://localhost:3000    routes    6 routes    settings  1 checked    database  disposable PostgreSQL, gone when dev stopsserving: saves reload, press ctrl-c to quit

The same request now succeeds:

text
{"id":"01a189a8-4c52-763f-b6d9-37adabfdd64c","title":"Plant a tree","status":"open","due_at":"2099-11-02T09:00:00Z","created_at":"2026-10-29T21:54:30.610Z"}

A value that doesn't fit the type stops the app before it serves anything, naming the setting but never the value:

shell
MAX_DUE_AHEAD=soon jadpo dev
text
jadpo dev, started 21:31:18error: `MAX_DUE_AHEAD` isn't a valid value for `max_due_ahead`  reason: `max_due_ahead` of type `Duration` reads `MAX_DUE_AHEAD`, and the value there breaks `format`. The value isn't shown, so it can't leak into a log.  next: Run `jadpo config set max_due_ahead` to store a valid one, or fix `MAX_DUE_AHEAD` in the environment (human)  rule: configuration.value_invalid  help: https://jadpo.dev/docs/errors/configuration.value_invalidnot running this version: 1 error, waiting for a save that fixes them

Values on your machine

To keep a value on your machine without typing it each time, put it in .env.local in the project's folder, one NAME=value a line:

.env.local:

text
MAX_DUE_AHEAD=7d

jadpo dev and jadpo config check read it, and a variable set in the environment wins over it. Keep .env.local out of git: add it to .gitignore beside build/. On a server, you set the variables in its environment instead, as page 13 does.

Secrets

A password or a key is a setting marked secret: true. Your code can never read a secret. It can only be handed to a connection to another service, such as the mail server page 11 sets up, which uses it to sign in there. So no response, log or error of your app can ever contain one.

Never type a secret into a chat with an agent, or into a command where your shell's history keeps it. When you need one on your machine, jadpo config set <setting> asks for it at a prompt that hides what you type, checks it against the setting's type, and writes it to .env.local.

What you have

A setting with a default, a test value and a variable to change it, and a check that refuses a bad value before the app starts. Next, on page 10, todos get owners.

Read more in the reference: Settings, Secrets and Values on your machine and in production.

Markdown for agents: /learn/learn-settings.md.Built from Jadpo main at a707efb. Every file on this page is compiled and tested by Jadpo's CI, on the app as the pages before it built it.