# Obsidian Dynamic Templates
_February 29, 2024_
==The main goal of this plugin is to offer a templating system where the generated content can be updated when state changes AND exists as plain Markdown.== Should you stop using the plugin, or Obsidian altogether, you will not lose the dynamic content that has already been generated in your notes. And, because the templates produce plain Markdown, they are fully compatible with [Obsidian Publish](https://obsidian.md/publish).
- A dynamic template reference can be inserted as a section anywhere in a Markdown note.
- Templates are backed by a JavaScript file which outputs Markdown.
- [[Obsidian Dataview|Dataview]]'s JavaScript API can be used.
- The content generated by the templates is saved as regular Markdown.
- In reading view, or if the note is published, only the generated content is visible.
## Source Code & Installation
The plugin is still in early stages of development and not available via Obsidian's community plugins interface yet. But you can find it on GitHub and install it manually:
- [github.com/nates-foo/obsidian-dynamic-templates](https://github.com/nates-foo/obsidian-dynamic-templates)
## Basic Usage
In order to remain invisible in reading view, the dynamic template syntax uses [Markdown comments](https://help.obsidian.md/Editing+and+formatting/Basic+formatting+syntax#Comments) to allow you to add _template references_ inside your notes:
```
%%{ template: 'templates/foobar.js' }%%
```
In this example, `templates/foobar.js` is the _template script_, a JavaScript file in your vault which returns arbitrary Markdown:
```js
// File: templates/foobar.js
const date = new Date().toLocaleDateString();
return `## Header
Date: ${date}
- Foo
- Bar`;
```
==When a template reference is _invoked_ (using a shortcut, for example), the note is automatically edited and our original template reference turns into:==
```
%%{ template: 'templates/foobar.js' }%%
## Header
Date: 2/21/2024
- Foo
- Bar
%% %%
```
`%%{ template: … }%%` marks the beginning of the templated section, and `%% %%` its end. Because they're both Markdown comments, they don't show in reading view, but the header and the list do. ==Each time the template is invoked, its previous generated content is replaced with the new one.== For example, if we invoke this template the next day, this section of the note will change:
```
%%{ template: 'templates/foobar.js' }%%
## Header
Date: 2/22/2024
- Foo
- Bar
%% %%
```
## Template Reference Resolution
Template reference resolution uses a "best match" strategy, so you can provide the full path from the root of your vault, or just a subset of the path as long as it is unique and includes the file name. For example, if there is only one `foobar.js` in the vault, either of these template references will work:
```
%%{ template: 'templates/foobar.js' }%%
```
```
%%{ template: 'foobar.js' }%%
```
## Inserting & Invoking Templates
Several commands are provided to insert or invoke templates and refresh their contents:
- **Invoke templates in active file:** Invokes _all_ templates in the active file.
- **Invoke templates in all Markdown files:** Searches all Markdown notes for template references and invokes them. In a large vault, or depending on the performance of your templates, this may be slow.
- **Invoke templates in all known templated files:** Whenever a template is invoked, the path to the referring note is saved. This command goes through the known referring notes and invokes their templates. This is meant to be more performant than the "Invoke templates in all Markdown files" while still allowing for bulk updates.
- **Insert from known templates:** Insert and invoke a known template at the current cursor position. Templates must be successfully invoked at least once by another command in order to appear in the list of known templates.
Templates can also be invoked from another template using the provided `invokeTemplate` async function:
```js
// File: templates/foobar.js
let markdown = '';
markdown += await invokeTemplate('header.js');
markdown += 'my content…';
markdown += await invokeTemplate('footer.js');
return markdown;
```
## Template Arguments
==Dynamic templates support static arguments,== for example:
```
%%{ template: 'templates/input.js', name: 'Nate', list: [1, 2, 3] }%%
```
Everything between `%%` and `%%` is treated as a JavaScript object so you can use strings, numbers, lists, nested objects, etc. Said object is available in the template script under the special variable `input`:
```js
// File: templates/input.js
const listMd = input.list.map(e => `- ${e}`).join("\n");
return `## Header
My name is ${input.name}.
My list is:
${listMd}`;
```
Invoking the template generates the following:
```
%%{ template: 'templates/input.js', name: 'Nate', list: [1, 2, 3] }%%
## Header
My name is Nate.
My list is:
- 1
- 2
- 3
%% %%
```
## JavaScript Modules & Require
```js
module.exports = …
```
```js
// File: templates/my_template.js
const myModule = require('scripts/my_module.js');
myModule.…
```
## Dataview Integration
[[Obsidian Dataview|Dataview]] is an excellent Obsidian plugin which makes it possible to query notes as if they were a database. If the Dataview plugin is installed, then ==[the Dataview JS API](https://blacksmithgu.github.io/obsidian-dataview/api/code-reference/) is available in templates through the special variable `dv`,== for example:
```js
// File: templates/last-modified.js
// file.mtime is "the date that the file was last modified".
return dv.current().file.mtime.toLocaleString();
```
Usage in a note:
```
%%{ template: 'templates/last-modified.js' }%%
```
Result after invocation:
```
%%{ template: 'templates/last-modified.js' }%%
2/21/2024
%% %%
```
> [!info] Dynamic Templates vs Dataview
> Dynamic templates are meant to be compatible with [[Reuse JavaScript Code Across Obsidian Notes#Dataview and Custom Views|Dataview custom views]], so you could use the same JavaScript file directly with Dataview:
>
> ````
> ```dataviewjs
> // Note the '.js' extension must be omitted.
> await dv.view('templates/last-modified');
> ```
> ````
>
> This will automatically render as the date the file was last modified when using Obsidian's reading view or live preview, however when opening the note in another program—or publishing it via [Obsidian Publish](https://obsidian.md/publish)—it will be a "dead" code block.
>
> In contrast, dynamic templates need to be invoked to update their contents, but the contents are then part of the Markdown file itself. Neither is _good_ or _bad_, but either may be a better fit depending on the use case.
## Use Case: Linking to Similar Notes
I initially wrote this plugin in order to be able to publish recipes which automatically link to other similar recipes, like [[Sausage Pitas (Vegan)]] or [[Veggie Summer Rolls]]. I started by using Dataview only—as explained in [[Obsidian for Meal Planning]]—which worked great but was not compatible with [Obsidian Publish](https://obsidian.md/publish).
%%
https://joschua.io/posts/2023/09/01/obsidian-publish-dataview/
%%