Issue with Templater API, calling tp.file (I think), but not tp.file.tags

Hello. I’m having some issues with the Templater API.

My vault is a repository of mathematics notes. My goal is to create new notes based on templates (Axiom, Concept, Corollary, Definition, etc.). Every template (except for two: Subject and Reference) will have the same required properties: tags, id, subject, source. Subject requires just a tag and id; and Reference requires information for citation (title, author, publisher, etc.). The tag and id of a note are automated; tag is just the name of the template; id is the time in YYYYMMDDHHMMSS format, which creates a unique id and also allows me to reference the chronology of the notes. Currently, each template has the tag (albeit, in a slightly different format) entered into the template file properties. These are also the only tags in my vault (they are of the form #AXM, etc., also, I’ll probably update them to #axiom, etc. for simplicity). The property values are mostly text, with a few being lists or outright numbers (due to the Templates core plugin not playing well with the properties, the id field is registered as text because it originally called {{date}}{{time}} and Obsidian evaluates that as text, not a number.

I have very little experience with JavaScript so I asked for help from my friendly neighborhood artificial intelligence to get me started. Because this project is broad and I think the issues I’m having (hopefully) are just syntactical, I’m going to limit how much I add to the post here, with plans to add it to GitHub once the foundation is set. Currently “my” (I’m thinking a 60:40 split) code looks like this:

const fs = require('fs').promises;
const tp = app.plugins.plugins['templater-obsidian']; 

async function formatDateToId() {
...
return id
}

async function getSubject() { // This was being returned to console if I call it within the template file
    try {
        const absolutePath = '/home/df/Obsidian/math/Zettelkasten/Subjects/';
        const files = await fs.readdir(absolutePath);
        const subjects = files.map(file => file.replace('.md', ''));
        console.log(subjects);
        const chosenSubject = await tp.system.suggester(subjects, subjects);
        return chosenSubject;
    } catch (err) {
        console.error('Error reading directory:', err);
        return [];
    }
}

async function getSource() { // essentially the same as getSubject()
...
}

async function generateStandardFrontmatter() {
    const tag = getTag(tp); // for all references
    const id = formatDateToId(new Date());
    const subject = getSubject();
    const source = getSource();
    console.log('generateStandardFrontmatter: ', tag, id, subject, source)
    
    return `---
    tags: ${tag}
    id: "${id}"
    subject: ${subject}
    source: ${source}
    ---`;
}

async function generateReferenceFrontmatter() { // same as standard, except many more fields
...
}

async function getTag() {

    // is getTag required? if I call the template name, can't that be the tag? instead of #AXM I can just use #axiom, etc.

    // getTag informs frontmatter generator functions of the template to pull.
    // this is because there is only one tag on a newly created file.
    console.log('getTag starting')
    const tag = tp.file.tags();
    console.log('getTag: ', tag);
    return tag;
}

async function main() {
    console.log('getTag next'); 
    const tag = await getTag(); \\ this is where I get an error
    console.log('main: ', tag);
    console.log('formatDateToId next'); 
    const id = await formatDateToId(); \\ this is fine
    console.log('main: ', id);
    console.log('letting frontmatter')

main()

module.exports = { formatDateToId, ...}

Still here? Thanks.

For simplicity, let’s focus on getTag(), which is where I’m currently having an issue. Templater does not trigger automatically on file creation. What I have is this:

---
tags:
  - AXM
id:
subject:
source:
---
### Axiom

<% tp.user.main.main() %>

which when called with “Alt + Shift + Q => Templater: Create new note from template,” returns this in the console:

getTag next
getTag starting

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'tags')
    at getTag (eval at load_user_script_function (plugin:templater-obsidian:1:1),
<anonymous>:188:25)
    at main (eval at load_user_script_function (plugin:templater-obsidian:1:1),
<anonymous>:195:23)
    at anonymous (eval at load_user_script_function (plugin:templater-obsidian:1:1),
<anonymous>:221:1)
    at UserScriptFunctions.load_user_script_function (plugin:templater-obsidian:2949:7)
    at async UserScriptFunctions.generate_user_script_functions (plugin:templater-obsidian:2933:9)
    at async UserScriptFunctions.generate_object (plugin:templater-obsidian:2963:35)
    at async UserFunctions.generate_object (plugin:templater-obsidian:2982:31)
    at async FunctionsGenerator.generate_object (plugin:templater-obsidian:3023:33)
    at async Templater.parse_template (plugin:templater-obsidian:3505:30)
    at async errorWrapper (plugin:templater-obsidian:78:12)

So, my question is this: why is it that when I create
const tp = app.plugins.plugins['templater-obsidian'];
does the app not recognize tp.file.tags(); but it does recognize file?

app.plugins.plugins['templater-obsidian'] is the Templater plugin instance, and this is not the same as the tp variable that you use in scripts (like tp.user/tp.file/tp.date).
You usually don’t need this plugin instance.

Thus, in your script tp doesn’t have file property. In this case, tp.file becomes undefined without an error unlike Python, where a similar operation to a dict will cause KeyError.

tp.file.tags will be undefined.tags, and this is where an error occurs in JavaScript.

In order to access the tp variable, you need to pass it as a parameter to your function, as the documentation says:

You can for example pass the tp object to your function, to be able to use all of the internal variables / functions of Templater: <% tp.user.<user_function_name>(tp) %>

1 Like

Yeah, that just about did it. Thanks for the help.