Templater User Scripts Obsidian
promptThis 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:
tpThe Templater object passed from the context of a template.
configThe 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: