Skip to content

Context: AI, network, storage, settings

interface AICompleteOptions {
maxTokens?: number;
systemPrompt?: string;
}
interface AIModel { id: string; name: string; }
complete(prompt: string, opts?: AICompleteOptions): Promise<string>;
stream(prompt: string, opts: AICompleteOptions, onToken: (token: string) => void): Promise<void>;
listModels(): Promise<AIModel[]>;

complete/stream/listModels all use the user’s own configured AI provider, model, and API key (whatever is set in Settings → AI) — a plugin cannot supply its own key or provider. See Trust model & security for what privacy mode does and does not gate here.

interface PluginFetchInit {
body?: string;
headers?: Record<string, string>;
method?: string;
}
interface PluginFetchResponse {
body: string;
headers: Record<string, string>;
status: number;
}
fetch(url: string, init?: PluginFetchInit): Promise<PluginFetchResponse>;

This is a Rust-side reqwest proxy (it bypasses the browser’s CORS restrictions), not the browser fetch. Only http/https URLs are allowed; the response body is always a UTF-8 string (binary responses are lossily decoded, not usable as bytes); duplicate response headers collapse to whichever value reqwest iterates last. See Trust model & security for the full egress and size/timeout policy.

read(key: string): Promise<string | null>;
write(key: string, value: string): Promise<void>;
list(): Promise<string[]>;
remove(key: string): Promise<void>;

A simple string key/value store, one directory per plugin. See Trust model & security for where it lives and its guarantees (or lack thereof).

getAll(): Record<string, boolean | number | string>;

The user’s answers to the fields your manifest declares in contributions.settings, rendered as a form in your plugin’s page under Settings → Plugins. One entry per declared field, always of the declared type; keys your current manifest does not declare are not returned.

Synchronous here and Promise-returning in the sandboxed tier — otherwise the same function, resolved the same way, so the same plugin source works in both. There is no setter in either tier: a value the user chose is not the plugin’s to move. Use context.storage for state of your own.

const { prefix = "»" } = context.settings.getAll();
context.events.on("settings:changed", () => {
rebuild(context.settings.getAll()); // the event carries no values
});

This one event is gated on settings, not on events, in both tiers — it carries no payload, and requiring events would make your install dialog claim you watch what the user does to their files. It is debounced, so a string field being typed into notifies you once the value settles rather than once per keystroke.

‼️ The settings object a tiptapExtensions factory receives is a snapshot taken when the plugin loads. The factory is not re-run, so read context.settings.getAll() when you need a current value.

interface PluginSettingField {
key: string;
label: string;
type: "boolean" | "color" | "enum" | "number" | "string";
default?: boolean | number | string;
description?: string; // one line under the label
min?: number; // number only, inclusive
max?: number; // number only, inclusive
options?: { value: string; label: string }[]; // enum only, required
}

color renders the app’s theme swatches beside a text input, and its value is whatever you would write in CSS — a hex value, rgb(…), a colour name, or a var(--token) that follows the user’s theme. enum renders a select over options.

A value outside min/max, or not among options, falls back the same way a type mismatch does: to your declared default, then to the type’s zero (an enum’s first option). A default your own field would refuse is an install-time error.

‼️ None of this relieves you of checking the value yourself. Constraints are resolved against your current manifest by whatever Baram the user is running — one older than a constraint resolves the field without it.

Disposable[] — every Disposable returned by commands.register, events.on, ui.showStatusBarItem, ui.addStyle, ui.addSidebarPanel, and ui.addSettingsTab is pushed here automatically and disposed when the plugin is unloaded (reload, remove, or app shutdown). You don’t need to track these yourself.