Plugin: CodeScript Toolkit

(formerly known as Fix Require Modules)

This plugin is meant for the developers and other code writers. If you find the purpose of the plugin is not clear enough, I would be more than welcome to accept the PR to improve the README

8 Likes

Started porting my Templater scripts and User Plugins js scripts over to be used by your plugin today.

Reasons for doing so:

  • Templater’s annoying ‘Create’ and ‘Insert’ duplications. I always want to use ‘Insert’ and have to double check which one to fire when script is not among those added a key combo (running out of combos on Windows too) or not pinned on Command Palette (I don’t like it too long).
    • I also was in the habit of naming ‘Template’ my Templater scripts that are not even Templates – what a stupid thing to do – enough is enough…
  • User Plugins have some quirks and bugs I am tired of working around or unable to do so.
  • Too lazy to compile my own plugins and I certainly don’t want to take on 20-30 extra plugins when they are scripts doing usually simple enough custom stuff only.

On a personal note, Mikhael is one of my most liked developers, not only for his intuitive solutions, but his all-around availability.

I recommend checking out his other actively maintained plugins as well.

Cheers

3 Likes

It is possible to avoid duplication between Create and Insert using one template with according check

Not sure I follow you. A conditional? How would Templater know what I would want to use the script as? Never heard of this.

This is how I differentiate them:
{48FFE2A1-C1EC-41F7-9033-E04FC643F717}
So I can see the ones with the (not yet learnt shortcut), that they are the Insert ones.

You are right. I confused with another thing I used. Sorry

1 Like

I was not very keen to use Templater as I didn’t want to take on a liability and Obsidian API is enough.
But I was still trying to find a way to use the Templater functionality.
Once I managed to find a solution with Claude (VSCode Copilot), I had it write up a guide.

@mnaoumov Please comment if you find anything wrong in it:

Guide: Adding Templater Functionality to TypeScript Scripts

Key Components:
  1. Interface Definition

Here’s a comprehensive list of available Templater functions we can add to the interface:

interface TemplaterPlugin {
    templater: {
        current_functions_object: {
            app: any;
            date: {
                now: (format: string) => Promise<string>;
                tomorrow: (format?: string) => Promise<string>;
                yesterday: (format?: string) => Promise<string>;
                weekday: (format?: string, weekday?: number) => Promise<string>;
            };
            file: {
                path: (full?: boolean) => string;
                folder: (full?: boolean) => string;
                title: string;
                selection: () => string;
                tags: string[];
                creation_date: (format?: string) => Promise<string>;
                last_modified_date: (format?: string) => Promise<string>;
                move: (path: string) => Promise<void>;
                rename: (newName: string) => Promise<void>;
                find_tfile: (path: string) => TFile | null;
                include: (path: string) => Promise<string>;
                cursor: (order?: number) => number;
                cursor_append: (content: string) => void;
                exists: (path: string) => boolean;
                create_new: (template: string, filename: string, open?: boolean) => Promise<void>;
            };
            frontmatter: {
                [key: string]: any;
            };
            system: {
                clipboard: () => Promise<string>;
                prompt: (prompt_text: string, default_value?: string, throw_on_cancel?: boolean) => Promise<string>;
                suggester: <T>(
                    text_items: ((item: T) => string) | string[],
                    items: T[],
                    throw_on_cancel?: boolean,
                    placeholder?: string,
                    limit?: boolean
                ) => Promise<T>;
            };
            web: {
                daily_quote: () => Promise<string>;
                random_picture: () => Promise<string>;
                request: (url: string) => Promise<any>;
            };
            obsidian: {
                // Access to Obsidian API
                [key: string]: any;
            };
            user: {
                // Custom user scripts
                [key: string]: any;
            };
        };
    };
}

Key Functions Available:

  1. Date Functions:

    • now() - Current date/time
    • tomorrow() - Tomorrow’s date
    • yesterday() - Yesterday’s date
    • weekday() - Get specific weekday
  2. File Operations:

    • path() - Get file path
    • title - Get file title
    • selection() - Get selected text
    • move() - Move file
    • rename() - Rename file
    • create_new() - Create new file
  3. System Interactions:

    • clipboard() - Get clipboard content
    • prompt() - Show user prompt
    • suggester() - Show selection menu
  4. Web Functions:

    • daily_quote() - Get daily quote
    • random_picture() - Get random picture
    • request() - Make web requests
  5. Meta Functions:

    • Access to frontmatter
    • Access to Obsidian API
    • Custom user scripts

Example Usage:

// Get user input
const userInput = await tp.system.prompt("Enter value");

// Use suggester for selection
const selected = await tp.system.suggester(
    items => items.display,
    items
);

// Include another template
const content = await tp.file.include("path/to/template.md");

// Create new file from template
await tp.file.create_new("template.md", "New File", true);
  1. Accessing Templater
const templaterPlugin = app.plugins.plugins['templater-obsidian'] as any;
const tp = templaterPlugin?.templater?.current_functions_object;

// Check availability
if (!tp?.date?.now) {
    console.error("Required Templater functions not found!");
    return;
}
Common Templater Functions:
// Dates
const getDate = async () => await tp.date.now("YYYY-MM-DD[T]HH:mm");

// Files
const getCurrentFile = () => tp.file.find_tfile(tp.file.path(true));

// System
const userInput = await tp.system.prompt("Enter value");
const selected = await tp.system.suggester(
    items => items.display, 
    items
);
Best Practices:
  1. Always wrap in error handling:
try {
    const date = await tp.date.now(format);
} catch (error) {
    console.error("Templater error:", error);
    // Provide fallback
    return moment().format(format);
}
  1. Check for plugin availability:
if (!app.plugins.plugins['templater-obsidian']) {
    console.error("Templater plugin not found!");
    return;
}
  1. Use Promise wrapper for mobile compatibility:
return new Promise<void>(async (resolve) => {
    try {
        // Templater code here
    } catch (error) {
        console.error('Error:', error);
    } finally {
        resolve();
    }
});
Example Template:
import { App, Plugin, TFile } from 'obsidian';

interface TemplaterPlugin {
    templater: {
        current_functions_object: {
            // Add needed functions
        };
    };
}

async function yourFunction(app: App): Promise<void> {
    return new Promise<void>(async (resolve) => {
        try {
            const templaterPlugin = app.plugins.plugins['templater-obsidian'] as any;
            const tp = templaterPlugin?.templater?.current_functions_object;
            
            if (!tp) {
                console.error("Templater not found!");
                return resolve();
            }

            // Your code using tp functions

        } catch (error) {
            console.error('Error:', error);
        } finally {
            resolve();
        }
    });
}

// ...existing Plugin class and invoke function...
Common Gotchas:

Templater functions are async - use await
Functions might be unavailable - always check
Mobile needs Promise wrapper
Use fallbacks for critical functions

1 Like

@Yurcee there are many places that I would consider wrong here

  1. Typings of TemplaterPlugin are not correct. I will provide the correct typings in a bit
  2. You are not using those typings later on
const templaterPlugin = app.plugins.plugins['templater-obsidian'] as TemplaterPlugin;
  1. I would not recommend using TemplaterPlugin outside of Templater. For example, it won’t be initialized if you try to use it before any templates were running.

  2. Use Promise wrapper for mobile compatibility is an anti-pattern, it’s quite bad to use async inside Promise constructor

The correct way is

const asyncFn = async () => {
  // ...
};
const promise = asyncFn();
return promise;
1 Like
import * as obsidian from 'obsidian';
import {
  App,
  TFile,
  TFolder,
} from 'obsidian';

enum RunMode {
  CreateNewFromTemplate,
  AppendActiveFile,
  OverwriteFile,
  OverwriteActiveFile,
  DynamicProcessor,
  StartupTemplate,
}

interface TemplaterPlugin {
  templater: {
    current_functions_object: {
      app: App;
      config: {
        active_file: TFile;
        run_mode: RunMode;
        target_file: TFile;
        template_file: TFile;
      };
      date: {
        // default format is 'YYYY-MM-DD'
        now(format?: string, offset?: number | string, reference?: string, reference_format?: string): string;
        tomorrow(format?: string): string;
        weekday(format?: string, weekday?: number, reference?: string, reference_format?: string): string;
        yesterday(format?: string): string;
      };
      file: {
        content: string;
        tags: string[];
        title: string;
        create_new(template: TFile | string, filename?: string, open_new?: boolean, folder?: TFolder | string): Promise<TFile | undefined>;
        // default format is 'YYYY-MM-DD HH:mm'
        creation_date(format?: string): string;
        cursor(order?: number): string;
        cursor_append(content: string): string | undefined;
        exists(filepath: string): Promise<boolean>;
        find_tfile(filename: string): TFile | null;
        folder(absolute?: boolean): string;
        include(include_link: string | TFile): Promise<string>;
        // default format is 'YYYY-MM-DD HH:mm'
        last_modified_date(format?: string): string;
        move(new_path: string, file_to_move?: TFile): Promise<string>;
        path(relative: boolean): string;
        rename(new_title: string): Promise<string>;
        selection(): string;
      };
      frontmatter: Record<string, unknown>;
      hooks: {
        on_all_templates_executed(callback_function: () => unknown): void;
      };
      obsidian: typeof obsidian;
      system: {
        clipboard(): Promise<string | null>;
        prompt(prompt_text?: string, default_value?: string, throw_on_cancel?: boolean, multiline?: boolean): Promise<string | null>;
        suggester<T>(text_items: string[] | ((item: T) => string), items: T[], throw_on_cancel?: boolean, placeholder?: string, limit?: number): Promise<T>;
      };
      user: Record<string, unknown>;
      web: {
        daily_quote(): Promise<string>;
        random_picture(size?: string, query?: string, include_size?: boolean): Promise<string>;
        request(url: string, path?: string): Promise<string>;
      };
    };
  };
}
2 Likes

Yeah, that’s why an empty Templater template is advisable to run for this purpose on startup. I mean even for normal js scripts, for Templater function availability.

Then I’ll need to fix that for mobile compatibility even for scripts I didn’t use Templater for (so far only one and I kept the non-Templater version ts file for backup). That’s important, yes, even for Templater js.

Thanks very much for commenting on this on short notice.

Cheers

1 Like

As far as I can see, currently, only one startup script is allowed:

Would be better (if it’s possbile) if we just specified the folder and anything in that folder would be loaded on startup.
(My example scripts on the screenshot are not actual startup files – I just added two random files there.)

Yes, you have only one startup script, but you can call from it all others

startup/main.ts

import { invoke as freezeDataView } from './Freeze DataView.ts';
import { invoke as redoLastCommand } from './RedoLastCommand.ts';

export async function invoke(app: App): Promise<void> {
  await freezeDataView(app);
  await redoLastCommand(app);
}
1 Like

Will do, thanks!

This way, you will have a better control over the execution order.

And additionally, this forum is not the best place for the BUG/FEATURE REQUESTS. For better maintainability, they are better be added to GitHub

1 Like

Right.
Was just trying to raise awareness of the plugin a bit more.

Cheers

This plugin needs way more eyes on it, I feel. You can literally make Obsidian do anything you want with it. Plus the developer is so responsive on GitHub; literally every feature I’ve wanted has been added[1].


  1. And very quickly from the times I’ve posted the feature requests, too. ↩︎

3 Likes

I’ve used this plugin to replace QuickAdd, Templater, Dataview, and Advanced URI for myself so far. And also used it to copy some functions from Supercharged Links into it so I could use them to get Supercharged Links styling on links in some plugin views that are unsupported by Supercharged Links. It’s amazing.

2 Likes

Indeed, Dataview basically works on top of the Obsidian metadatacache and not friendly with string (context) searches. I stopped using dv.io.load a long time ago and use Obsidian API only (which the 2025 bots know about or can scour the docs) and basically made my own search script with any prop filter without Dataview.
It’s just that average users cannot go down this path and I speak from experience that even when you give them a complete tutorial how to do something, they do not even answer. I mean I hardly ever use the built-in search of Obsidian anymore.
Every single time a more complex search is to be entered, you’re gonna check the Help how to do it and you cannot even see what you are adding in that small bar…? Wow, I mean it’s 2025…

So again, thanks to the developer I have 50 custom scripts with modals for my own PKM.

1 Like

Ah, interesting. The direction I went was moving all inline [key:: value] pairs to frontmatter[1] and editing my scripts that add inline (key:: value) pairs to instead add the properties to frontmatter/YAML[2] and then add a more human readable text “inline”[3].

So, now I can use the regular Obsidian search.

[Spent (dollars)] restaurant

  1. Programmatically, of course. ↩︎

  2. Via app.metadataCache.getFileCache and app.fileManager.processFrontMatter. ↩︎

  3. Just to the content of the note. ↩︎

To some extent, I think I can grasp how to replace Templater with this plugin. Is the migration from dataview simple? I use dataview predominantly to manage tasks and render dashboard elements. I’ve been holding out for Datacore, but if this plugin can replace the dataview plugin without a whole lot of work, I’ll just migrate to using only this.

I guess that depends on your proficiency with TypeScript or JavaScript… It took me a couple weeks to replace everything and I definitely didn’t re-make all of Dataview’s things; just my own uses of it/DataviewJS… Had to make various scripts and functions… Here’s my script that renders a table in markdown, for example. CodeScript Toolkit’s code-button blocks in raw mode are great.

async function renderTable(container, thisFile, heading, cols, rows) {
	const wrapper = document.createElement('div');
	wrapper.style.marginBottom = '1.5em';
	if (heading.length) await MarkdownRenderer.render(app, heading, wrapper, thisFile.path, null);

	const md = [
		`| ${cols.join(' | ')} |`,
		`| ${cols.map(() => '---').join(' | ')} |`,
		...rows.map(r => `| ${r.join(' | ')} |`)
	].join('\n');

	const tableDiv = document.createElement('div');
	await MarkdownRenderer.render(app, md, tableDiv, thisFile.path, null);
	wrapper.appendChild(tableDiv);
	container?.appendChild(wrapper);
}
exports.renderTable = renderTable;
1 Like