Templater 技能
本技能使技能兼容的代理能够创建和编辑有效的 Templater 模板,包括所有 Templater 特定的语法扩展。
版本信息
本文档基于 Templater v2.18.1+ 编写。请注意:
- 从 v1.x 到 v2.x 有大量 breaking changes,许多 API 签名已变更
- 旧版本可能不支持本文档中的所有功能
- 使用前请检查您的 Templater 版本
重要:本文档主要介绍 v2.0+ 的现代用法,旧版兼容信息请参考文档末尾的兼容部分。
v1 → v2 主要 breaking changes
- tp.file.create_new:参数顺序变化(template 第一,filename 第二,可选)
- tp.system.prompt / suggester:移除 title 参数(multi_suggester 有 title)
- tp.frontmatter:仍只读;修改用 app.fileManager.processFrontMatter
- tp.web.random_picture / daily_quote:可能不稳定或受限(建议避免依赖第三方)
- 旧 tp.file.create():已移除
- 沙盒增强:系统命令、HTTP 请求在移动端/非 trusted 模板受限
升级建议:备份 vault → 更新插件 → 测试模板
版本兼容性指南
版本迁移指南
| 功能 | 旧版本 (<1.10.0) | 新版本 (1.16.0+) |
|---|---|---|
| 用户输入 | tp.system.prompt() | tp.system.prompt() (参数变化) |
| Frontmatter访问 | tp.frontmatter.get() | tp.frontmatter 直接访问属性 |
| 文件创建 | tp.file.create() | tp.file.create_new() |
| 静态函数 | 无 | tp.static |
| 事件系统 | 无 | tp.hooks |
| 模板配置 | 无 | tp.config |
关键API变化说明
tp.system.prompt() 参数变化
- 旧版本:
tp.system.prompt(text, default, throw_on_cancel) - v1.x 版本:
tp.system.prompt(prompt_text: string, default_value?: string, throw_on_cancel?: boolean, multiline?: boolean, title?: string) - v2.x 版本:
tp.system.prompt(prompt_text?: string, default_value?: string, throw_on_cancel: boolean = false, multiline?: boolean = false) - 重要:v2.x 中移除了
title参数
Frontmatter访问方式变化
- 旧版本:使用
tp.frontmatter.get('key')获取属性 - 新版本:直接使用
tp.frontmatter.key访问属性 - 注意:
tp.frontmatter是只读的,用于快速访问元数据。要修改元数据,需要使用app.fileManager.processFrontMatter结合tp.hooks
文件创建API变化
- 旧版本:
tp.file.create(filename, content, open) - v1.x 版本:
tp.file.create_new(filename: string, template?: TFile | string, open_new?: boolean, folder?: TFolder) - v2.x 版本:
tp.file.create_new(template: TFile | string, filename?: string, open_new?: boolean = false, folder?: TFolder | string) - 重要:v2.x 中参数顺序发生了重大变化,template 现在是第一个参数
新增功能
tp.static:提供不依赖执行上下文的静态函数访问tp.hooks:提供事件系统,支持模板执行前后的回调tp.config:提供模板执行的配置信息,包括运行模式和参数
安全警告
Templater 允许执行任意 JavaScript 代码,存在安全风险:
主要风险
- 执行系统命令:
tp.system.executeCommandWithOutput()可执行系统命令,可能被恶意模板利用,在 v2.x 中已被 Sandbox 严格限制 - 文件操作:可读写保险库中的文件
- 网络请求:可发送 HTTP 请求到任意服务器,
tp.obsidian.requestUrl()可能带来 CORS 或敏感数据泄露风险,在移动端/沙盒环境中有严格限制
安全建议
- 只使用可信模板:不要从不可信来源复制模板
- 限制系统命令:谨慎使用
tp.system.executeCommandWithOutput(),在 v2.x 中已被 Sandbox 严格限制 - 审查网络请求:谨慎使用
tp.obsidian.request()和tp.obsidian.requestUrl(),避免向不可信的服务器发送请求,防止敏感数据泄露。注意:这些函数在移动端/沙盒环境中有严格限制。 - 启用模板信任:在 Templater v2.x 中,"Only run trusted templates" 选项默认开启,这是一个重要的安全措施
- 审查模板代码:使用前检查模板中的 JavaScript 代码
- 备份保险库:定期备份您的 Obsidian 保险库
概述
Templater 是 Obsidian 的一个强大插件,允许您创建带有变量、函数和控制流的动态模板。它通过模板语法扩展了 Markdown,这些语法在插入模板时会被处理。
基本语法
模板变量
<% tp.file.title %> <!-- 文件标题(无扩展名) -->
<% tp.file.folder() %> <!-- 文件夹名(相对) -->
<% tp.file.folder(true) %> <!-- 绝对文件夹路径 -->
<% tp.file.path(true) %> <!-- vault 相对路径 -->
<% tp.file.content %> <!-- 执行前文件内容(只读,快照) -->
用户输入
现代 Templater 使用 tp.system.prompt 进行用户输入:
<%*
// 基本输入
const title = await tp.system.prompt('Enter a title:');
// 带默认值
const name = await tp.system.prompt('Enter your name:', 'John Doe');
// 多行输入
const description = await tp.system.prompt('Enter description:', '', false, true);
-%>
tp.system.prompt参数说明:
tp.system.prompt(prompt_text?: string, default_value?: string, throw_on_cancel: boolean = false, multiline?: boolean = false)
prompt_text:提示文本(可选)default_value:默认值(可选)throw_on_cancel:取消时是否抛出错误(可选,默认false)multiline:是否支持多行输入(可选,默认false,设置为true时显示文本域)
下拉选择
<%*
// 基本用法
const selected = await tp.system.suggester(
['Option 1', 'Option 2'],
['value1', 'value2']
);
// 高级用法(带显示函数)
const item = await tp.system.suggester(
item => item.display,
[
{display: "Option 1", value: "value1"},
{display: "Option 2", value: "value2"}
]
);
tR = item.value;
-%>
tp.system.suggester参数说明:
tp.system.suggester(text_items: string[] | ((item: T) => string), items: T[], throw_on_cancel?: boolean, placeholder?: string, limit?: number)
text_items:显示的文本数组,或从item生成文本的函数items:实际返回的值数组throw_on_cancel:取消时是否抛出错误(可选,默认false)placeholder:输入框占位符(可选)limit:显示的最大项目数(可选)
多选下拉选择
Templater 还提供 tp.system.multi_suggester 用于多选:
<%*
const choices = await tp.system.multi_suggester(
["选项1", "选项2", "选项3"],
["val1", "val2", "val3"],
false,
"请选择...",
undefined,
"多选示例"
);
tR = `选择了: ${choices.join(', ')}`;
-%>
tp.system.multi_suggester参数说明:
tp.system.multi_suggester(
text_items: string[] | ((item: T) => string),
items: T[],
throw_on_cancel?: boolean,
placeholder?: string,
limit?: number,
title?: string
)
text_items:显示的文本数组,或从item生成文本的函数items:实际返回的值数组throw_on_cancel:取消时是否抛出错误(可选,默认false)placeholder:输入框占位符(可选)limit:显示的最大项目数(可选)title:选择框标题(可选)- 返回值为选中项的数组
文件创建
<%* await tp.file.create_new('Content', 'New Note.md', true) %>
<%* await tp.file.create_new('Content', 'New Note.md', true, 'Projects/Meetings') %>
<%* await tp.file.create_new(tp.file.find_tfile('Templates/Note Template.md'), 'New Note.md', true) %>
说明:Templater v2.x API 签名为 create_new(template: TFile | string, filename?: string, open_new?: boolean = false, folder?: TFolder | string)
template:模板内容(字符串)或模板文件(TFile对象)filename:新文件的名称(可选,默认 "Untitled")open_new:是否打开新创建的文件(可选,默认false)folder:目标文件夹(TFolder对象或字符串路径)(可选)
重要:tp.file.create_new 返回的是一个 Promise<TFile> 对象,这对于后续需要立即对新创建文件进行操作(如移动、重命名)至关重要。
示例说明:
- 基本用法:
tp.file.create_new('文件内容', '文件名.md', true)- 第一个参数为模板内容字符串 - 指定文件夹:
tp.file.create_new('文件内容', '文件名.md', true, 'folder/path')- 第一个参数为模板内容字符串 - 使用模板文件:
tp.file.create_new(template文件对象, '文件名.md', true)- 第一个参数为模板文件对象
文件夹路径说明:
- 文件夹路径应使用相对于保险库根目录的完整路径,如
'Projects/Meetings' - 路径取决于用户的实际文件夹结构
- 确保目标文件夹已存在,否则需要先创建文件夹
重要警告:
- 避免在用于
create_new的模板文件中包含自引用逻辑,可能导致无限创建 - 当使用
TFile对象作为模板参数时,确保该文件不会再次触发创建逻辑
示例:
<%*
// 安全的创建方式 - 使用字符串内容
const content = `# New Note
Created on ${tp.date.now('YYYY-MM-DD')}`;
await tp.file.create_new(content, 'New Note.md', true);
-%>
<%*
// 使用模板文件
const templateFile = tp.file.find_tfile('Templates/Note Template.md');
await tp.file.create_new(templateFile, 'New Note.md', true);
-%>
日期和时间
<% tp.date.now('YYYY-MM-DD') %>
<% tp.date.now('YYYY-MM-DD HH:mm:ss') %>
<% tp.date.now('YYYY-MM-DD', 1) %> <!-- 明天的日期 -->
<% tp.date.now('YYYY-MM-DD HH:mm', 0, new Date()) %> <!-- 使用指定的 Date 对象 -->
<% tp.date.yesterday('YYYY-MM-DD') %>
<% tp.date.tomorrow('YYYY-MM-DD') %>
tp.date.now 参数说明:
tp.date.now(format: string, offset?: number, date?: Date)
format:日期时间格式字符串(如 'YYYY-MM-DD')offset:天数偏移量(可选,默认 0)date:基础日期对象(可选,默认当前时间)
示例:
tp.date.now('YYYY-MM-DD'):当前日期tp.date.now('YYYY-MM-DD', 1):明天的日期tp.date.now('YYYY-MM-DD', -1):昨天的日期tp.date.now('YYYY-MM-DD HH:mm', 0, new Date('2023-12-25')):指定日期的格式化结果
tp.date.weekday 参数说明:
tp.date.weekday(format: string, dayOfWeek: number, date?: Date)
format:日期时间格式字符串dayOfWeek:星期几(0=周日,1=周一,...,6=周六),返回最近的那个星期几date:基础日期对象(可选,默认当前时间)
示例:
tp.date.weekday('YYYY-MM-DD', 1):最近的周一tp.date.weekday('YYYY-MM-DD', 6, new Date('2023-12-25')):2023年12月25日之后最近的周六
tp.date.month 参数说明:
tp.date.month(format: string, offset?: number, date?: Date)
format:日期时间格式字符串offset:月份偏移量(可选,默认 0)date:基础日期对象(可选,默认当前时间)
示例:
tp.date.month('YYYY-MM'):当前月份tp.date.month('YYYY-MM', 1):下个月tp.date.month('YYYY-MM', -1):上个月
静默执行(保留换行)
Templater 支持多种执行模式,可组合使用 +(保留换行)和 -(去除后续换行)修饰符:
输出命令
| 语法 | 说明 |
|---|---|
<% ... %> |
输出命令,不保留换行 |
<%+ ... %> |
输出命令,保留换行 |
<% ... -%> |
输出命令,去除后续换行 |
<%+ ... -%> |
输出命令,保留换行但去除后续多余换行 |
执行命令
| 语法 | 说明 |
|---|---|
<%* ... %> |
执行命令,不保留换行 |
<%*+ ... %> |
执行命令,保留换行 |
<%* ... -%> |
执行命令,去除后续换行 |
<%*+ ... -%> |
执行命令,保留换行但去除后续多余换行 |
<%+ tp.date.now('YYYY-MM-DD') %>
<%*+
// 执行 JavaScript 代码并保留换行
const today = new Date();
tR = `今天是 ${today.toLocaleDateString()}`;
-%>
说明:+ 修饰符用于保留模板中的空白行,避免输出挤在一起;- 修饰符用于去除标签后的多余换行,使输出更加紧凑。这在需要保持模板结构清晰时非常有用。
执行模式(修饰符)
| 语法 | 输出 | 保留换行 | 去除后续换行 |
|---|---|---|---|
<% ... %> |
是 | 否 | 否 |
<%+ ... %> |
是 | 是 | 否 |
<% ... -%> |
是 | 否 | 是 |
<%+ ... -%> |
是 | 是 | 是 |
<%* ... %> |
否 | 否 | 否 |
<%*+ ... %> |
否 | 是 | 否 |
<%* ... -%> |
否 | 否 | 是 |
<%*+ ... -%> |
否 | 是 | 是 |
高级语法
JavaScript 执行
<%*
const today = new Date();
const formattedDate = today.toISOString().split('T')[0];
tp.file.rename(`Note ${formattedDate}`);
-%>
控制流
<%*
// 直接使用 tR,不要声明
if (tp.file.title.includes('Meeting')) {
tR = 'This is a meeting note.';
} else {
tR = 'This is a regular note.';
}
-%>
<%*
tR = '';
for (let i = 1; i <= 5; i++) {
tR += `- Item ${i}\n`;
}
-%>
最佳实践:在复杂的逻辑块中,应始终推荐使用 tR += '...'(累加)而非 tR = '...'(覆盖),除非明确意图是清空之前的内容。这可以保护之前已生成的文本不被意外覆盖。
示例:
<%*
let message = "Hello";
tR += `${message} World\n`; // 使用 += 而不是 =
%>
函数
<% tp.file.cursor() %>
<% tp.file.rename('New Title.md') %>
<% tp.file.move('New Folder/New Title.md') %>
<% tp.system.executeCommandWithOutput('echo "Hello World"') %>
<% tp.user.myFunction() %>
Frontmatter
<%*
const tag = await tp.system.prompt('Enter a tag:');
-%>
---
title: <% tp.file.title %>
date: <% tp.date.now('YYYY-MM-DD') %>
tags:
- template
- <% tag %>
---
tp.frontmatter
现代 Templater 提供 tp.frontmatter 对象以只读方式访问当前文件的 frontmatter:
<%*
// 读取 frontmatter
const title = tp.frontmatter.title;
const tags = tp.frontmatter.tags;
%>
重要说明:
tp.frontmatter是一个只读对象,仅用于快速访问元数据在 YAML frontmatter 中直接使用异步命令(如
await tp.system.prompt())会导致解析错误推荐在模板开头使用 JavaScript 执行块收集用户输入,然后在 frontmatter 中使用变量
关键限制:
tp.frontmatter的可用性取决于文件状态:现有文件
- 模板变量(如
<% tp.file.title %>)可用 - 可以通过
tp.frontmatter访问现有元数据
新创建文件
- 在文件创建完成前,
tp.frontmatter可能为空或不可用
- 模板变量(如
修改 Frontmatter 的正确方法:
要修改 Frontmatter,需要结合 tp.hooks.on_all_templates_executed 使用 Obsidian 的 app.fileManager.processFrontMatter:
<%*
tp.hooks.on_all_templates_executed(async () => {
const file = tp.file.find_tfile(tp.file.path(true));
await app.fileManager.processFrontMatter(file, (fm) => {
fm["status"] = "active";
fm["priority"] = "high";
// 添加标签
if (!fm.tags) fm.tags = [];
if (!fm.tags.includes("new-tag")) fm.tags.push("new-tag");
// 移除标签
fm.tags = fm.tags.filter(tag => tag !== "old-tag");
});
});
%>
Obsidian 集成
tp.obsidian API
Templater 提供 tp.obsidian 对象以访问 Obsidian 原生 API,用于高级操作。tp.obsidian.app 与全局 app 对象是等价的,都是指向 Obsidian 应用实例的引用。
tp.config
现代 Templater 提供 tp.config 对象以访问模板配置信息:
<%*
// 获取当前模板文件
const templateFile = tp.config.template_file;
tR = `Template: ${templateFile.name}`;
// 获取目标文件
const targetFile = tp.config.target_file;
if (targetFile) {
tR += `\nTarget: ${targetFile.name}`;
}
// 获取运行模式
const runMode = tp.config.run_mode;
tR += `\nRun mode: ${runMode}`;
%>
tp.config属性说明:
template_file:当前模板文件(TFile对象)target_file:目标文件(TFile对象,可能为undefined)run_mode:运行模式(字符串,如 "CreateNewNote"、"InsertTemplate" 等)args:传递给模板的参数(对象,可能为undefined)
tp.config.args详细说明:
- 当通过命令调用模板并传递参数时,参数会存储在此对象中
- 例如,通过Templater URI调用时传递的参数会被解析到该对象
- 可用于构建接受外部参数的模板,实现更灵活的模板行为
用途:
- 调试模板执行环境
- 根据运行模式执行不同逻辑
- 构建条件性模板行为
- 处理外部传递的参数
示例:
<%*
// 检查是否有传递的参数
if (tp.config.args) {
const title = tp.config.args.title || 'Default Title';
const tags = tp.config.args.tags || ['template'];
tR = `Title: ${title}\nTags: ${tags.join(', ')}`;
} else {
tR = 'No arguments provided';
}
-%>
<%*
// 获取当前保险库中的所有文件
const files = app.vault.getFiles(); // 推荐使用全局 app 对象
tR = `保险库中有 ${files.length} 个文件`;
-%>
<%*
// 获取文件创建时间和修改时间
const creationDate = tp.file.creation_date('YYYY-MM-DD');
const modifiedDate = tp.file.last_modified_date('YYYY-MM-DD');
tR = `创建时间: ${creationDate}\n`;
tR += `修改时间: ${modifiedDate}`;
-%>
HTTP 请求
Templater 支持通过 tp.obsidian 进行 HTTP 请求:
<%*
// 发送 HTTP 请求(返回响应对象)
const response = await tp.obsidian.request({
url: 'https://api.example.com/data',
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
tR = `响应状态: ${response.status}`;
-%>
<%*
// 包含授权头的请求示例
const apiToken = "your_api_token_here";
const response = await tp.obsidian.request({
url: 'https://api.example.com/data',
method: 'GET',
headers: {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
tR = `响应状态: ${response.status}`;
-%>
<%*
// 发送 HTTP 请求(返回响应文本)
const responseText = await tp.obsidian.requestUrl('https://api.example.com/data');
tR = responseText;
-%>
HTTP 请求错误处理
重要:HTTP 请求可能因网络问题、服务器错误等原因失败,应使用 try-catch 处理:
<%*
try {
const response = await tp.obsidian.request({
url: 'https://api.example.com/data',
method: 'GET'
});
if (response.status === 200) {
const data = JSON.parse(response);
tR = `成功获取数据: ${data.message}`;
} else {
tR = `请求失败: ${response.status}`;
}
} catch (error) {
console.error('网络错误:', error);
new app.Notice('无法获取数据,请检查网络连接', 5000);
tR = `错误: ${error.message}`;
}
-%>
用户函数
创建用户函数
配置用户脚本文件夹:
步骤 1:创建文件夹
- 在 Obsidian 保险库中创建一个文件夹,如
scripts/
步骤 2:设置路径
- 打开 Obsidian 设置(⚙️)
- 导航到:
社区插件→Templater→设置 - 在 "User Scripts Folder" 字段中输入文件夹路径,如
scripts/ - 点击 "确定" 保存设置
注意:
- Templater 默认没有预设的用户脚本文件夹
- 路径是相对于保险库根目录的相对路径
- 确保文件夹名称与设置中的路径一致
- 在 Obsidian 保险库中创建一个文件夹,如
创建 JavaScript 文件(例如:
scripts/myFunctions.js):
// scripts/myFunctions.js - 导出多个函数
module.exports = {
greet: function(tp, name) {
return `Hello, ${name}! Current file: ${tp.file.title}`;
},
getRandomNumber: function(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
};
// 或者 - 导出单个函数
function mainFunction(tp) {
return tp.file.title;
}
module.exports = mainFunction;
重要说明:
- 必须传递 tp 参数:用户函数需要接收
tp对象作为第一个参数,才能在函数内部使用 Templater 功能 - 不同导出方式的调用:
- 导出多个函数:
tp.user.scriptName.functionName(tp, ...) - 导出单个函数:
tp.user.scriptName(tp)
- 导出多个函数:
使用用户函数
<%*
// 调用导出多个函数的脚本
const greeting = tp.user.myFunctions.greet(tp, 'World');
tR = greeting;
-%>
<%*
// 调用导出单个函数的脚本
const title = tp.user.mainFunction(tp);
tR = title;
-%>
模板示例
每日笔记模板
---
title: <% tp.date.now('YYYY-MM-DD') %>
date: <% tp.date.now('YYYY-MM-DD HH:mm:ss') %>
tags:
- daily
---
<%*
const today = tp.date.now('YYYY-MM-DD');
const dayName = tp.date.now('dddd');
tR = `# ${today} ${dayName}\n\n`;
tR += "## Tasks\n\n";
tR += "- [ ] \n\n";
tR += "## Notes\n\n\n";
tR += "## Reflection\n\n\n";
tR += "## Tomorrow's Tasks\n\n";
tR += "- [ ] \n\n";
-%>
<% tp.file.cursor() %>
会议笔记模板
<%*
const meetingTitle = await tp.system.prompt('Meeting title:');
const meetingType = await tp.system.prompt('Meeting type:');
const attendees = await tp.system.prompt('Attendees (comma-separated):');
-%>
---
title: Meeting - <% meetingTitle %>
date: <% tp.date.now('YYYY-MM-DD HH:mm:ss') %>
tags:
- meeting
- <% meetingType %>
attendees: "<% attendees %>"
---
# Meeting - <% meetingTitle %>
## Date & Time
<%*
const start = new Date();
const end = new Date(start.getTime() + 60 * 60 * 1000);
// 使用tp.date.now格式化日期,避免依赖全局moment对象
const startTime = tp.date.now('YYYY-MM-DD HH:mm', 0, start);
const endTime = tp.date.now('YYYY-MM-DD HH:mm', 0, end);
tR = `${startTime} - ${endTime}`;
-%>
## Attendees
-
## Agenda
-
## Notes
## Decisions
-
## Action Items
- [ ]
## Next Steps
-
<% tp.file.cursor() %>
项目笔记模板
<%*
const projectTitle = await tp.system.prompt('Project title:');
const projectType = await tp.system.prompt('Project type:');
-%>
---
title: <% projectTitle %>
date: <% tp.date.now('YYYY-MM-DD') %>
tags:
- project
- <% projectType %>
status: planning
priority: medium
---
# <% projectTitle %>
## Overview
## Goals
-
## Timeline
### Milestones
- [ ]
### Tasks
- [ ]
## Resources
-
## Notes
<% tp.file.cursor() %>
最佳实践
- 使用描述性变量名在模板中
- 组织用户函数在单独的文件中以提高可维护性
- 测试模板在广泛使用前
- 使用注释在复杂模板中解释逻辑
- 定期备份模板
- 使用 tR += 而非 tR =:在复杂逻辑中,推荐使用
tR += '...'(累加)而非tR = '...'(覆盖),特别是在新文件 + 多模板块的情况下,这可以避免意外覆盖已生成的内容
错误处理与调试
调试技巧
<%*
// 在控制台输出调试信息(在 Obsidian 开发者工具中查看)
console.log('Debug info:', tp.file.title);
console.log('Frontmatter:', tp.frontmatter);
// 使用 try-catch 处理错误
try {
const result = await tp.file.include("[[Template]]");
tR = result;
} catch (error) {
console.error('Error:', error);
tR = `Error: ${error.message}`;
// 显示用户友好的错误消息
new app.Notice(`Template error: ${error.message}`, 5000);
}
%>
错误处理最佳实践
处理文件不存在的情况
<%*
// 处理文件不存在的情况
const filePath = "Notes/Example.md";
try {
const file = app.vault.getAbstractFileByPath(filePath);
if (!file) {
throw new Error(`文件不存在: ${filePath}`);
}
const fileContent = await app.vault.read(file);
tR = fileContent;
} catch (error) {
console.error(`读取文件错误: ${error.message}`);
new app.Notice(`错误: ${error.message}`, 5000);
tR = `⚠️ 无法加载内容`;
}
%>
处理用户取消输入的情况
<%*
try {
const title = await tp.system.prompt('Enter a title:', '', true);
if (!title) {
throw new Error('用户取消了输入');
}
tR = `Title: ${title}`;
} catch (error) {
console.error('输入错误:', error.message);
new app.Notice('操作已取消', 3000);
tR = '⚠️ 操作已取消';
}
%>
处理网络错误
<%*
try {
const response = await tp.obsidian.request({
url: 'https://api.example.com/data',
method: 'GET'
});
tR = `Response status: ${response.status}`;
} catch (error) {
console.error('网络错误:', error.message);
new app.Notice('网络请求失败,请检查网络连接', 5000);
tR = '⚠️ 网络请求失败';
}
%>
调试说明:
- 开发者工具:在 Obsidian 中按
Ctrl+Shift+I(Windows/Linux)或Cmd+Option+I(Mac)打开开发者工具,在 Console 标签页查看console.log输出 - Notice:
new Notice(message, timeout)用于在 Obsidian 界面显示临时消息,提升用户体验 - 异步调试:对于异步操作,记得使用
try-catch包裹并添加await
异步支持
Templater 支持 await 关键字处理异步操作:
<%*
// 异步包含其他模板
const content = await tp.file.include("[[Template]]");
tR = content;
// 异步获取文件内容
const file = app.vault.getAbstractFileByPath("Notes/Example.md");
const fileContent = await app.vault.read(file);
tR += fileContent;
%>
异步执行顺序
重要说明:
- 执行机制:Templater 首先会解析文档中的所有
<%* %>块和<% %>块,然后再按它们在文档中出现的顺序依次执行(类似变量提升 hoisting) - 异步操作(使用
await)会阻塞后续执行,直到完成 - 多个异步命令会按顺序执行,确保操作的一致性
- 未使用
await的异步操作会并行执行,可能导致不可预测的结果 - 执行块中的异步操作会影响整个模板的执行进度,确保操作的顺序性
- 注意:执行顺序可能与直观的从上到下阅读顺序不完全一致,特别是在复杂的模板中
Web API 注意事项
重要:tp.web 相关函数(如 random_picture()、daily_quote())在 Templater v2.x 中已完全移除(出于安全原因)。这些函数在 v2.0+ 版本中不再可用。
tp.static
现代 Templater 提供 tp.static 对象以访问静态函数,主要用于:
- 提供不依赖执行上下文的静态函数访问
- 适用于需要传递 Templater 功能到回调函数的场景
- 确保在任何上下文中都能访问核心功能
- 在用户脚本(User Scripts)中访问那些通常需要活动编辑器上下文才能调用的函数
重要说明:tp.static 对象仍需要在 JavaScript 执行块(<%*)内使用,但它提供的函数可以被传递到其他函数或回调中使用。它不能在执行块外(如 <%)直接使用。
用户脚本中的使用示例:
// scripts/myFunctions.js - 在用户脚本中使用 tp.static
module.exports = {
formatDate: function(date, tpStatic) {
// 在用户脚本中,无法直接访问 tp 对象
// 但可以通过传递 tp.static 来使用 Templater 的日期函数
return tpStatic.date.now('YYYY-MM-DD', 0, date);
}
};
在模板中调用:
<%*
const formattedDate = tp.user.myFunctions.formatDate(new Date(), tp.static);
tR = formattedDate;
-%>
<%*
// 在执行块内使用
const now = tp.static.date.now('YYYY-MM-DD');
tR = now;
-%>
<%*
// 作为参数传递
function formatDate(date, tpStatic) {
return tpStatic.date.now('YYYY-MM-DD', 0, date);
}
const formatted = formatDate(new Date(), tp.static);
tR = formatted;
-%>
tp.hooks
Templater 提供 tp.hooks 对象以注册事件监听器,用于响应模板执行的不同阶段。最核心的 Hook 是 tp.hooks.on_all_templates_executed(callback),它是处理模板执行后清理工作(如修改刚生成的文件的属性)的唯一安全方式。
重要:Hook 的注册应尽量放置在模板代码的最顶层。如果在复杂的逻辑中(如条件判断后)才注册 Hook,而此时触发点已过,回调将永远不会执行。
<%*
// 注册事件监听器
tp.hooks.on('on_all_templates_executed', () => {
// 所有模板执行完成后执行的清理操作
console.log('All templates executed');
});
// 注册一次性事件监听器
tp.hooks.once('on_template_executed', (templateFile, targetFile) => {
console.log('Template executed:', templateFile.name, '→', targetFile.name);
});
%>
常见错误与陷阱
模板开发常见陷阱
- 参数顺序错误:将
filename写在template前(tp.file.create_new)。 - 忘记使用 await:对异步操作(如 create_new、prompt)不使用 await,导致竞态条件。
- 在 frontmatter 中使用异步命令:直接在 YAML frontmatter 中写
<% await tp.system.prompt() %>会导致解析错误。 - 在循环中频繁创建文件:循环内多次调用
tp.file.create_new会严重影响性能。 - 依赖 tp.web.*:tp.web.random_picture / daily_quote 在 v2 中已移除或不稳定。
- 忘记使用 tR +=:在多模板块中使用
tR =覆盖内容,导致之前的输出被清空。
推荐的解决方案
tp.file.create_new 标准写法:
<%* const newFile = await tp.file.create_new( template, // 第一位:模板内容或 TFile filename, // 第二位:文件名 open, // 是否打开 folder // 文件夹路径 ); -%>修改 frontmatter 的安全方式:
<%* tp.hooks.on_all_templates_executed(async () => { const file = tp.file.find_tfile(tp.file.path(true)); await app.fileManager.processFrontMatter(file, (fm) => { fm.status = "active"; fm.tags = [...(fm.tags || []), "new"]; }); }); -%>批量文件操作:预生成内容后再批量创建文件,减少 I/O 操作。
tp.hooks.on_all_templates_executed 的重要性
tp.hooks.on_all_templates_executed 是 Templater v2 中最重要、最安全的模板执行后处理机制。它的核心优势:
- 执行时机:在所有模板代码执行完毕后运行,确保文件已完全创建。
- 安全性:提供了一个安全的上下文来修改刚创建的文件(如修改 frontmatter)。
- 避免竞态:避免了因异步操作顺序导致的文件未就绪问题。
推荐用法:
- 修改新创建文件的 frontmatter
- 对新文件进行后续操作(如移动、重命名)
- 执行需要文件完全就绪的逻辑
示例:
<%*
// 在模板开始处注册 Hook(推荐)
tp.hooks.on_all_templates_executed(async () => {
// 安全地修改刚创建的文件
const file = tp.file.find_tfile(tp.file.path(true));
await app.fileManager.processFrontMatter(file, (fm) => {
fm.createdAt = tp.date.now('YYYY-MM-DD');
});
// 显示完成通知
new app.Notice('模板执行完成,文件已更新!', 5000);
});
// 其他模板逻辑...
-%>
事件系统详细说明:
| 事件 | 触发条件 | 参数 | 使用场景 |
|---|---|---|---|
on_template_create |
模板文件被创建时 | (templateFile: TFile) |
初始化新模板、添加默认内容 |
on_template_delete |
模板文件被删除时 | (templateFile: TFile) |
清理与模板相关的资源 |
on_template_executed |
单个模板执行完成后 | (templateFile: TFile, targetFile: TFile) |
记录模板使用情况、执行后续操作 |
on_all_templates_executed |
所有模板执行完成后 | () |
执行全局清理、显示完成消息、修改刚生成文件的属性 |
核心用法示例:使用 on_all_templates_executed 修改刚生成的文件属性
<%*
tp.hooks.on_all_templates_executed(async () => {
// 确保文件已完全创建
const file = tp.file.find_tfile(tp.file.path(true));
if (file) {
// 修改文件的 frontmatter
await app.fileManager.processFrontMatter(file, (fm) => {
fm.status = 'active';
fm.priority = 'high';
});
}
});
%>
示例:记录模板执行
<%*
tp.hooks.on('on_template_executed', async (templateFile, targetFile) => {
const logFile = app.vault.getAbstractFileByPath('Templater Log.md');
if (logFile) {
const content = await app.vault.read(logFile);
const newContent = `${content}\n- ${new Date().toISOString()}: ${templateFile.name} → ${targetFile.name}`;
await app.vault.modify(logFile, newContent);
}
});
%>
用途:
- 执行清理操作
- 记录模板执行历史
- 触发后续操作
- 实现复杂的模板工作流
异步执行最佳实践
推荐做法:
<%*
// 按顺序执行多个异步操作
const content1 = await tp.file.include("[[Template1]]");
const content2 = await tp.file.include("[[Template2]]");
tR = content1 + "\n\n" + content2;
%>
参考
功能分类
为了更清晰地组织 Templater 的功能,以下是按类别分组的 API 参考:
文件操作
tp.file.create_new()- 创建新文件tp.file.rename()- 重命名当前文件tp.file.move()- 移动当前文件tp.file.cursor()- 放置光标位置tp.file.creation_date()- 文件创建日期tp.file.last_modified_date()- 文件最后修改日期tp.file.include()- 包含其他文件内容tp.file.find_tfile()- 通过路径获取 TFile 对象tp.file.selection()- 获取选中的文本(仅在对已有文件插入模板且有文本被选中时有效)
日期和时间
tp.date.now()- 当前日期/时间tp.date.yesterday()- 昨天的日期tp.date.tomorrow()- 明天的日期tp.date.weekday()- 获取特定星期几的日期tp.date.month()- 月份操作
用户交互
tp.system.prompt()- 用户输入提示tp.system.suggester()- 下拉选择提示tp.system.clipboard()- 读取剪贴板
系统操作
tp.system.executeCommandWithOutput()- 执行 shell 命令并返回输出- 重要警告:该函数在 Templater v2.x 中已被 Sandbox 严格限制。在大多数现代环境中(尤其是移动端和 sandbox 模式)此函数无法正常工作,强烈建议不要在生产模板中使用。
Web 操作
tp.obsidian.request()- 发送 HTTP 请求tp.obsidian.requestUrl()- 发送 HTTP 请求并返回响应文本
注意:tp.web 相关函数(如 random_picture()、daily_quote())在 Templater v2.x 中已完全移除(出于安全原因)。
用户脚本
tp.user.scriptName()- 单个导出函数tp.user.scriptName.func()- 多个导出函数
高级功能
tp.obsidian- 访问 Obsidian 原生 APItp.static- 访问静态函数tp.frontmatter- 访问和修改 frontmattertp.hooks- 事件系统tp.config- 模板配置
Templater 变量
| 变量 | 描述 | 示例 |
|---|---|---|
tp.file.title |
当前文件标题 | <% tp.file.title %> |
tp.file.folder() |
当前文件文件夹(相对路径) | <% tp.file.folder() %> |
tp.file.folder(true) |
当前文件文件夹(绝对路径) | <% tp.file.folder(true) %> |
tp.file.path() |
当前文件路径(相对路径) | <% tp.file.path() %> |
tp.file.path(true) |
当前文件路径(绝对路径) | <% tp.file.path(true) %> |
tp.file.content |
当前文件内容(仅在对已有文件执行模板时有效;新建文件时为空)。注意:该变量在模板执行开始时即固定,仅包含执行模板前磁盘上的文件内容,不反映执行过程中的动态变化。 | <% tp.file.content %> |
tp.frontmatter.tags |
文件标签数组 | <% tp.frontmatter.tags %> |
tp.frontmatter |
当前文件的 frontmatter | <% tp.frontmatter.title %> |
Templater 函数
| 函数 | 描述 | 示例 | 异步 |
|---|---|---|---|
tp.date.now() |
当前日期/时间 | <% tp.date.now('YYYY-MM-DD') %> |
❌ |
tp.date.yesterday() |
昨天的日期 | <% tp.date.yesterday('YYYY-MM-DD') %> |
❌ |
tp.date.tomorrow() |
明天的日期 | <% tp.date.tomorrow('YYYY-MM-DD') %> |
❌ |
tp.date.weekday() |
获取特定星期几的日期 | <% tp.date.weekday('YYYY-MM-DD', 1) %> |
❌ |
tp.date.month() |
月份操作 | <% tp.date.month('YYYY-MM-DD', 1) %> |
❌ |
tp.file.create_new() |
创建新文件 | <%* await tp.file.create_new('Content', 'New Note.md', true); -%> |
✅ |
tp.file.rename() |
重命名当前文件 | <% tp.file.rename('New Title.md') %> |
❌ |
tp.file.move() |
移动当前文件 | <% tp.file.move('New Folder/New Title.md') %> |
❌ |
tp.file.cursor() |
放置光标位置。注意:该函数可以接受一个可选的数字参数,例如 <% tp.file.cursor(1) %>, <% tp.file.cursor(2) %>,允许用户通过快捷键在多个预设光标点之间跳转。 |
<% tp.file.cursor(1) %> |
❌ |
tp.file.cursor_append() |
在当前光标位置追加内容(常用于多光标场景) | <% tp.file.cursor_append('追加内容') %> |
❌ |
tp.file.creation_date() |
文件创建日期 | <% tp.file.creation_date('YYYY-MM-DD') %> |
❌ |
tp.file.last_modified_date() |
文件最后修改日期 | <% tp.file.last_modified_date('YYYY-MM-DD') %> |
❌ |
tp.file.include() |
包含其他文件内容 | <%* tR = await tp.file.include('[[Template]]'); -%> |
✅ |
tp.file.find_tfile() |
通过路径获取 TFile 对象 | <% tp.file.find_tfile('Notes/Example.md') %> |
❌ |
tp.file.selection() |
获取选中的文本(仅在对已有文件插入模板且有文本被选中时有效,返回选中字符串或空) | <% tp.file.selection() %> |
❌ |
tp.file.exists() |
检查文件/文件夹是否存在,返回 boolean(同步) | <% tp.file.exists('Folder/Note.md') %> |
❌ |
tp.system.prompt() |
用户输入提示 | <% tp.system.prompt('Enter a title:', 'Default', false, true) %> |
❌ |
tp.system.suggester() |
下拉选择提示 | <% tp.system.suggester(item => item.display, [{display: "Option 1", value: "value1"}]) %> |
❌ |
tp.system.clipboard() |
读取剪贴板 | <%* tR = await tp.system.clipboard(); -%> |
✅ |
tp.system.executeCommandWithOutput() |
执行 shell 命令并返回输出 | <% tp.system.executeCommandWithOutput('echo "Hello"') %> |
❌ |
| tp.user.scriptName() | 单个导出函数 | <% tp.user.myFunction(tp) %> | ❌ |
| tp.user.scriptName.func() | 多个导出函数 | <% tp.user.myModule.greet(tp, 'World') %> | ❌ |
| tp.obsidian | 访问 Obsidian 原生 API | <% tp.obsidian.app.vault.getFiles() %> | ❌ |
| tp.obsidian.request() | 发送 HTTP 请求 | <% tp.obsidian.request({url: 'https://api.example.com'}) %> | ✅ |
| tp.obsidian.requestUrl() | 发送 HTTP 请求并返回响应文本 | <% tp.obsidian.requestUrl('https://api.example.com') %> | ✅ |
| tp.static | 访问静态函数 | <% tp.static.date.now('YYYY-MM-DD') %> | ❌ |
工作流示例
复杂工作流示例
以下是一个结合多个模板和条件逻辑的复杂工作流示例:
<%*
// 步骤 1: 收集用户输入
const projectType = await tp.system.suggester(
['个人项目', '团队项目', '客户项目'],
['personal', 'team', 'client']
);
const projectName = await tp.system.prompt('项目名称:', '', true);
const hasDeadline = await tp.system.prompt('有截止日期吗?', 'yes', true, false).toLowerCase() === 'yes';
let deadline = '';
if (hasDeadline) {
deadline = await tp.system.prompt('截止日期 (YYYY-MM-DD):');
}
// 步骤 2: 创建项目文件夹
const folderPath = `Projects/${projectType}/${projectName}`;
const folder = app.vault.getAbstractFileByPath(folderPath);
if (!folder) {
await app.vault.createFolder(folderPath);
}
// 步骤 4: 创建项目文件
const projectContent = `---
title: ${projectName}
type: ${projectType}
date: ${tp.date.now('YYYY-MM-DD')}
${deadline ? `deadline: ${deadline}` : ''}
status: planning
---
# ${projectName}
## 概述
## 任务
- [ ] 项目初始化
- [ ] 需求分析
- [ ] 设计阶段
- [ ] 开发阶段
- [ ] 测试阶段
- [ ] 交付
## 资源
## 备注
`;
await tp.file.create_new(projectContent, `${projectName}.md`, true, folderPath);
// 步骤 5: 创建会议记录模板
const meetingContent = `---
title: ${projectName} - 会议记录
date: <% tp.date.now('YYYY-MM-DD HH:mm:ss') %>
type: meeting
project: ${projectName}
---
# ${projectName} - 会议记录
## 参会人员
-
## 议程
-
## 讨论内容
## 行动项
- [ ]
## 下次会议
`;
await tp.file.create_new(meetingContent, `${projectName} - 会议记录模板.md`, false, `${folderPath}/Meeting Notes`);
// 步骤 5: 显示完成消息
new app.Notice(`项目 ${projectName} 创建完成!`, 5000);
%>
注意:当 template 参数为字符串且包含 <% %> 时,Templater 会二次执行里面的模板语法。这是预期行为,用于动态模板嵌套,但如果模板内容来自用户输入或外部源,可能导致意外执行。
推荐:对于复杂模板,建议使用 TFile 对象作为 template 参数(例如 tp.file.find_tfile('Templates/Meeting Note.md')),以避免嵌套执行的潜在风险。
性能优化
在使用 Templater 时,请注意以下性能最佳实践:
- 避免重复计算:将重复使用的值存储在变量中
- 限制网络请求:减少 HTTP 请求的频率,避免在模板中进行过多的网络调用
- 优化文件操作:批量处理文件操作,避免频繁读写
- 使用异步操作:对于耗时操作,使用
await确保顺序执行 - 简化模板逻辑:将复杂逻辑移至用户脚本中
- 使用缓存:对于重复使用的外部数据,考虑使用缓存
性能优化具体建议
避免在循环中进行文件I/O操作
文件I/O操作是耗时的操作,应避免在循环中重复执行:
<%*
// 不推荐的做法
const items = ['item1', 'item2', 'item3'];
for (const item of items) {
// 每次循环都进行文件操作
await tp.file.create_new(`Content for ${item}`, `${item}.md`);
}
// 推荐的做法
const items = ['item1', 'item2', 'item3'];
// 先准备所有内容
const contents = items.map(item => `Content for ${item}`);
// 批量创建文件
for (let i = 0; i < items.length; i++) {
await tp.file.create_new(contents[i], `${items[i]}.md`);
}
%>
限制HTTP请求频率
减少HTTP请求的频率,避免在模板中进行过多的网络调用:
<%*
// 不推荐的做法
const urls = ['https://api.example.com/data1', 'https://api.example.com/data2'];
const results = [];
for (const url of urls) {
// 每次循环都发送HTTP请求
const response = await tp.obsidian.request({ url });
results.push(response);
}
// 推荐的做法
// 考虑使用批量API端点
const response = await tp.obsidian.request({
url: 'https://api.example.com/batch',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ endpoints: ['data1', 'data2'] })
});
%>
避免大型模板中的复杂计算
将复杂计算移至用户脚本中,简化模板逻辑:
<%*
// 不推荐的做法
// 在模板中进行复杂计算
const data = await tp.obsidian.request({ url: 'https://api.example.com/data' });
const processedData = data.map(item => {
// 复杂的处理逻辑
return {
id: item.id,
value: item.value * 2,
status: item.value > 100 ? 'high' : 'low'
};
});
// 推荐的做法
// 在用户脚本中进行复杂计算
const processedData = await tp.user.processData(tp, 'https://api.example.com/data');
%>
批量操作优于多次单操作
使用批量操作替代多次单操作,减少API调用次数:
<%*
// 推荐的做法:在一个 processFrontMatter 回调中完成所有修改
await app.fileManager.processFrontMatter(tp.file.find_tfile(tp.file.path(true)), (fm) => {
fm.status = 'active';
fm.priority = 'high';
fm.tags = ['project', 'important'];
});
%>
参考 API 分类(v2.18.1+)
tp.file
create_new:创建新文件rename:重命名当前文件move:移动当前文件cursor:放置光标位置include:包含其他文件内容find_tfile:通过路径获取 TFile 对象path:获取文件路径folder:获取文件夹路径title:获取文件标题creation_date:获取文件创建日期last_modified_date:获取文件最后修改日期selection:获取当前选择的文本exists:检查文件是否存在cursor_append:在光标位置追加内容
tp.date
now:获取当前日期时间yesterday:获取昨天的日期tomorrow:获取明天的日期weekday:获取特定星期几的日期month:月份操作
tp.system
prompt:用户输入提示suggester:下拉选择提示multi_suggester:多选下拉选择clipboard:读取剪贴板executeCommandWithOutput:执行 shell 命令并返回输出(慎用)
tp.obsidian
request:发送 HTTP 请求requestUrl:发送 HTTP 请求并返回响应文本app:访问 Obsidian 应用实例(全局 app 等价)
tp.hooks
on_all_templates_executed:所有模板执行完后运行on_template_executed:单个模板执行完后运行
tp.config
args:外部传递的参数run_mode:运行模式template_file:当前模板文件target_file:目标文件
tp.frontmatter
- 只读对象,用于访问文件的 frontmatter 属性(如
.title,.tags等)
tp.static
- 静态访问 Templater 功能(用于回调函数)
参考资料
- Templater 官方文档(英文)
- Templater GitHub 仓库(英文)
- Obsidian 论坛 - Templater(英文)
中文资源
- Obsidian 中文社区 - 包含 Templater 相关讨论
- Templater 中文教程 - 知乎上的 Templater 教程
- Obsidian 插件使用指南 - 中文插件使用指南
- Templater中文社区讨论组 - 专注于Templater的中文讨论
- Templater插件配置视频教程 - B站上的Templater配置教程
- Obsidian模板高级技巧 - B站上的模板高级使用技巧
术语对照
| 英文 | 中文 |
|---|---|
| Templater | Templater(插件名,保留英文) |
| Template | 模板 |
| Variable | 变量 |
| Function | 函数 |
| Control Flow | 控制流 |
| Frontmatter | Frontmatter (YAML 属性) |
| User Scripts | 用户脚本 |
| API | 应用程序接口 |
| Async/Await | 异步/等待 |
| Console | 控制台 |
| Vault | 保险库 |
| Obsidian | Obsidian(保留英文) |
| Execute Command | 执行命令 |
| Output Command | 输出命令 |