### TypeScript Global Module Declaration Source: https://docs.sillytavern.app/for-contributors/writing-extensions Add a TypeScript .d.ts module declaration to your extension's root directory for global type autocompletion. This example covers both user-scoped and server-scoped extension types. ```typescript export {}; // 1. Import for user-scoped extensions import '../../../../public/global'; // 2. Import for server-scoped extensions import '../../../../global'; // Define additional types if needed... declare global { // Add global type declarations here } ``` -------------------------------- ### Initialize Extension Settings Source: https://docs.sillytavern.app/for-contributors/writing-extensions Always provide default settings and handle missing keys to ensure proper initialization, especially after updates. This merges default settings with existing ones, accommodating new keys. ```javascript function loadSettings() { // Merge with defaults to handle new keys after updates and initialize if it doesn't exist. extensionSettings[MODULE_NAME] = SillyTavern.libs.lodash.merge( structuredClone(defaultSettings), extensionSettings[MODULE_NAME] ); } ``` -------------------------------- ### Register New Macros with Arguments and Categories Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use `macros.register()` to add new macros. Supports descriptions, categories, and defining arguments for structured input. Handlers run synchronously and cannot return Promises. ```javascript const { macros } = SillyTavern.getContext(); // Simple macro with a handler function macros.register('tomorrow', { description: 'Returns tomorrow\'s date', handler: () => { return new Date(Date.now() + 24 * 60 * 60 * 1000).toLocaleDateString(); }, }); // Macro with unnamed arguments and a category macros.register('greet', { description: 'Generates a greeting for the given name', category: macros.category.UTILITY, unnamedArgs: [ { name: 'name', description: 'The name to greet' }, ], handler: ({ unnamedArgs }) => { const [name] = unnamedArgs; return `Hello, ${name}!`; }, }); ``` -------------------------------- ### Configure Internationalization via Manifest Source: https://docs.sillytavern.app/for-contributors/writing-extensions Specify supported locales and their corresponding translation file paths in the extension's manifest file for automatic loading. ```json { "display_name": "Foobar", "js": "index.js", // rest of the fields "i18n": { "fr-fr": "i18n/french.json", "de-de": "i18n/german.json" } } ``` -------------------------------- ### Provide Console Feedback Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use consistent prefixes for console messages to help identify logs from your extension. Use `console.log`, `console.debug`, and `console.error` appropriately, but avoid excessive logging in production. ```javascript const MODULE_NAME = 'MyExtension'; console.log(`[${MODULE_NAME}] Extension loaded`); console.debug(`[${MODULE_NAME}] Processing data:`, data); console.error(`[${MODULE_NAME}] Error occurred:`, error); ``` -------------------------------- ### Create and Show Custom Popup Source: https://docs.sillytavern.app/for-contributors/writing-extensions Instantiate and display a custom popup with advanced options like custom buttons, inputs, and styling. Handles user interaction results and custom input values. ```javascript const { Popup, POPUP_TYPE, POPUP_RESULT } = SillyTavern.getContext(); const popup = new Popup( '
Custom HTML content here
', POPUP_TYPE.TEXT, '', { wide: true, // Wide display mode okButton: 'Save', // Custom OK button text cancelButton: 'Discard', // Custom Cancel button text customButtons: [ { text: 'Export', icon: 'fa-download', result: POPUP_RESULT.CUSTOM1, }, ], customInputs: [ { id: 'my_checkbox', label: 'Enable feature', type: 'checkbox', defaultState: false, }, ], allowVerticalScrolling: true, } ); const result = await popup.show(); if (result === POPUP_RESULT.AFFIRMATIVE) { // OK was clicked } else if (result === POPUP_RESULT.CUSTOM1) { // Export button was clicked } // Read custom input values const checkboxValue = popup.inputResults?.get('my_checkbox'); ``` -------------------------------- ### Show Basic Blocking Loader with Stoppable Toast Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use the `loader.show()` function to display a loading overlay and toast notification. The returned handle allows hiding the loader. The default `toastMode` is 'stoppable'. ```javascript const { loader } = SillyTavern.getContext(); // Basic blocking loader with a stoppable toast const handle = loader.show({ message: 'Processing data...' }); try { const result = await someExpensiveOperation(); } finally { await handle.hide(); } ``` -------------------------------- ### Use Async/Await for I/O Operations Source: https://docs.sillytavern.app/for-contributors/writing-extensions For I/O operations and heavy computations, use `async/await` to avoid blocking the UI thread. For very heavy computations, break them into smaller chunks and yield to the UI periodically. ```javascript // Use async for I/O operations async function processData() { const result = await fetch('/api/process'); return result.json(); } // Break up heavy computations async function heavyComputation(data) { for (let i = 0; i < data.length; i++) { // Process chunk if (i % 1000 === 0) { await new Promise(resolve => setTimeout(resolve, 0)); // Yield to UI } } } ``` -------------------------------- ### Listen to SillyTavern Events Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use `eventSource.on()` to subscribe to application events. Ensure you import `eventSource` and `event_types` from `SillyTavern.getContext()`. The `handleIncomingMessage` function is a placeholder for your custom event logic. ```javascript const { eventSource, event_types } = SillyTavern.getContext(); eventSource.on(event_types.MESSAGE_RECEIVED, handleIncomingMessage); function handleIncomingMessage(data) { // Handle message } ``` -------------------------------- ### Use Unique Module Names Source: https://docs.sillytavern.app/for-contributors/writing-extensions Employ a descriptive and unique module name for your extension to prevent naming conflicts with other extensions. Avoid generic names like 'settings'. ```javascript // GOOD - Specific and unique const MODULE_NAME = 'my_extension_name'; // BAD - Too generic, likely to conflict const MODULE_NAME = 'settings'; ``` -------------------------------- ### SillyTavern Extension Manifest Source: https://docs.sillytavern.app/for-contributors/writing-extensions Defines the metadata and entry points for a SillyTavern UI extension. Ensure this file is named manifest.json within your extension's directory. ```json { "display_name": "The name of the extension", "loading_order": 1, "requires": [], "optional": [], "dependencies": [], "js": "index.js", "css": "style.css", "author": "Your name", "version": "1.0.0", "homePage": "https://github.com/your/extension", "auto_update": true, "minimum_client_version": "1.0.0", "i18n": { "de-de": "i18n/de-de.json" }, "hooks": { "install": "onInstall", "update": "onUpdate", "delete": "onDelete", "enable": "onEnable", "disable": "onDisable", "activate": "onActivate" } } ``` -------------------------------- ### Configure Prompt Interceptor in manifest.json Source: https://docs.sillytavern.app/for-contributors/writing-extensions Define a prompt interceptor by specifying the `generate_interceptor` field in your extension's `manifest.json`. This field should contain the name of the global JavaScript function that SillyTavern will call. The `loading_order` property controls the execution sequence of interceptors. ```json { "display_name": "My Interceptor Extension", "loading_order": 10, // Affects execution order "generate_interceptor": "myCustomInterceptorFunction", // ... other manifest properties } ``` -------------------------------- ### Show Input Popup Source: https://docs.sillytavern.app/for-contributors/writing-extensions Opens a popup with a text input field. Returns the user's input string or `null` if cancelled. ```javascript const { Popup } = SillyTavern.getContext(); // Text input dialog — returns the entered string, or null if cancelled const userInput = await Popup.show.input('Enter Name', 'Please provide a name:', 'default value'); ``` -------------------------------- ### Show Information Popup Source: https://docs.sillytavern.app/for-contributors/writing-extensions Displays a simple informational message to the user. Returns the result of the clicked button. ```javascript const { Popup } = SillyTavern.getContext(); // Information display — returns the clicked button result await Popup.show.text('Info', 'Operation completed successfully.'); ``` -------------------------------- ### Manage Settings Presets with PresetManager Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use the PresetManager to read and write custom data within SillyTavern's settings presets. This is useful for storing extension-specific configurations. ```javascript const { getPresetManager } = SillyTavern.getContext(); // Get the preset manager for the current API type const pm = getPresetManager(); // Write data to the preset extension field: // - path: the path to the field in the preset data // - value: the value to write // - name (optional): the name of the preset to write to, defaults to the currently selected preset await pm.writePresetExtensionField({ path: 'hello', value: 'world' }); // Read data from the preset extension field: // - path: the path to the field in the preset data // - name (optional): the name of the preset to read from, defaults to the currently selected preset const value = pm.readPresetExtensionField({ path: 'hello' }); ``` -------------------------------- ### Prefer SillyTavern Context API Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use `SillyTavern.getContext()` to access core functionalities like chat, characters, and save settings. This API is more stable and less prone to breaking with SillyTavern updates compared to direct imports. ```javascript // GOOD - Stable API const { chat, characters, saveSettingsDebounced } = SillyTavern.getContext(); // AVOID - May break with internal changes import { chat, characters } from '../../../../script.js'; ``` -------------------------------- ### Register a Function Tool for LLM Invocation Source: https://docs.sillytavern.app/for-contributors/writing-extensions Register custom function tools using `registerFunctionTool()` to allow the LLM to invoke them. Define the tool's name, description, parameters using JSON schema, and an `action` function to execute. ```javascript const { registerFunctionTool } = SillyTavern.getContext(); registerFunctionTool({ name: 'get_weather', displayName: 'Get Weather', description: 'Get the current weather for a given location', parameters: { $schema: 'http://json-schema.org/draft-04/schema#', type: 'object', properties: { location: { type: 'string', description: 'City name' }, }, required: ['location'], }, action: async ({ location }) => { const data = await fetchWeatherData(location); return JSON.stringify(data); }, }); ``` -------------------------------- ### Show Non-blocking Loader Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use this to display a loader for background tasks without freezing the UI. The `onStop` callback can be used to handle cancellation. ```javascript const { loader } = SillyTavern.getContext(); const handle = loader.show({ blocking: false, message: 'Downloading in background...', onStop: () => abortDownload(), }); ``` -------------------------------- ### Call SillyTavern Extras API Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use `doExtrasFetch` to make requests to your SillyTavern Extras API server. This function automatically adds necessary headers and handles fetching. Specify the request method, headers, and body as needed. ```javascript import { getApiUrl, doExtrasFetch } from "../../extensions.js"; const url = new URL(getApiUrl()); url.pathname = '/api/summarize'; const apiResult = await doExtrasFetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Bypass-Tunnel-Reminder': 'bypass', }, body: JSON.stringify({ // Request body }) }); ``` -------------------------------- ### Generate Structured Outputs with JSON Schema Source: https://docs.sillytavern.app/for-contributors/writing-extensions Enable structured outputs by providing a `jsonSchema` to `generateRaw` or `generateQuietPrompt`. This ensures the model generates a JSON object conforming to the schema, useful for extensions requiring structured data. Note that the output is a stringified JSON and requires manual parsing and validation. ```javascript const { generateRaw, generateQuietPrompt } = SillyTavern.getContext(); // Define a JSON schema for the expected output const jsonSchema = { // Required: a name for the schema name: 'StoryStateModel', // Optional: a description of the schema description: 'A schema for a story state with location, plans, and memories.', // Optional: the schema will be used in strict mode, meaning that only the fields defined in the schema will be allowed strict: true, // Required: a definition of the schema value: { '$schema': 'http://json-schema.org/draft-04/schema#', 'type': 'object', 'properties': { 'location': { 'type': 'string' }, 'plans': { 'type': 'string' }, 'memories': { 'type': 'string' } }, 'required': [ 'location', 'plans', 'memories' ], }, }; const prompt = 'Generate a story state with location, plans, and memories. Output as a JSON object.'; const rawResult = await generateRaw({ prompt, jsonSchema, }); const quietResult = await generateQuietPrompt({ quietPrompt: prompt, jsonSchema, }); ``` -------------------------------- ### Stacking Multiple Loaders Source: https://docs.sillytavern.app/for-contributors/writing-extensions Multiple loaders can be active simultaneously. The overlay remains visible as long as at least one blocking loader is active. Each loader must be hidden individually. ```javascript const { loader } = SillyTavern.getContext(); const loader1 = loader.show({ message: 'Task 1...' }); const loader2 = loader.show({ message: 'Task 2...' }); await loader1.hide(); // Overlay stays — loader2 is still active await loader2.hide(); // Now overlay hides ``` -------------------------------- ### Emit SillyTavern Events Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use `eventSource.emit()` to trigger application events, including custom ones. You can pass arbitrary data as additional arguments. Use `await` to ensure all handlers complete before proceeding. ```javascript const { eventSource } = SillyTavern.getContext(); // Can be a built-in event_types field or any string. const eventType = 'myCustomEvent'; // Use `await` to ensure all event handlers complete before continuing execution. await eventSource.emit(eventType, { data: 'custom event data' }); ``` -------------------------------- ### Accessing SillyTavern Context Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use the `getContext()` function from the global `SillyTavern` object to access application state, utilities, and functions. The chat log is mutable. ```javascript const context = SillyTavern.getContext(); context.chat; // Chat log - MUTABLE context.characters; // Character list context.characterId; // Index of the current character context.groups; // Group list context.groupId; // ID of the current group // And many more... ``` -------------------------------- ### Implement Extension Lifecycle Hook Functions Source: https://docs.sillytavern.app/for-contributors/writing-extensions Export hook functions from your extension's main JS entry point. These functions can be asynchronous and are called at specific points in the extension's lifecycle. Note that hooks have a 5-second timeout. ```javascript // index.js - your extension's entry point export async function onInstall() { console.log('Extension installed! Performing first-time setup...'); // e.g., initialize default data, create storage entries } export async function onActivate() { console.log('Extension activated during page load'); } export async function onUpdate() { console.log('Extension updated! Running migrations...'); // e.g., migrate data from old format to new format } export async function onDelete() { console.log('Extension about to be deleted. Cleaning up...'); // e.g., remove stored data, clean up localStorage const { localforage } = SillyTavern.libs; await localforage.removeItem('my_extension_data'); } export function onEnable() { console.log('Extension enabled'); } export function onDisable() { console.log('Extension disabled'); } ``` -------------------------------- ### Generate Reply Using External JS Import Source: https://docs.sillytavern.app/for-contributors/writing-extensions Import functions from other JavaScript files within your extension to generate content, such as a reply from the background API. Note that direct imports from SillyTavern's internal modules are discouraged due to potential breakage. ```javascript import { generateQuietPrompt } from "../../../../script.js"; async function handleMessage(data) { const text = data.message; const translated = await generateQuietPrompt({ quietPrompt: text }); // ... } ``` -------------------------------- ### Register Slash Commands with Extended Details Source: https://docs.sillytavern.app/for-contributors/writing-extensions Register new slash commands using `SlashCommandParser.addCommandObject` to provide detailed information for autocomplete and help features. This is the recommended method over `registerSlashCommand`. ```javascript SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'repeat', callback: (namedArgs, unnamedArgs) => { return Array(namedArgs.times ?? 5) .fill(unnamedArgs.toString()) .join(isTrueBoolean(namedArgs.space.toString()) ? ' ' : '') ; }, aliases: ['example-command'], returns: 'the repeated text', namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'times', description: 'number of times to repeat the text', typeList: ARGUMENT_TYPE.NUMBER, defaultValue: '5', }), SlashCommandNamedArgument.fromProps({ name: 'space', description: 'whether to separate the texts with a space', typeList: ARGUMENT_TYPE.BOOLEAN, defaultValue: 'off', enumList: ['on', 'off'], }), ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'the text to repeat', typeList: ARGUMENT_TYPE.STRING, isRequired: true, }), ], helpString: `
Repeats the provided text a number of times.
Example:
`, })); ``` -------------------------------- ### Configure Extension Lifecycle Hooks in manifest.json Source: https://docs.sillytavern.app/for-contributors/writing-extensions Map hook names to exported function names within the 'hooks' object in your extension's manifest.json file. ```json { "display_name": "My Extension", "js": "index.js", // Other fields here... "hooks": { "install": "onInstall", "update": "onUpdate", "delete": "onDelete", "enable": "onEnable", "disable": "onDisable", "activate": "onActivate" } } ``` -------------------------------- ### Avoid Large Data in Extension Settings Source: https://docs.sillytavern.app/for-contributors/writing-extensions Do not store large datasets in `extensionSettings` as they are loaded into memory and saved frequently, potentially causing performance issues. Use `localforage` or `localStorage` for storing data. ```javascript // BAD - Don't store large data extensionSettings[MODULE_NAME].largeDataset = { /* megabytes of data */ }; // GOOD - Use localforage (abstraction over IndexedDB/localStorage) const { localforage } = SillyTavern.libs; await localforage.setItem(`${MODULE_NAME}_data`, largeData); // Or use localStorage for smaller data localStorage.setItem(`${MODULE_NAME}_data`, JSON.stringify(smallData)); ``` -------------------------------- ### Register Data Bank Scraper Source: https://docs.sillytavern.app/for-contributors/writing-extensions Register a custom scraper to import data into the Data Bank from external sources. Requires defining an ID, name, description, icon, availability check, and the scraping logic. ```javascript const { registerDataBankScraper } = SillyTavern.getContext(); await registerDataBankScraper({ id: 'my_scraper', name: 'My Data Source', description: 'Import data from My Data Source', iconClass: 'fa-solid fa-database', iconAvailable: true, isAvailable: async () => true, scrape: async () => { // Return an array of File objects const content = await fetchDataFromSource(); return [new File([content], 'data.txt', { type: 'text/plain' })]; }, }); ``` -------------------------------- ### Persist Extension Settings Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use `extensionSettings` to store and retrieve JSON-serializable data for your extension. Call `saveSettingsDebounced()` to persist changes. Ensure unique keys to avoid conflicts and consider initializing default settings. ```javascript const { extensionSettings, saveSettingsDebounced } = SillyTavern.getContext(); // Define a unique identifier for your extension const MODULE_NAME = 'my_extension'; // Define default settings const defaultSettings = Object.freeze({ enabled: false, option1: 'default', option2: 5 }); // Define a function to get or initialize settings function getSettings() { // Initialize settings if they don't exist if (!extensionSettings[MODULE_NAME]) { extensionSettings[MODULE_NAME] = structuredClone(defaultSettings); } // Ensure all default keys exist (helpful after updates) for (const key of Object.keys(defaultSettings)) { if (!Object.hasOwn(extensionSettings[MODULE_NAME], key)) { extensionSettings[MODULE_NAME][key] = defaultSettings[key]; } } return extensionSettings[MODULE_NAME]; } // Use the settings const settings = getSettings(); settings.option1 = 'new value'; // Save the settings saveSettingsDebounced(); ``` -------------------------------- ### Generate Text in Quiet Chat Context Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use `generateQuietPrompt` for background text generation without UI interruption. It preserves chat context and is suitable for tasks like summarization or image prompt creation. ```javascript const { generateQuietPrompt } = SillyTavern.getContext(); const quietPrompt = 'Generate a summary of the chat history.'; const result = await generateQuietPrompt({ quietPrompt, }); ``` -------------------------------- ### Generate Text with Raw Prompt Control Source: https://docs.sillytavern.app/for-contributors/writing-extensions Utilize `generateRaw` for complete control over prompt construction, bypassing chat context. It supports Text Completion and Chat Completion modes, and can accept system prompts and prefill text. ```javascript const { generateRaw } = SillyTavern.getContext(); const systemPrompt = 'You are a helpful assistant.'; const prompt = 'Generate a story about a brave knight.'; const prefill = 'Once upon a time,'; /* In Chat Completion mode, will produce a prompt like this: [ {role: 'system', content: 'You are a helpful assistant.'}, {role: 'user', content: 'Generate a story about a brave knight.'}, {role: 'assistant', content: 'Once upon a time,'} ] */ /* In Text Completion mode (no instruct), will produce a prompt like this: "You are a helpful assistant.\nGenerate a story about a brave knight.\nOnce upon a time," */ const result = await generateRaw({ systemPrompt, prompt, prefill, }); ``` -------------------------------- ### Import Wrapper for Webpack Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use this utility function to import members from modules by URL, bypassing webpack's bundling. It includes error handling for failed imports. ```javascript /** * Import a member from a module by URL, bypassing webpack. * @param {string} url URL to import from * @param {string} what Name of the member to import * @param {any} defaultValue Fallback value * @returns {Promise} Imported member */ export async function importFromUrl(url, what, defaultValue = null) { try { const module = await import(/* webpackIgnore: true */ url); if (!Object.hasOwn(module, what)) { throw new Error(`No ${what} in module`); } return module[what]; } catch (error) { console.error(`Failed to import ${what} from ${url}: ${error}`); return defaultValue; } } // Import a function from 'script.js' module const generateRaw = await importFromUrl('/script.js', 'generateRaw'); ``` -------------------------------- ### Show Confirmation Popup Source: https://docs.sillytavern.app/for-contributors/writing-extensions Displays a confirmation dialog to the user. Returns `POPUP_RESULT.AFFIRMATIVE` or `POPUP_RESULT.NEGATIVE`. ```javascript const { Popup } = SillyTavern.getContext(); // Confirmation dialog — returns POPUP_RESULT.AFFIRMATIVE or POPUP_RESULT.NEGATIVE const confirmed = await Popup.show.confirm('Confirm Action', 'Are you sure you want to proceed?'); ``` -------------------------------- ### Render Handlebars HTML Templates Asynchronously Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use `renderExtensionTemplateAsync` from `getContext` to render Handlebars HTML templates. The function automatically sanitizes output and adds localization attributes. Append the returned HTML to your extension's settings panel. ```javascript const { renderExtensionTemplateAsync } = SillyTavern.getContext(); // Renders 'third-party/my-extension/settings.html' with the given data const settingsHtml = await renderExtensionTemplateAsync( 'third-party/my-extension', 'settings', { title: 'My Extension', version: '1.0', defaultValue: 'test' } ); // Append to the extensions settings panel $('#extensions_settings2').append(settingsHtml); ``` ```html
{{title}}
``` -------------------------------- ### Register Legacy Macro Source: https://docs.sillytavern.app/for-contributors/writing-extensions The legacy `registerMacro()` function is deprecated. It supports simple string replacements or synchronous function handlers. Use `macros.register()` for new development. ```javascript const { registerMacro, unregisterMacro } = SillyTavern.getContext(); // Simple string macro registerMacro('fizz', 'buzz'); // Function macro (must be synchronous) registerMacro('tomorrow', () => { return new Date(Date.now() + 24 * 60 * 60 * 1000).toLocaleDateString(); }); // Unregister unregisterMacro('fizz'); ``` -------------------------------- ### Avoid Storing Secrets in Extension Settings Source: https://docs.sillytavern.app/for-contributors/writing-extensions Never store sensitive data like API keys in `extensionSettings` as they are stored in plain text and accessible to all extensions. Use server plugins for secure handling of sensitive data. ```javascript // BAD - Don't do this! extensionSettings[MODULE_NAME].apiKey = 'secret_key_123'; // NOTE: There is no secure way to store secrets in client-side extensions. // If you need to handle sensitive data, use server plugins instead. // See: https://docs.sillytavern.app/for-contributors/server-plugins/ ``` -------------------------------- ### Use DOMPurify for HTML Sanitization Source: https://docs.sillytavern.app/for-contributors/writing-extensions Access the DOMPurify library from SillyTavern's shared libs to sanitize potentially unsafe HTML content before rendering. ```javascript const { DOMPurify } = SillyTavern.libs; const sanitizedHtml = DOMPurify.sanitize(''); ``` -------------------------------- ### Register Macro Alias Source: https://docs.sillytavern.app/for-contributors/writing-extensions Create an alias for an existing macro using `macros.registerAlias()`. The `visible` option controls whether the alias appears in the UI. ```javascript const { macros } = SillyTavern.getContext(); macros.registerAlias('greet', 'hello', { visible: true }); ``` -------------------------------- ### Register Debug Function Source: https://docs.sillytavern.app/for-contributors/writing-extensions Add custom functions to the Debug Menu for diagnostics or manual triggers. Requires a unique ID, display name, description, and an async function to execute. ```javascript const { registerDebugFunction } = SillyTavern.getContext(); registerDebugFunction( 'my_ext_clear_cache', 'Clear My Extension Cache', 'Clears all cached data for My Extension', async () => { const { localforage } = SillyTavern.libs; await localforage.removeItem('my_extension_cache'); toastr.success('Cache cleared'); } ); ``` -------------------------------- ### Sanitize User Inputs with DOMPurify Source: https://docs.sillytavern.app/for-contributors/writing-extensions Always validate and sanitize user inputs before using them to prevent security risks. Use `DOMPurify` from `SillyTavern.libs` to sanitize HTML input. ```javascript // Validate input type first if (typeof userInput !== 'string') { toastr.error('Invalid input type'); return; } // Use DOMPurify to sanitize HTML input const { DOMPurify } = SillyTavern.libs; const cleanInput = DOMPurify.sanitize(userInput); ``` -------------------------------- ### Manage Chat Metadata Source: https://docs.sillytavern.app/for-contributors/writing-extensions Associate arbitrary data with the current chat using `chatMetadata`. Use `saveMetadata()` to persist changes. Avoid storing long-lived references to `chatMetadata` as it changes between chats. ```javascript const { chatMetadata, saveMetadata } = SillyTavern.getContext(); // Set some metadata for the current chat chatMetadata['my_key'] = 'my_value'; // Get the metadata for the current chat const value = chatMetadata['my_key']; // Save the metadata to the server await saveMetadata(); ``` -------------------------------- ### Implement a Generation Interceptor Function Source: https://docs.sillytavern.app/for-contributors/writing-extensions Define a global interceptor function to modify chat history before text generation. This function can be asynchronous and receives chat history, context size, an abort signal, and the generation type. ```javascript globalThis.myCustomInterceptorFunction = async function(chat, contextSize, abort, type) { // Example: Add a system note before the last user message const systemNote = { is_user: false, name: "System Note", send_date: Date.now(), mes: "This was added by my extension!" }; // Insert before the last message chat.splice(chat.length - 1, 0, systemNote); } ``` -------------------------------- ### Add Locale Data Directly Source: https://docs.sillytavern.app/for-contributors/writing-extensions Manually add translation data for specific locales using `addLocaleData`. Ensure the locale code is valid and existing keys are not overridden. ```javascript SillyTavern.getContext().addLocaleData('fr-fr', { 'Hello': 'Bonjour' }); SillyTavern.getContext().addLocaleData('de-de', { 'Hello': 'Hallo' }); ``` -------------------------------- ### Send Toast Notifications Source: https://docs.sillytavern.app/for-contributors/writing-extensions Utilize the globally available `toastr` object for displaying non-intrusive feedback messages like success, error, warning, or info. ```javascript toastr.success('Data saved successfully'); toastr.error('Failed to connect to API'); toastr.warning('This feature is experimental'); toastr.info('Processing...'); ``` -------------------------------- ### Unregister a Macro Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use `macros.registry.unregisterMacro()` to remove a previously registered macro. This is the recommended method for unregistering macros. ```javascript const { macros } = SillyTavern.getContext(); macros.registry.unregisterMacro('greet'); ``` -------------------------------- ### Write Data to Character Cards Source: https://docs.sillytavern.app/for-contributors/writing-extensions Use `writeExtensionField` to store JSON-serializable data within a character card's extension data field. The `characterId` is an index, not a unique identifier, and can be undefined for group chats or when no character is selected. ```javascript const { writeExtensionField, characterId } = SillyTavern.getContext(); // Write some data to the character card await writeExtensionField(characterId, 'my_extension_key', { someData: 'value', anotherData: 42 }); // Read the data back from the character card const character = SillyTavern.getContext().characters[characterId]; // The data is stored in the `extensions` object of the character's data const myData = character.data?.extensions?.my_extension_key; ``` -------------------------------- ### Clean Up Event Listeners Source: https://docs.sillytavern.app/for-contributors/writing-extensions Prevent memory leaks by removing event listeners when they are no longer needed. This includes listeners for custom events and DOM events. ```javascript function cleanup() { eventSource.removeListener(event_types.MESSAGE_RECEIVED, handleMessage); document.getElementById('myElement').removeEventListener('click', handleClick); } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.