Nested Templates

Hello everyone, don’t typically use forums for specific programs myself but I’ve been a big fan of Obsidian since I first discovered it a number of months ago. There’s a number of things I’ve thought of requesting but this one has stuck out the most from my end. Hopefully an explanation of where I’m coming from will help present exactly what I’m talking about.

Obsidian has been a great resource in referencing information regarding Youtube video projects that I put together as a hobby. A lot of these pages for these projects to help keep track of everything involve repeatedly the same information, such as project names in video editors or gathering lists of sources for information, that sort of thing. So far I’ve stuck with using headers for separating these categories within these notes, and it definitely works however it becomes cumbersome when you add an extensive to-do list on the same page and a lot of images.

The idea here is, to help organize these notes, is to have an extended note created within each template, that is attached to each new note created using that template. Note, I don’t mean to reference a note already existing when creating a new note, but creating a “note within a note”, generated by the template itself. For those familiar with Notion, this would be akin to having a template within Notion that has another page created within that page. I believe this would really streamline the process for organization structures like this, especially in instances where the user is constantly filling out similar information based around particular projects into a single place for reference, simply saving a lot of time creating new notes that are only going to be used in the one note.

Hopefully everyone gets what I mean. I tried searching around a bit to see if anyone else had discussed such a topic, but couldn’t find much luck. Also, if such a thing really does work in the current version I really couldn’t figure that out.

12 Likes

Interesting. Is this not possible with the current Template feature? I imagine you could create a template that includes the line:

![Some Note]

Also, this is related, I think: Note extractor

(PS: Welcome to the forum!)

1 Like

I see what you’re saying with the embedded note references, and while those are definitely good tools for referencing other material directly in another note that isn’t exactly what I was going after. Sorry for any confusion.

What I meant more specifically is templates including new notes generated along with that template, where the new notes generated also have some pre-determined lines to save time formatting and such. Maybe putting it like this will help.

  • [[Template A]]
    ⮩ [[Template B]]
    ⮩ [[Template C]]

Where [[Template A]] is the main, overall template, and within that template there is also new notes, in this case [[Template B]] and [[Template C]], both of which also have their own text generated. This would mean when creating a new note based off of [[Template A]], it would also generate [[Template B]] and [[Template C]], which I’d imagine would be referenced in [[Template A]].

Apologies for any confusion again, it seems to get difficult to explain these sorts of things simpler when we start going into “notes within notes” and the like haha.

Overall I just believe this sort of functionality would save time organizing new notes when structured in a similar way, save the process of copy-pasting other bits of text. I’d imagine to make this work it’d involve either ZK UIDs or some manner of variable-based note names that go along with the new notes.

Thanks for the warm welcome!

1 Like

Ah, interesting. Well, the current template feature can do this halfway right now, maybe… If your Template A looks like…

Some text
- [[Template B yyyyMMddhhss]]
- [[Template C yyyyMMddhhss]]

…where yyyyMMddhhss are timestamps using the Template’s syntaxes, those two notes would be created as orphans. They could then be manually filled with more templated content.

But yes, I get what you’re going for. Nested templates, essentially. A good candidate for a plugin, I imagine.

Alternatively, you could probably use automation/macro tools to create these nested templates using e.g., Keyboard Maestro (macOS) or AutoHotKey (Windows).

2 Likes

Cool feature request. I hadn’t thought of it, but now I need it.

I think that “Nested Templates”, as suggested by @ryanjamurphy, would be a better title for this. It made things a lot clearer for me, at least.

2 Likes

With my rather fuzzy concept of what’s being looked for, I would have expected text expander or macro being the way to go. And probably easier, once set up, than any native solution. I’d use PhraseExpress, but that’s only because I have a glimmer of how it works.

3 Likes

There’s some serious recursion and the danger of self-reference loops, but I think it could be done in Alfred, for example. There goes my spare time.

1 Like

Nested templates is a much more elegant name for what I’m talking about! I’m glad you were able to shorten what I meant.

On another note though, how would one go about using the syntaxes like you mentioned? I can’t seem to find exactly how that’s done this second. Would be very helpful.

I’ve considered trying to make some sort of macro to do the same job for the mean time but figured I’d throw it out here on the forums before attempting anything haha.

Sorry, I was writing out the above off the top of my head, so it might not work exactly as I described.

The templates documentation is here: https://publish.obsidian.md/help/Plugins/Templates

You should be able to use e.g., [[Note title {{date}} {{time}]] inside a template to generate those orphan notes unique to every new template.

1 Like

Yep, +1 for “Nested Templates” or “Modular Templates” or “Meta-Templates”

The application here is a parent/meta/main template that has child/module/section templates embedded into it upon creation.

First question might be, why not just construct the main template to include everything you want? A: Well, being able to reuse the module templates in other meta-templates allows for consistency for sections that repeat. And when changing a child template, it changes in every meta-template it appears under.

Q: why not embed a file with the ![File]? This is good for getting the same file to reappear, but not if you change that file. It would no longer be a template, just a single file that is always changing.

Q: why not link to a file with the [File]? See above.

Solution:
Just allow a template to contain a reference to another template with {{Child Template}} format.

Warm welcome to any alternate ideas or workarounds.

3 Likes

Definitely interested in this feature. As a developer by day, nested templates make sense to modularize my templating.

I want the ability to have re-usable ‘footer’ and ‘header’ sections across a vast majority of my templates and having a single place to modify those sections for future files makes a lot of sense.

With that being said thanks for such a great product, I’ve been deep diving on Obsidian a lot recently.

1 Like

I have encountered the same issue and so far I reached a dead end where I get a recursion error when there are more than 10 template stack calls.

I’m trying to find a creative way to solve this without making my own plugin, maybe using a bit of JS and run it with Templater, but I was wondering @n0ll if by now you’ve found a better solution, before I dive into it myself :slight_smile:

Thanks!

P.S
I’m sharing the exact use-case of the discussion above, making a “Base Meta” having “Base Sections” and dynamically populated fields per section (using the Buttons plugin to create some of a “Metadata Frontend” to set a default dynamic meta on the “new note” template and allow each specific note to “apply” its specific fields using these buttons, under a defined metadata structure template that automates setting most of this advanced metadata fields automatically (e.g via smart prompts to give a nice user experience)

Since I started the thread ages ago Templater added a feature to generate files after generating the a specific template, while using a specific template for the separately generated file.

I had essentially copied someone else’s example from an old blogpost roughly a year ago or so, I may be able to dig it up again but most likely lost the original.

[[<% (await tp.file.create_new(tp.file.find_tfile("TemplateB"), tp.file.title + " - Ideas", false)).basename %>|Ideas]]

With that specifically, it would generate a new file based on the original file’s title (let’s just call it host file for simplicity I guess). The nested file would share the host file’s title in addition to " - Ideas" to help prevent any conflicts while still generating a new file to add notes for future reference to.

After all said and done, within the host file it’s still shown as just a simple link with “Ideas” for simplicity.

It’s by no means a wonderful solution but it’s more or less what I looked to achieve at the time. Something a little more elegant and user-friendly would be much appreciated though. Also the exact syntax for that string I don’t totally remember right now to be honest.

1 Like

It’s a different approach I didn’t think of, thank you, I’ll explore this option.
Thanks!

As I said, I’ve been exploring this idea, and I came up with something. I’m pretty sure it’s nothing like what you referenced @n0ll , but you gave me a direction so thank you!

Disclaimer

I’ve never coded in javascript in my life, not to speak about scripting with Obsidian. The following code is my first attempt and is W.I.P and about 70-80% auto generated by GitHub CoPilot, with me typing what I want in English based on all of the different YouTube videos I’ve seen, and hoping for the best :pray:. So I have no clue if I’m using javascript best practices and I’m pretty sure this code is very “bad” so to speak, but I know one thing - it works! And even if it’s not that useful as is, it might be a good starting point for someone else like me :slight_smile:

Goal

My goal was to be able to modify sections of my frontmatter/metadata and have it all formatted automatically so that I could separate the sections into files and organize them all nicely.

End Results

  • Assuming a hard-coded frontmatter scheme (sections).
  • Assuming additional functions which I will not share (pointless).

Frontmatter :file_folder:

  • frontmatter.md:
---
alias:
- ""
---
  • header.md: Some default header to encapsulate everything.
  • buttons.md: Buttons I want to add to the frontmatter.
  • footer.md: Footer string, --- for example.

createFrontmatter.js

async function createFrontmatter(tp, include_frontmatter = true) {
    console.log("createFrontmatter: Generating metadata for \"" + tp.file.title + "\"")
    
    let frontmatter = await tp.file.include('[[frontmatter]]')
    let header = await tp.file.include('[[header]]')
    let buttons = await tp.file.include('[[buttons]]')
    let additional_metadata = await tp.user.createAdditionalMetadata(tp)
    let footer = await tp.file.include('[[footer]]')

    let frontmatter_template = ``
    if (include_frontmatter) {
        frontmatter_template +=
        `${frontmatter}\n`
    }
    frontmatter_template +=
        `${header}\n` +
        `${buttons}\n\n` +
        `${additional_metadata}\n\n` +
        `${footer}`

    // console.log("createFrontmatter: Frontmatter template for \"" + tp.file.title + "\" is:\n" + frontmatter_template)
    return frontmatter_template
}

module.exports = createFrontmatter;

createAdditionalMetadata.js

async function createAdditionalMetadata(tp) {
    let metadata_global_fields = await tp.user.getGlobalMetaFields(tp)
    let metadata_dynamic_fields = await tp.user.getDynamicMetaFields(tp)

    let additional_metadata = `` +
        `>[!info]- 📇 Additional Metadata`

    let metadata_fields = {
        "[!summary]- ⚙️ Properties": [
            metadata_global_fields["properties"],
            metadata_dynamic_fields["properties"]
        ],
        "[!summary]+ 📌 Attributes": [
            metadata_global_fields["attributes"],
            metadata_dynamic_fields["attributes"]
        ],
        "[!summary]+ 🤝 Relationships": [
            metadata_global_fields["relationships"],
            metadata_dynamic_fields["relationships"]
        ],
        "[!summary]- 🤖 AI": [
            metadata_global_fields["ai"],
            metadata_dynamic_fields["ai"]
        ],
        "[!summary]- 📝 Action Items": [
            metadata_global_fields["action_items"],
            metadata_dynamic_fields["action_items"]
        ]
    }

    for (const [key, value] of Object.entries(metadata_fields)) {
        additional_metadata += `\n>\n>> ${key}`
        for (const field of value) {
            if (field) {
                for (let i = 0; i < field.split('\n').length; i++) {
                    additional_metadata += `\n>> ${field.split('\n')[i]}`
                }
            }
        }
    }

    return additional_metadata
}

module.exports = createAdditionalMetadata;

getGlobalMetaFields.js

async function getGlobalMetaFields(tp) {
    const supported_sections = {
        "properties": await tp.file.include('[[properties]]'),
        "attributes": await tp.file.include('[[attributes]]'),
        "relationships": await tp.file.include('[[relationships]]'),
        "ai": await tp.file.include('[[ai]]'),
        "action_items": await tp.file.include('[[action_items]]')
    };

    return supported_sections;
}

module.exports = getGlobalMetaFields;

getDynamicMetaFields.js

async function getFields(tp, section) {
    let template = `${tp.file.folder()} (${section} Template)`
    template = tp.user.toMarkdownFileLink(template)
    try {
        return await tp.file.include(template);
    } catch (e) {
        return [].join("\n");
    }
}
async function getDynamicMetaFields(tp) {
    const supported_sections = {
        "properties": await getFields(tp, "Properties"),
        "attributes": await getFields(tp, "Attributes"),
        "relationships": await getFields(tp, "Relationships"),
        "ai": await getFields(tp, "AI"),
        "action_items": await getFields(tp, "Action Items"),
    };

    return supported_sections;
}

module.exports = getDynamicMetaFields;

properties.md

- UUID:: v1-<% tp.file.creation_date("YYYYMMDDHHmmss") %>
- Created:: <% tp.file.creation_date("YYYY-MM-DDTHH:mm:ss") %>
- Updated:: <% tp.file.include('[[Get last modified date  (Snippet Template)]]') %>

attributes.md

- 🗂 Type:: <% tp.user.getTypeMetaField(tp) %>
- 🗓️ Date:: <% tp.file.include('[[Get last modified short date url (Snippet Template)]]') %>
- 👅 Language:: <% tp.user.getLanguageMetaField(tp) %>
```button
name Add Status
type prepend template
action Cybernus/Backend/fields/metadata/attributes/status
remove true

relationships.md

- 🪐 Space:: <% tp.user.getSpacesMetaField(tp) %>
- 🗺️ MOC:: <% tp.user.getMOCMetaField(tp) %>
- 🖇️ Links:: 
- 🏷️ Tags:: 

ai.md

- 🤖 ai_idea_full_date:: <% tp.user.createDateTreeTag(tp) %>

action_items.md

```button
name Track Action Items with Todoist
type append template
action Cybernus/Backend/fields/metadata/track_action_items
remove true

Final Results


2 Likes

The screenshots are missing the "- :label: Tags:: " due to a typo and I can’t edit the post anymore so please ignore this small detail :slight_smile:

1 Like