### 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.
`,
}));
```
--------------------------------
### 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
```
--------------------------------
### 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.