
📖 Documentation: metaedit.obsidian.guide - guides for every feature, the full API reference, a cookbook of workflows, and troubleshooting.
#tag in place or edit a frontmatter tags: list (see Editing tags)This plugin is in the community plugin browser in Obsidian. Search for MetaEdit and you can install it from there.
Community Plugins inside Obsidian. There is a folder icon on the right of Installed Plugins. Click that and it opens your plugins folder.main.js file, manifest.json file, and a styles.css file.https://user-images.githubusercontent.com/29108628/119513092-3223e000-bd74-11eb-9060-3e0cae4dbef3.mp4
Run MetaEdit on a note and it lists that note's tags from both homes:
#tags show up as #tag rows. Selecting one lets you:#draft -> #published). The rest of the line, and any other occurrence of the same tag, are left untouched.#area/old -> #area/new.#tag:value data syntax. This choice is made per edit and never leaks into later edits.tags: show up as the tags property. Editing it strips a leading # you type, accepts a list / single value / comma- or space-separated string, stores the canonical #-free YAML list, and removes the key entirely when you clear the last tag.What MetaEdit deliberately leaves to Obsidian (verified against Obsidian 1.12.7):
#tags (it could not target them safely).https://user-images.githubusercontent.com/29108628/121333246-ebf48200-c918-11eb-889b-23b9a80299b2.mp4
You can access the API by using app.plugins.plugins["metaedit"].api.
I recommend destructuring the API, like so:
const {autoprop} = this.app.plugins.plugins["metaedit"].api;
autoprop(propertyName: string)Takes a string containing a property name. Looks for the property in user settings and will open a prompt with the possible values for that property (and its description, if set).
Returns the selected value: a string for a Single property, or a string[] for a Multi property. If nothing was selected, or the property was not found / Auto Properties are disabled, it returns null.
For a Multi property used in a template, join the array yourself, e.g.
<% (await autoprop("Tags"))?.join(", ") %>.
This is an asynchronous function, so you should await it.
update(propertyName: string, propertyValue: unknown, file: TFile | string)Updates a property with the given name to the given value in the given file.
If the file is a string, it should be the file path. Otherwise, a TFile is fine.
This is an asynchronous function, so you should await it.
update changes an existing property. If you want to create the property when it is missing, use addOrUpdateProperty.
When updating inline Dataview fields, non-string values are stringified. YAML frontmatter properties can preserve richer YAML values such as numbers, booleans, arrays, and objects.
update is replace-by-design: when a note has several inline name:: value lines with the same name, it rewrites all of them to the new value. To add a new instance instead and leave the existing ones untouched, use appendDataviewField.
When the named property is a body #tag, update renames that one occurrence in place (update("#topic", "science", file) writes #science, not #topic/science). The value is normalized to a valid tag (a leading # is optional) and an invalid name - one with spaces, commas, or punctuation Obsidian would not index as a single tag - is rejected rather than written.
createYamlProperty(propertyName: string, propertyValue: unknown, file: TFile | string)Creates a YAML frontmatter property in the given file.
If the file already has a property with the same name, MetaEdit leaves it unchanged.
This is an asynchronous function, so you should await it.
addOrUpdateProperty(propertyName: string, propertyValue: unknown, file: TFile | string)Updates an existing property with the given name, or creates a YAML frontmatter property when the property does not exist.
This is an asynchronous function, so you should await it.
appendDataviewField(propertyName: string, propertyValue: unknown, file: TFile | string, options?: { location?: "afterLastMatch" | "end" })Adds a new inline name:: value Dataview field instance to the body of the note, leaving any existing fields with the same name unchanged. This is the add-an-instance counterpart to update, which replaces every existing instance.
The field is never inserted into YAML frontmatter or a fenced code block. Non-string values are stringified (arrays are joined with ,).
options.location controls placement:
- "afterLastMatch" (default): right after the last existing name:: line; if there is none, at the end of the note body.
- "end": always at the end of the note body.
If the file is a string, it should be the file path. Otherwise, a TFile is fine.
This is an asynchronous function, so you should await it.
For example, a Dataview/Templater wishlist that appends a new pick without disturbing earlier ones:
const {appendDataviewField} = this.app.plugins.plugins["metaedit"].api;
await appendDataviewField("watch", "[[Dune: Part Two]]", tp.file.path);
getPropertyValue(propertyName: string, file: TFile | string)Gets the value of the given property in the given file.
If the file is a string, it should be the file path. Otherwise, a TFile is fine.
This is an asynchronous function, so you should await it.
getPropertiesInFile(file: TFile | string)Gets all metadata properties MetaEdit can read from the given file, including tags, YAML frontmatter properties, and inline Dataview fields.
This is an asynchronous function, so you should await it.
getFilesWithProperty(propertyName: string)Gets all markdown files with a YAML frontmatter property matching the given name.
getAutoProperties()Gets a copy of MetaEdit's configured Auto Properties.
The returned array is a copy, so mutating it will not change MetaEdit settings. Use setAutoProperties to save changes.
setAutoProperties(autoProperties: AutoProperty[])Replaces MetaEdit's configured Auto Properties and saves settings.
Each Auto Property must have a string name and a choices array of strings.
This is an asynchronous function, so you should await it.
onMetadataChange(callback)Registers a metadata-change listener and returns an unsubscribe function.
The callback receives { file, data, cache, properties, previousProperties }. properties contains the current properties parsed by MetaEdit for the file. previousProperties contains the last property snapshot emitted by this subscription for that file, or null when no previous snapshot is available.
MetaEdit does not classify changes as add, rename, value change, or remove, because Obsidian's metadata event does not provide a stable semantic diff. Compare previousProperties and properties in your callback when you need that detail.
Call the returned function when your plugin unloads, or register it with Obsidian's cleanup system:
const unsubscribe = app.plugins.plugins["metaedit"].api.onMetadataChange((change) => {
console.log(change.file.path, change.properties);
});
this.register(unsubscribe);
<%*
const {autoprop} = this.app.plugins.plugins["metaedit"].api;
_%>
#tasks
Complete:: 0
Project::
Status:: <% await autoprop("Status") %>
Priority:: <% await autoprop("Priority") %>
Due Date::
Complete:: 0
Energy::
Estimated Time::
Total:: 1
Complete:: 0
Incomplete:: 1
---
- [ ] <% tp.file.cursor() %>

Requires Dataview and Buttons.
```dataviewjs
const {update} = this.app.plugins.plugins["metaedit"].api
const {createButton} = app.plugins.plugins["buttons"]
dv.table(["Name", "Status", "Project", "Due Date", ""], dv.pages("#tasks")
.sort(t => t["due-date"], 'desc')
.where(t => t.status != "Completed")
.map(t => [t.file.link, t.status, t.project, t["due-date"],
createButton({app, el: this.container, args: {name: "Done!"}, clickOverride: {click: update, params: ['Status', 'Completed', t.file.path]}})])
)
```

Requires Dataview.
```dataviewjs
const {update} = this.app.plugins.plugins["metaedit"].api;
const buttonMaker = (pn, pv, fpath) => {
const btn = this.container.createEl('button', {"text": "Done!"});
const file = this.app.vault.getAbstractFileByPath(fpath)
btn.addEventListener('click', async (evt) => {
evt.preventDefault();
await update(pn, pv, file);
});
return btn;
}
dv.table(["Name", "Status", "Project", "Due Date", ""], dv.pages("#tasks")
.sort(t => t["due-date"], 'desc')
.where(t => t.status != "Completed")
.map(t => [t.file.link, t.status, t.project, t["due-date"],
buttonMaker('Status', 'Completed', t.file.path)])
)
```

Made by Christian B. B. Houmann Discord: Chhrriissyy#6548 Twitter: https://twitter.com/chrisbbh Feel free to @ me if you have any questions.
Also from dev: NoteTweet: Post tweets directly from Obsidian.
$ claude mcp add MetaEdit \
-- python -m otcore.mcp_server <graph>