(formerly known as Fix Require Modules
, see Rebranding section for more details)
This is a plugin for Obsidian
that allows to do a lot of things with JavaScript
/TypeScript
scripts from inside the Obsidian
itself.
This plugin is for you if you want to:
- Write in any flavor of
JavaScript
/TypeScript
in:DevTools Console
withinObsidian
;CustomJS
scripts;datacorejs
/datacorejsx
/datacorets
/datacoretsx
scripts;dataviewjs
scripts;Modules
scripts;QuickAdd
scripts;Templater
scripts;- etc.
- Write modular scripts using modern
JavaScript
/TypeScript
syntax and patterns. - Prototype
Obsidian
plugins. - Explore
Obsidian
API (public and internal) in runtime easier. - Invoke any
JavaScript
/TypeScript
script via command or hotkey.
There are several very good plugins that allow to write JavaScript
/TypeScript
scripts for Obsidian
, but they all have their own limitations and quirks.
Most of those plugins support writing scripts in CommonJS
(cjs
) only, which is not so used nowadays.
None of those plugins provide you the developer experience as you would have in any other modern JavaScript
/TypeScript
development environment.
This plugin aims to erase the line between the Obsidian
world and the JavaScript
/TypeScript
development world.
The plugin adds the following functions to the global scope:
function require(id: string, options?: Partial<RequireOptions>);
async function requireAsync(id: string, options?: Partial<RequireOptions>): Promise<unknown>;
async function requireAsyncWrapper((requireFn: RequireAsyncWrapperArg)): Promise<unknown>;
interface RequireOptions {
cacheInvalidationMode: 'always' | 'never' | 'whenPossible';
moduleType?: ModuleType;
parentPath?: string;
}
type ModuleType = 'json' | 'jsTs' | 'md' | 'node' | 'wasm';
type RequireAsyncWrapperArg = (require: RequireExFn) => Promise<unknown> | unknown;
type RequireExFn = { parentPath?: string } & NodeJS.Require & RequireFn;
type RequireFn = (id: string, options?: Partial<RequireOptions>) => unknown;
Explanation of the options will be shown in the Features section.
Obsidian
on desktop has a built-in require()
function, but it is quite limited.
Obsidian
on mobile does not have it at all.
This plugin brings the advanced version of require()
to both desktop and mobile.
Combines all features of require()
and dynamic import()
.
All features brought by this plugin are available for it.
Wraps synchronous require()
calls in asynchronous ones.
It is useful when you want to use the synchronous require()
calls but some features are not available for it normally.
await requireAsyncWrapper((require) => {
require(anyFeature);
});
It is especially useful for migrating scripts you have for desktop to use on mobile, as you can see in the Features section, most of the features of require()
don't work on mobile.
For each of the feature, we provide a table showing whether the feature enabled on the platform: Desktop
or on Mobile
. And whether it works for require()
or for requireAsync()
(and requireAsyncWrapper()
).
Most of the examples below will be shown using require()
, but you can adjust the examples to use requireAsync()
or requireAsyncWrapper()
, as soon as the feature is enabled for your platform
Desktop | Mobile | |
---|---|---|
require() |
✅ | ✅ |
requireAsync() |
✅ | ✅ |
Certain Obsidian
built-in modules are available for import during plugin development but show Uncaught Error: Cannot find module
if you try to require()
them manually. This plugin fixes that problem, allowing the following require()
calls to work properly:
require('obsidian');
require('@codemirror/autocomplete');
require('@codemirror/collab');
require('@codemirror/commands');
require('@codemirror/language');
require('@codemirror/lint');
require('@codemirror/search');
require('@codemirror/state');
require('@codemirror/text');
require('@codemirror/view');
require('@lezer/common');
require('@lezer/lr');
require('@lezer/highlight');
Example usage:
const obsidian = require('obsidian');
new obsidian.Notice('My notice');
const { Notice } = require('obsidian');
new Notice('My notice');
Desktop | Mobile | |
---|---|---|
require() |
✅ | ✅ |
requireAsync() |
✅ | ✅ |
There is a global variable app
that gives access to Obsidian
App
instance.
However, starting from Obsidian
v1.3.5
this global variable is deprecated in the public API.
Starting from Obsidian
v1.6.6
this global variable was completely removed from the public API.
Currently this global variable is still available, but it's better not rely on it, as it is not guaranteed to be maintained.
This plugin gives you a safer alternative:
require('obsidian/app');
Desktop | Mobile | |
---|---|---|
require() |
✅ | ✅ |
requireAsync() |
✅ | ✅ |
You can access the list of all special Obsidian
module names that are made available by this plugin. This includes module names like obsidian
, @codemirror/view
, etc.
require('obsidian/specialModuleNames');
Desktop | Mobile | |
---|---|---|
require() |
✅ | ❌ |
requireAsync() |
✅ | ❌ |
Obsidian
on desktop is shipped with some additional modules that you can require()
.
// bundled with Electron app
require('electron');
require('electron/common');
require('electron/renderer');
// packed in `app.asar`
require('@electron/remote');
require('btime');
require('get-fonts');
Desktop | Mobile | |
---|---|---|
require() |
✅ | ❌ |
requireAsync() |
✅ | ✅ |
Fixes Cannot find module
errors for relative paths:
require('./some/relative/path.js');
require('../some/other/relative/path.js');
Optionally provide the path to the current script/note if detection fails. Submit an issue if needed:
require('./some/relative/path.js', { parentPath: 'path/to/current/script.js' });
require('./some/relative/path.js', { parentPath: 'path/to/current/note.md' });
Desktop | Mobile | |
---|---|---|
require() |
✅ | ❌ |
requireAsync() |
✅ | ✅ |
Adds support for root-relative paths:
require('/path/from/root.js');
The root /
folder is configurable via settings.
Desktop | Mobile | |
---|---|---|
require() |
✅ | ❌ |
requireAsync() |
✅ | ✅ |
On Linux and MacOS, the system root path is /path/from/system/root.js
.
In order to distinguish them from root-relative path, you need to prepend ~
to the path.
require('~/path/from/system/root.js');
Desktop | Mobile | |
---|---|---|
require() |
✅ | ❌ |
requireAsync() |
✅ | ✅ |
Adds support for vault-root-relative paths:
require('//path/from/vault/root.js');
Desktop | Mobile | |
---|---|---|
require() |
✅ | ❌ |
requireAsync() |
✅ | ✅ |
Originally, require()
only supported CommonJS
(cjs
) modules and would throw require() of ES Module path/to/script.mjs not supported. Instead change the require of path/to/script.mjs to a dynamic import() which is available in all CommonJS modules
. This plugin adds support for ECMAScript modules:
require('path/to/script.mjs');
Now you can use any type of JavaScript modules:
require('./path/to/script.js');
require('./path/to/script.cjs');
require('./path/to/script.mjs');
TypeScript
modules
Desktop | Mobile | |
---|---|---|
require() |
✅ | ❌ |
requireAsync() |
✅ | ✅ |
Adds support for TypeScript
modules:
require('./path/to/script.ts');
require('./path/to/script.cts');
require('./path/to/script.mts');
Warning
When the plugin loads a TypeScript
module, it strips all type annotations and convert the code into JavaScript
syntax.
The plugin will report an error only if the code is syntactically incorrect. No type-checking is performed, as it done by IDEs and/or compilers.
So you can potentially load some non-compilable TypeScript
module, and the plugin won't report any errors. You can get runtime errors when using the module.
It is advisable to validate your TypeScript
modules with external IDEs and/or compilers.
Example of such problematic module:
interface Foo {
bar: string;
}
export function printFoo(foo: Foo): void {
console.log(foo.barWithTypo); // this line would cause a compilation error in a regular IDE, but the plugin won't report any errors
}
The plugin just strips all type annotations and converts the code into JavaScript
:
export function printFoo(foo) {
console.log(foo.barWithTypo);
}
So when we execute within Obsidian
:
require('/FooModule.ts').printFoo({ bar: 'baz' });
we get undefined
instead of baz
.
Desktop | Mobile | |
---|---|---|
require() |
✅ | ❌ |
requireAsync() |
✅ | ✅ |
You can require NPM modules installed into your configured scripts root folder.
require('npm-package-name');
See Tips how to avoid performance issues.
Desktop | Mobile | |
---|---|---|
require() |
✅ | ❌ |
requireAsync() |
✅ | ❌ |
You can require Node built-in modules such as fs
with an optional prefix node:
.
require('fs');
require('node:fs');
Desktop | Mobile | |
---|---|---|
require() |
✅ | ✅ |
requireAsync() |
✅ | ✅ |
You can require JSON files.
require('./foo.json');
Desktop | Mobile | |
---|---|---|
require() |
✅ | ❌ |
requireAsync() |
✅ | ❌ |
You can require Node binaries .node
.
require('./foo.node');
Desktop | Mobile | |
---|---|---|
require() |
❌ | ❌ |
requireAsync() |
✅ | ✅ |
You can require WebAssembly binaries .wasm
.
await requireAsync('./foo.wasm');
Desktop | Mobile | |
---|---|---|
require() |
✅ | ❌ |
requireAsync() |
✅ | ❌ |
You can require content of .asar
files like if they were folders.
require('./foo.asar/bar.js');
Desktop | Mobile | |
---|---|---|
require() |
✅ | ✅ |
requireAsync() |
✅ | ✅ |
You can require content of .md
files from code-script
code blocks.
require('./foo.md'); // require the default script block
require('./foo.md?codeScriptName=bar'); // require the named script block
foo.md
```code-script
export function baz(): void {
}
```
```code-script
// codeScriptName: bar
export function qux(): void {
}
```
The first code-script
code block in the file is a default script block used when ?codeScriptName=...
part is not specified.
If the first line of the code-script
code block has special format // codeScriptName: ...
, this name can be used for query ?codeScriptName=...
.
You can customize behavior via frontmatter of the note.
---
codeScriptToolkit:
defaultCodeScriptName: foo
invocableCodeScriptName: bar
isInvocable: true
---
defaultCodeScriptName
- name of the code block to be used when?codeScriptName=...
part is not specified.invocableCodeScriptName
- name of the code block to be used when running the note via Invoke Scripts.isInvocable
- whether to add the current note into the list for Invoke Scripts commands.
Desktop | Mobile | |
---|---|---|
require() |
✅ | ✅ |
requireAsync() |
✅ | ✅ |
Module type is determined via file extension. You can override it if needed.
require('./actual-js-file.some-unknown-extension', { moduleType: 'jsTs' });
Possible values:
json
- JSON files.jsTs
- JavaScript/TypeScript files:.js
/.cjs
/.mjs
/.ts
/.cts
/.mts
.md
- Markdown files.node
- Node binaries.wasm
- WebAssembly (WASM).
Desktop | Mobile | |
---|---|---|
require() |
❌ | ❌ |
requireAsync() |
✅ | ✅ |
await requireAsync('https://some-site.com/some-script.js');
Module type is determined by Content-Type
header returned when you fetch the url.
In some cases the header is missing, incorrect or too generic like text/plain
or application/octet-stream
.
In those cases jsTs
module type is assumed, but it's recommended to specify it explicitly to avoid warnings.
await requireAsync('https://some-site.com/some-script.js', {
moduleType: 'jsTs'
});
Desktop | Mobile | |
---|---|---|
require() |
✅ | ❌ |
requireAsync() |
✅ | ✅ |
You can require files using file URLs:
require('file:///C:/path/to/vault/then/to/script.js');
Desktop | Mobile | |
---|---|---|
require() |
✅ | ❌ |
requireAsync() |
✅ | ✅ |
You can require files using resource URLs:
require(
'app://obsidian-resource-path-prefix/C:/path/to/vault/then/to/script.js'
);
See getResourcePath() and Platform.resourcePathPrefix for more details.
Desktop | Mobile | |
---|---|---|
require() |
❌ | ❌ |
requireAsync() |
✅ | ✅ |
// top-level-await.js
await Promise.resolve(); // top-level await
export const dep = 42;
// script.js
await requireAsync('./top-level-await.js');
Desktop | Mobile | |
---|---|---|
require() |
✅ | ✅ |
requireAsync() |
✅ | ✅ |
Modules are cached for performance, but the cache is invalidated if the script or its dependencies change.
You can also control cache invalidation mode:
require('./someScript.js', { cacheInvalidationMode: 'always' });
require('./someScript.js', { cacheInvalidationMode: 'never' });
require('./someScript.js', { cacheInvalidationMode: 'whenPossible' });
always
- always get the latest version of the module, ignoring the cached versionnever
- always use the cached version, ignoring the changes in the module, if anywhenPossible
- get the latest version of the module if possible, otherwise use the cached version
Also, you can use a query string to skip cache invalidation (except for URLs), which behaves as setting cacheInvalidationMode
to never
:
require('./someScript.js?someQuery'); // cacheInvalidationMode: 'never'
require('https://some-site.com/some-script.js?someQuery'); // cacheInvalidationMode: 'whenPossible'
Dynamic import()
Desktop | Mobile |
---|---|
✅* | ✅* |
Dynamic import()
was partially modified to be an alias to requireAsync()
.
However due to the technical limitations, it's not possible to extend dynamic import()
in all contexts.
It is fully extended in:
- External script files:
js
,cjs
,mjs
,ts
,cts
,mts
,md
. - Code buttons blocks.
It has original (not extended) behavior in:
DevTools Console
withinObsidian
;CustomJS
scripts;datacorejs
/datacorejsx
/datacorets
/datacoretsx
scripts;dataviewjs
scripts;Modules
scripts;QuickAdd
scripts;Templater
scripts;
So if you need fully functional variant, use requireAsync()
instead.
Desktop | Mobile |
---|---|
✅ | ✅ |
If you need to clear the require
cache, you can invoke the CodeScript Toolkit: Clear cache
command.
Desktop | Mobile |
---|---|
✅ | ✅ |
Manages source maps for compiled code, allowing seamless debugging in Obsidian
.
Desktop | Mobile |
---|---|
✅ | ✅ |
Make any script invocable by defining a module that exports a function named invoke
(sync or async) that accepts app
argument of App
type.
// cjs sync
exports.invoke = (app) => {
console.log('cjs sync');
};
// cjs async
exports.invoke = async (app) => {
console.log('cjs async');
await Promise.resolve();
};
// mjs sync
export function invoke(app) {
console.log('mjs sync');
}
// mjs async
export async function invoke(app) {
console.log('mjs async');
await Promise.resolve();
}
// cts sync
import type { App } from 'obsidian';
exports.invoke = (app: App): void => {
console.log('cts sync');
};
// cts async
import type { App } from 'obsidian';
exports.invoke = async (app: App): Promise<void> => {
console.log('cts async');
await Promise.resolve();
};
// mts sync
import type { App } from 'obsidian';
export function invoke(app: App): void {
console.log('mts sync');
}
// mts async
import type { App } from 'obsidian';
export async function invoke(app: App): Promise<void> {
console.log('mts async');
await Promise.resolve();
}
Desktop | Mobile |
---|---|
✅ | ✅ |
Configure a script folder so every script in it can be invoked using the Command Palette
. Use CodeScript Toolkit: Invoke script: <<Choose>>
for more predictable lists:
Desktop | Mobile |
---|---|
✅ | ✅ |
Invoke any script when Obsidian
loads via a configuration setting.
You can add an optional cleanup()
function to the startup script, which will be called when the plugin is unloaded.
The function has the same signature as invoke()
function.
import type { App } from 'obsidian';
export async function cleanup(app: App): Promise<void> {
// executes when the plugin is unloaded
}
export async function invoke(app: App): Promise<void> {
// executes when the plugin is loaded, including when the app is started
}
You can reload the startup script using the CodeScript Toolkit: Reload startup script
command.
Desktop | Mobile |
---|---|
✅ | ❌ |
Assign hotkeys to frequently used scripts:
Desktop | Mobile |
---|---|
✅ | ✅ |
Create code buttons that execute JavaScript
/TypeScript
:
```code-button
---
caption: Click me!
---
// CommonJS (cjs) style
const { dependency1 } = require('./path/to/script1.js');
// ES Modules (esm) style
import { dependency2 } from './path/to/script2.js';
// Top-level await
await Promise.resolve(42);
// TypeScript syntax
function myTypeScriptFn(arg: string): void {}
```
Warning
For code buttons to work properly, the plugin has to be able to uniquely identify the code button block to its source in the markdown note.
In some rare occasions, plugin fails to do so and will show an error instead of the button.
In this case, you need to slightly modify the code button source to ensure such source is unique in its markdown note.
Example:
> [!NOTE]
>
> ```code-button
> // Identical code button source
> ```
>
> ```code-button
> // Identical code button source
> ```
The plugin is unable to distinguish those two buttons and will show an error. But if you change at least one of them:
> [!NOTE]
>
> ```code-button
> // No longer identical code button source
> ```
>
> ```code-button
> ---
> someKey: someValue
> ---
> // No longer identical code button source
> ```
The buttons are no longer identical and the plugin can distinguish them now.
Code button config is a YAML
block at the beginning of the code block. Below shown an example of the full config with default values.
```code-button
---
caption: (no caption)
isRaw: false
removeAfterExecution:
shouldKeepGap: false
when: never
shouldAutoOutput: true
shouldAutoRun: false
shouldShowSystemMessages: true
shouldWrapConsole: true
---
// Code
```
The config block is optional and all keys are optional. If the config key is missing, the default value is used.
To simplify adding new button you can use command CodeScript Toolkit: Insert sample code button
.
See spec for all config keys.
During runtime execution of the code button block, the following variable is available codeButtonContext
.
The variable contains all metadata and helper functions available during runtime execution.
See spec for all possible values.
Desktop | Mobile |
---|---|
✅ | ✅ |
Code blocks intercept all calls to console.debug()
, console.error()
, console.info()
, console.log()
, console.warn()
and display them in the results panel.
```code-button
---
shouldWrapConsole: true # default
---
console.debug('debug message');
console.error('error message');
console.info('info message');
console.log('log message');
console.warn('warn message');
```
If you do not want to intercept console messages, you can set the shouldWrapConsole
setting to false
.
```code-button
---
shouldWrapConsole: false
---
// Code
```
See Code button config.
Desktop | Mobile |
---|---|
✅ | ✅ |
Code blocks automatically output the last evaluated expression like in REPL
environments, such as DevTools Console
.
```code-button
---
shouldAutoOutput: true # default
---
1 + 2;
3 + 4;
5 + 6; // this will be displayed in the results panel
```
To disable this feature, set the shouldAutoOutput
setting to false
.
```code-button
---
shouldAutoOutput: false
---
1 + 2;
3 + 4;
5 + 6; // this will NOT be displayed in the results panel
```
See Code button config.
Desktop | Mobile |
---|---|
✅ | ✅ |
Code blocks can be configured to run automatically when the note is opened using the shouldAutoRun
setting.
```code-button
---
shouldAutoRun: true
---
// Code
```
See Code button config.
Desktop | Mobile |
---|---|
✅ | ✅ |
Within code block you have access to the codeButtonContext.container
HTML element that wraps the results panel.
```code-button
codeButtonContext.container.createEl('button', { text: 'Click me!' });
```
See Code button context.
Desktop | Mobile |
---|---|
✅ | ✅ |
Within code block you have access to the codeButtonContext.renderMarkdown()
function that renders markdown in the results panel.
```code-button
await codeButtonContext.renderMarkdown('**foo**');
```
See Code button context.
Desktop | Mobile |
---|---|
✅ | ✅ |
Within code block you have access to the codeButtonContext.sourceFile
variable which represents the note file that contains the code block.
```code-button
console.log(codeButtonContext.sourceFile);
```
See Code button context.
Desktop | Mobile |
---|---|
✅ | ✅ |
Within code block you have access to the following functions that modify the containing note file:
```code-button
await codeButtonContext.insertAfterCodeButtonBlock('**foo**');
await codeButtonContext.insertBeforeCodeButtonBlock('**bar**');
await codeButtonContext.removeCodeBlock();
await codeButtonContext.replaceCodeBlock(**baz**);
```
See Code button context.
Desktop | Mobile |
---|---|
✅ | ✅ |
Code buttons in raw mode show only the output container. Button itself, console output, system messages are hidden.
```code-button
---
isRaw: true
---
await codeButtonContext.renderMarkdown('**foo**');
```
It implies the following full configuration:
---
isRaw: true
shouldAutoOutput: false
shouldAutoRun: true
shouldShowSystemMessages: false
shouldWrapConsole: false
---
See Code button config.
Desktop | Mobile |
---|---|
✅ | ✅ |
You can configure a button to remove itself after execution:
```code-button
---
removeAfterExecution:
shouldKeepGap: false
when: never
---
// Code
```
See Code button config.
Desktop | Mobile |
---|---|
✅ | ✅ |
This plugin allows you to create temporary plugins.
This is useful for quick plugin prototyping from inside the Obsidian
itself.
The key here is the function codeButtonContext.registerTempPlugin()
, which is available in the script scope.
```code-button
import { Plugin } from 'obsidian';
class MyPlugin extends Plugin {
onload() {
console.log('loading MyPlugin');
}
}
codeButtonContext.registerTempPlugin(MyPlugin);
```
The loaded temp plugins can be unloaded using the CodeScript Toolkit: Unload temp plugin: PluginName
/ CodeScript Toolkit: Unload temp plugins
commands.
Also all temp plugins are unloaded when current plugin is unloaded.
See Code button context.
Desktop | Mobile |
---|---|
✅ | ✅ |
Warning
This allows arbitrary code execution, which could pose a security risk. Use with caution. Disabled by default.
You can invoke script files or custom code using Obsidian URL schema.
All characters has to be properly URL-escaped, e.g., you have to replace ␣
(space) with %20
.
Opening URL
obsidian://CodeScriptToolkit?module=/foo/bar.ts&functionName=baz&args='arg1','arg%20with%20space2',42,app.vault,%7Bbaz%3A'qux'%7D
would be equivalent to calling
const module = await requireAsync('/foo/bar.ts');
await module.baz('arg1', 'arg2 with spaces', 42, app.vault, { baz: 'qux' });
If you omit args
parameter, it will be treated as no arguments.
If you omit functionName
parameter, it will treated as invoke
, which is useful for Invocable scripts.
Opening URL
obsidian://CodeScriptToolkit?code=await%20sleep(1000);%20console.log('foo%20bar')
would be equivalent to calling
await sleep(1000);
console.log('foo bar');
Desktop | Mobile |
---|---|
✅ | ✅ |
If you plan to use scripts extensively, consider putting them in a dot folder
, such as .scripts
within your vault. Obsidian
doesn't track changes within dot folders
and won't re-index your node_modules
folder repeatedly.
The plugin is available in the official Community Plugins repository.
To install the latest beta release of this plugin (regardless if it is available in the official Community Plugins repository or not), follow these steps:
- Ensure you have the BRAT plugin installed and enabled.
- Click Install via BRAT.
- An Obsidian pop-up window should appear. In the window, click the
Add plugin
button once and wait a few seconds for the plugin to install.
By default, debug messages for this plugin are hidden.
To show them, run the following command:
window.DEBUG.enable('fix-require-modules');
For more details, refer to the documentation.
This plugin was formerly known as Fix Require Modules
.
The plugin quickly overgrew its original purpose and got way more features than just fixing require()
calls. That's why it got a new name.
However, for the backward compatibility, the previous id fix-require-modules
is still used internally and you might find it
- in plugin folder name;
- in plugin URL;
- in Debugging section;