Templater
User Scripts Obsidian
prompt
This script, prompts the user for the relevant template variables based on a
configuration object. It also handles incorrect template configuration,
cancellation events, and other errors. The script is intended to be used as a
user script for the Templater
plugin.
The following provides an overview of the topics covered on this page:
tp.user.prompt(tp, config)
The following arguments, in the presented order, are required:
tp
The Templater
object passed from the context of a template.
config
The configuration object that holds relevant template variables. Each template variable holds a single configuration object, that is composed of an arbitrary number of configuration elements objects. In turn, each configuration element can have some of the following properties:
{
prompt: boolean,
display: string,
value: string | string[] | (tp, config) => string | (tp, config) => string[],
multiple: boolean,
limit: number,
text: string[] | (item: any) => string,
check: (value: string, tp: Templater, config: object) => boolean,
process: (value: string, tp: Templater, config: object) => string
}
Regardless of how Templater
is invoked, the prompt
and value
are required
configuration element properties. If the prompt
value is set to true
, the
display
property is also required. All other properties mentioned above are
optional. For example, a configuration object with the elements path
and
filename
might look like this:
// The configuration object.
let config = {
// The `path` element indicating where to create the note.
path: {
prompt: true,
display: "Please indicate the path.",
value: "Folder"
},
// The `filename` element indicating how to name the note.
filename: {
prompt: true,
display: "Please indicate the file name.",
value: "A new note"
}
}
The three properties presented below represent required configuration element properties.
prompt
value
property of the
configuration element. If true
, the user will be prompted, and the prompt
modal will be populated with the current contents of the value
property.
Upon submission, the value
will be updated with the prompt input. If
false
, the value
of the property will be used as declared in the template.
The type of prompt shown depends on the type of the value
property:
value
is set to a string, the tp.system.prompt
will be used for
the prompt (i.e., a single-line input, or textarea when the multiple
property is set to true
).value
is set to an array of elements, the tp.system.suggester
will be used for the prompt (i.e., selection from a drop-down menu).display
value
property. This property is required if prompt
is set to true
.value
value
property determines the type of prompt used
and can be either a string or an array of elements.value
to a function
that returns a string or an array of elements. If set to a function, the
function will be evaluated immediately, right before the prompt, if a prompt
is requested. The function can be asynchronous and must be declared with two
arguments, tp
and config
. The tp
argument represents the Templater
object, and the config
argument represents the configuration object. The
config
argument is useful for accessing other configuration elements and its
state depends on the order of the configuration elements.{{
and }}
delimiters. For
example, if the value
of the filename
element is set to {{ path }}
, the
value
of the filename
element will be set to the value
of the path
element. The script adjusts the prompting order to ensure all references are
correctly resolved. Also, multiple references are supported (e.g., Composite
value from {{ filename }} and some {{ date }} element
).Aside from the prompt
, display
, and value
properties above, the following
optional properties can also be used.
multiple
value
property is set to a string to indicate whether the user
should be allowed to enter a multi-line input in the prompt modal (i.e., a
text area). If true
, multi-line input is allowed. If false
, only
single-line input is allowed. This property is ignored when the value
is set
to an array. Defaults to false
.limit
value
property is set to an array to indicate the maximum
number of suggestions that should be displayed by the tp.system.suggester
.
If this property is not set, the default behavior of the
tp.system.suggester
will be used.text
value
property is set to an array to indicate the
corresponding text that should be displayed in the user interface for each
suggestion (i.e., array element in value
). This property can be set to an
array of strings or a function.
i
in text
will be displayed for element i
in
value
).value
. The function should return a string. See the examples on the
tp.system.suggester
documentation page for more information.check
value
property is valid according to some
custom criteria.value
,
tp
, and config
. The value
argument represents the value
of the
configuration element. The tp
argument represents the Templater
object.
The config
argument represents the configuration object. The tp
and
config
arguments are useful, e.g., for accessing other configuration
elements and their value
properties at the time of the check. The function
must return a boolean, which gets interpreted as follows:
true
, the value
property is considered valid.false
, the value
property is considered invalid
and an error is thrown.check
property is used in conjunction with prompt: true
, the check
is performed after the prompt modal is submitted. As a rule, the check if
always performed last, even if the configuration element has a process
property for post-processing (i.e., see below).process
value
property.value
,
tp
, and config
. The value
argument represents the value
of the
configuration element. The tp
argument represents the Templater
object.
The config
argument represents the configuration object. The tp
and
config
arguments are useful for accessing other configuration elements and
at the time of the processing.value
of
the configuration element. If the property prompt
is set to true
, then new
prompt modal will be shown with the updated value
allowing the user to
further modify the value
. If this behavior is not desired, you can set the
prompt
property to false
in the process
function, which will prevent the
prompt modal from being shown.value
property). See the Examples section for more
information.As a rule, the following order is followed when evaluating the configuration elements:
value
property is set to a function, the function is evaluated first.prompt: true
), the prompt is shown and
the user can enter a new value
.process
property is set, the value
is processed.check
property is set, the value
is checked.The prompt
function call returns undefined
. The configuration
object is passed by reference and modified in-place.
In this section, we succinctly review some of the capabilities of the prompt
user script in the context of another user script called
makeNoteWithPrompting
, which uses prompt
behind
the scenes to create a note file, or insert a template into an existing note.
Please refer back to the Usage section for more information on the
configuration elements and their properties. We will gradually build up towards
a fully-fledged fictive template for creating questionably helpful movie notes.
Suppose we have a movie template creatively called Movie Template
with the
following content:
<%-*
// The configuration object containing several elements.
let config = {
// The movie title.
title: {
prompt: true,
display: "What is the tile of the movie?",
value: "The Movie Title"
},
// The genre of the movie.
genre: {
prompt: true,
display: "What is the movie genre?",
value: ["comedy", "thriller", "other"]
},
// Whether the movie is re-watchable.
rewatch: {
prompt: true,
display: "Would I rewatch this movie?",
value: ["yes", "no"],
text: ["Without a doubt, yes!", "I mean... it was okay, but..."]
},
// A general summary of the movie.
summary: {
prompt: true,
display: "What is the summary of the movie?",
value: "Summary...",
multiline: true
}
}
// Create the note.
await tp.user.makeNoteWithPrompting(tp, config)
_%>
# 🎥 <% config.title.value %>
## Description
- Genre: #<% config.genre.value %>
- Re-watchable: <% config.rewatch.value %>
## Summary
<% config.summary.value %>
Running the Templater: Open insert Template modal
command from within an open
note and selecting the Movie Template
template will prompt the user for all
the config
object elements that have prompt
set to true
. Then, depending
on the input provided, the following will be inserted in the open note:
# 🎥 A cool movie title
## Description
- Genre: #other
- Re-watchable: no
## Summary
Line one
Line two
Continuing with the template above, suppose we want to be able to use the sample
template to also create new notes from it. We can easily adjust our
configuration object to support this use case by specifying the path
and
filename
properties. After this change, our configuration object might look
like this:
// The configuration object containing several elements.
let config = {
// The note location.
path: {
prompt: true,
display: "Where to store the movie note?",
value: "Movies"
},
// The note file name.
filename: {
prompt: true,
display: "How to name the movie note?",
value: "New Movie"
},
// The movie title.
title: {
prompt: true,
display: "What is the tile of the movie?",
value: "The Movie Title"
},
// The genre of the movie.
genre: {
prompt: true,
display: "What is the movie genre?",
value: ["comedy", "thriller", "other"]
},
// Whether the movie is re-watchable.
rewatch: {
prompt: true,
display: "Would I rewatch this movie?",
value: ["yes", "no"],
text: ["Without a doubt, yes!", "I mean... it was okay, but..."]
},
// A general summary of the movie.
summary: {
prompt: true,
display: "What is the summary of the movie?",
value: "Summary...",
multiline: true
}
}
Note. The rest of the template remains unchanged, we only adjusted the
config
object by adding the path
and filename
elements.
Running the Templater: Create new note from template
command after the
adjustments, we will also be prompted for the path
and filename
elements.
Based on the input provided for these two elements, a new note will be created
with the specified name, in the location indicated. The contents of the new note
will be the same as the one shown in the previous example.
Let’s continue building on the template from the previous example and see how we
can use referencing to make our template more dynamic. Suppose we want the
title of the movie to set as the file name of the note. We can easily do this by
using the {{ ... }}
syntax to reference the title
element in the filename
.
To achieve this, we need to change our config
object as follows:
// The configuration object containing several elements.
let config = {
// ...
// The note file name.
filename: {
prompt: true,
display: "How to name the movie note?",
value: "{{ title }}"
},
// The movie title.
title: {
prompt: true,
display: "What is the tile of the movie?",
value: "The Movie Title"
},
// ...
}
Invoking Templater
after this change will result in setting the value of
filename
to whatever the value of title
is set to. Since our example also
uses prompting, the prompt modal for filename
will be populated with the value
of title
as the default value.
Note. The title
element appears after filename
in the config
object.
This is not issue because the the script will first handle elements that do not
relay on referencing, then, it will sort the elements that use referencing and
handle them in the correct order.
Building on the example above, let’s continue by adding a validation check to
ensure that that the thriller
option is not a valid choice of movie genre. We
can easily accomplish this by adding a check
property to the genre
configuration element.
The check
property must be a function that takes the value
property as input
and returns true
if the value
is considered valid, and false
otherwise. In
our example, the check
property might look like this:
// The configuration object containing several elements.
let config = {
// ...
// The genre of the movie.
genre: {
prompt: true,
display: "What is the movie genre?",
value: ["comedy", "thriller", "other"],
check: (value) => value === "thriller" ? false : true
},
// ...
}
Now, running Templater
and selecting thriller
as the genre will result in an
error message saying that the value is not valid, and the script will stop
executing.
Note. The check
property can also specify a function with the tp
and
config
parameters should more complex validation be required, e.g.:
// ...
check: (value, tp, config) => {
// Do something with the `config` or `tp` objects here.
// Return `true` or `false` based on the validation.
}
// ...
Finally, let’s see how to use value processing to modify the value
property
of a config element and re-prompt the user with the modified value
. To achieve
this, we need to add a process
property. The process
property must be a
function that takes the value
property as input and returns a string
representing the modified value
.
Suppose we want the summary
element to reference the movie title, which in
turn will be processed by a custom function to obtain the movie summary from an
external API. In a nutshell, the following will happen:
summary
configuration element will be set to the value of the title
config element.summary
element will be processed using a custom function
and will be updated with the actual summary of the movie.Our custom processing function is called getMovieSummary
and takes as input
the title of a movie and returns its summary, e.g., using the OpenAI
API, as
seen below.
// Get a movie summary based on its title.
async function getMovieSummary(prompt, systemPrompt = "Your role is to identify a movie by its title and provide a succinct summary.") {
// The OpenAI API endpoint.
const openaiURL = "https://api.openai.com/v1/chat/completions";
// The OpenAI API request headers.
const headers = {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`
};
// The OpenAI API request body.
const body = JSON.stringify({
model: "gpt-3.5-turbo",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: prompt }
]
});
// Send the request to the OpenAI API.
const response = await fetch(openaiURL, {
method: "POST",
headers: headers,
body: body
});
// Parse the response object.
const data = await response.json();
// Get the assistant response.
const assistantResponse = data.choices[0].message.content;
// Return the response.
return assistantResponse;
}
Note. The function getMovieSummary
assumes that your OpenAI
API key is
stored in the OPENAI_API_KEY
environment variable and is available system-wide
(e.g., via launchctl setenv
for macOS
). If you do not know how to achieve
this, just replace ${process.env.OPENAI_API_KEY}
in the function with your
actual API key.
Now that we have our custom processing function, we can place it at the top of
our template file and then add it to the process
property on the summary
configuration element. The config
object might look like this:
// The configuration object containing several elements.
let config = {
// ...
// A general summary of the movie.
summary: {
prompt: true,
display: "What is the summary of the movie?",
value: "{{ title }}",
multiline: true,
process: getMovieSummary
}
// ...
}
Note. It might take a few seconds for the API call to complete. Once the
summary is available, the user will be prompted again with the chance to edit
it. If you would like to avoid the confirmation prompt, you can leverage the
additional arguments, by updating the process
property to the following:
// ...
process: async (value, tp, config) => {
// Disable the confirmation prompt.
config.summary.prompt = false
// Process the processed value (i.e., the summary).
return await getMovieSummary(value)
}
// ...
It is advisable that you wrap the makeNoteWithPrompting
script in a try ...
catch
block to gracefully handle errors. For example, you may consider
replacing the lines
// Create the note.
await tp.user.makeNoteWithPrompting(tp, config)
with
// Proceed with the note creation gracefully.
try {
// Create the note or insert the template.
await tp.user.makeNoteWithPrompting(tp, config)
// Let the user know if the operation succeeded.
new Notice(`Created note '${config.filename.value}'.`)
} catch(error) {
// Inform the user.
new Notice(error.message)
// Stop the execution.
return
}
This will allow errors (e.g., failures if the note already exists, prompt
cancellations, failed checks etc.) to be caught and displayed in the
Obsidian
interface as gentle notices.
Let’s put everything together and see how the complete template looks like:
<%-*
// Get a movie summary based on its title.
async function getMovieSummary(prompt, systemPrompt = "Your role is to identify a movie by its title and provide a succinct summary.") {
// The OpenAI API endpoint.
const openAiURL = "https://api.openai.com/v1/chat/completions";
// The OpenAI API request headers.
const headers = {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`
};
// The OpenAI API request body.
const body = JSON.stringify({
model: "gpt-3.5-turbo",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: prompt }
]
});
// Send the request to the OpenAI API.
const response = await fetch(openAiURL, {
method: "POST",
headers: headers,
body: body
});
// Parse the response object.
const data = await response.json();
// Get the assistant response.
const assistantResponse = data.choices[0].message.content;
// Return the response.
return assistantResponse;
}
// The configuration object containing several elements.
let config = {
// The note location.
path: {
prompt: true,
display: "Where to store the movie note?",
value: "Movies"
},
// The note file name.
filename: {
prompt: true,
display: "How to name the movie note?",
value: "{{ title }}"
},
// The movie title.
title: {
prompt: true,
display: "What is the tile of the movie?",
value: "The Movie Title"
},
// The genre of the movie.
genre: {
prompt: true,
display: "What is the movie genre?",
value: ["comedy", "thriller", "other"],
check: (value) => value === "thriller" ? false : true
},
// Whether the movie is re-watchable.
rewatch: {
prompt: true,
display: "Would I rewatch this movie?",
value: ["yes", "no"],
text: ["Without a doubt, yes!", "I mean... it was okay, but..."]
},
// A general summary of the movie.
summary: {
prompt: true,
display: "What is the summary of the movie?",
value: "{{ title }}",
multiline: true,
process: getMovieSummary
}
}
// Proceed with the note creation gracefully.
try {
// Create the note or insert the template.
await tp.user.makeNoteWithPrompting(tp, config)
// Let the user know if the operation succeeded.
new Notice(`Created note '${config.filename.value}'.`)
} catch(error) {
// Inform the user.
new Notice(error.message)
// Stop the execution.
return
}
_%>
# 🎥 <% config.title.value %>
## Description
- Genre: #<% config.genre.value %>
- Re-watchable: <% config.rewatch.value %>
## Summary
<% config.summary.value %>
The following video shows the template above in action: