What is the best way to pass parameters to documents in the web app (like sys.inputs in the CLI)?

If I have a typst file, I can pass arguments using --input and then access them using the sys.inputs dictionary when compiling at the command line.

For example, with demo.typ:

#let user_name = sys.inputs.at("name", default: "Guest")
Hello #user_name

I can call

typst compile demo.typ --input name=John

I’d ideally like to configure the sys.inputs dictionary within the web app to compile a particular version of the document, but I could not see how to do it.

So, what is the best way to handle parameterised documents like this in the web app? (Assuming there isn’t some project settings option somewhere that I have missed?)

The first method that comes to mind is to use a Typst file that contains a dictionary (or any other definitions):
inputs.typ

#let inputs = (
  "name": "Edward S. Hands"
)

main.typ

#import "inputs.typ": inputs

#let user-name = inputs.at("name", default: "Guest")

Hello #user-name

This way you could either change the values in input.typ to change your document, or you could have multiple input files that your main document can point to.

1 Like

Thanks for this idea. Is there a way to make it so the same .typ file would work in the web app or in the command line? (e.g. something that conditionally chooses either sys.inputs if used on CLI, or the content from inputs.typ?)

Not that I imagine people use both for the same documents, but I like the idea of the underlying source file being compilable both in the web or locally.

Easily, even. The most direct would be this:

#let inputs = (
  name: sys.inputs.at("name", default: "Guest"),
)

Personally, I think this here is also a great option:

#let inputs = (
  name: "Guest",
  ..sys.inputs,
)

When you spread a dictionary into another, the later keys (i.e. the ones from sys.inputs) will override the earlier ones (i.e. default values). This way, the inputs dict will contain extra keys if they were provided via the CLI, while the previous option would only have the "name". As long as you only access known keys, you won’t notice the difference.

You can also more clearly separate the defaults and the CLI overrides like this:

#let defaults = (
  name: "Guest",
)
#let inputs = for (key, default) in defaults {
  ((key): sys.inputs.at(key, default: default))
}

That code is a bit involved: is generates dicts where the key is not a literal "key" (which is what happens when the key is an identifier), but instead with the string contained in key (which is what happens when the key is a different expression, such as a parenthesized one). It then uses a for loop to join all those individual dicts, creating a single dictionary with all the values.

Of course, this can be rewritten as a function, too:

#let sys-inputs(..defaults) = {
  assert.eq(defaults.pos(), (), message: "only named arguments allowed")
  let defaults = defaults.named()
  for (key, default) in defaults {
    ((key): sys.inputs.at(key, default: default))
  }
}

#let inputs = sys-inputs(
  name: "Guest",
)
3 Likes