obsidian-plugin-templater
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 | 输出命令 |
More from family3253/skill
multi-search-engine
Multi search engine integration with 17 engines (8 CN + 9 Global). Supports advanced search operators, time filters, site search, privacy engines, and WolframAlpha knowledge queries. No API keys required.
21opencode-agent-creator
Expert guidance for creating, configuring, and refining OpenCode agents. Use when working with agent files, authoring new agents, improving existing agents, or understanding agent structure and best practices. Use PROACTIVELY when user mentions creating agents, configuring tools, setting permissions, or agent architecture.
7add-skill
Wrapper skill for the add-skill CLI. Installs skills from arbitrary GitHub repos for OpenCode/Claude/Codex.
5gog
Google Workspace CLI for Gmail, Calendar, Drive, Contacts, Sheets, and Docs.
3superpowers-verification-before-completion
Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
3task-status
Send short status descriptions in chat for long-running tasks. Use when you need to provide periodic updates during multi-step operations, confirm task completion, or notify of failures. Includes automated periodic monitoring that sends updates every 5 seconds, status message templates, and a helper function for consistent status reporting.
3