> ## Documentation Index
> Fetch the complete documentation index at: https://dripart-docs-custom-nodes-sdk-v2-frontend.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript settings and storage API

> Pack settings and user-scoped persistent storage.

<Note>
  This page is generated from the authoritative declaration file. Do not edit it by hand. Contract SHA-256: <code>152c7fab547f</code>.
</Note>

This reference contains 7 exported declarations: `SettingValue`, `SettingDef`, `SettingOption`, `SettingAttrs`, `SettingsHandle`, `StorageUsage`, `StorageHandle`.

Download the complete contract from the docs source: [TypeScript declaration](https://github.com/Comfy-Org/docs/blob/main/public/custom-nodes-sdk/v2/comfy-api.d.ts).

## Contract

```typescript theme={null}
// ─── settingsHandle.ts ───────────────────────────────────────────

/** @knipIgnoreUnusedButUsedByCustomNodes */
export type SettingValue = string | number | boolean | readonly string[]

/** @knipIgnoreUnusedButUsedByCustomNodes */
export interface SettingDef {
  /**
   * Namespaced, by convention `<Pack>.<name>` — it shares one space with core
   * and every other pack, and it is what the value is stored under forever.
   */
  readonly id: string
  readonly name: string
  /**
   * Which control the panel shows. Every one of these is declarative — the
   * host renders it.
   *
   * A pack-supplied renderer is deliberately absent. Core's own setting type
   * accepts a function that is handed the value and a setter and returns an
   * element; publishing that would put packs in charge of the settings
   * panel's markup, which is the thing that cannot then be restyled. Packs
   * that needed a colour or a file were falling back to a text field the user
   * pasted into, so the gap was the missing *types*, not a missing slot.
   */
  readonly type:
    | 'boolean'
    | 'number'
    | 'slider'
    | 'knob'
    | 'combo'
    | 'radio'
    | 'text'
    | 'password'
    | 'color'
    | 'image'
    | 'url'
  readonly defaultValue: SettingValue
  readonly tooltip?: string
  /** Panel grouping. Defaults to the id split on dots. */
  readonly category?: readonly string[]
  /**
   * Choices for `combo` and `radio`.
   *
   * A bare string is both the stored value and the label. Use the pair form
   * when they differ — several packs store a semantic number and show words
   * for it (`0` = off, `1` = selected, `2` = all), and comparing those
   * numerically is the whole point. Flattening them to strings silently
   * re-types every user's saved choice.
   */
  readonly options?: readonly SettingOption[]
  /**
   * Bounds for `number` and `slider`. Without these a slider has no range to
   * draw and packs fall back to a plain text box.
   */
  readonly attrs?: SettingAttrs
  readonly onChange?: (value: SettingValue, previous?: SettingValue) => void
}

/** @knipIgnoreUnusedButUsedByCustomNodes */
export type SettingOption =
  | string
  | { readonly value: string | number; readonly label: string }

/** @knipIgnoreUnusedButUsedByCustomNodes */
export interface SettingAttrs {
  readonly min?: number
  readonly max?: number
  readonly step?: number
}

export interface SettingsHandle {
  /**
   * Registers a setting. Call once, at extension load: a value already stored
   * for this id survives, so re-declaring cannot reset a user's choice.
   */
  declare(def: SettingDef): void
  get<T extends SettingValue = SettingValue>(id: string): T | undefined
  set(id: string, value: SettingValue): Promise<void>
  /**
   * Watches a setting, including one the pack did not declare.
   *
   * `declare`'s own `onChange` only fires for settings the pack owns, so a
   * pack that needs to react to a *core* preference — colour palette, link
   * render mode, locale — had nothing to observe and polled or ignored it.
   *
   * Fires on change only, not on registration. Returns a function that stops
   * watching; call it from wherever the pack tears down.
   */
  onChange<T extends SettingValue = SettingValue>(
    id: string,
    listener: (value: T | undefined, previous: T | undefined) => void
  ): Unsubscribe
}

// ─── storageHandle.ts ────────────────────────────────────────────

/** @knipIgnoreUnusedButUsedByCustomNodes */
export interface StorageUsage {
  /** Total bytes stored under the namespace. */
  readonly usedBytes: number
  /** How many entries make up {@link usedBytes}. */
  readonly entryCount: number
  /**
   * The ceiling this host enforces, or `undefined` where it enforces none.
   *
   * Undefined is the honest answer for a local install with the user's own
   * disk behind it, and it is deliberately not reported as `Infinity`: a pack
   * dividing by it to draw a gauge would get a meaningless bar rather than the
   * chance to skip drawing one. Do not treat a present number as a promise
   * that a write below it succeeds — another namespace shares the same store.
   */
  readonly quotaBytes?: number
}

export interface StorageHandle {
  /**
   * Names stored under a namespace, which must be one this pack owns.
   *
   * Empty when nothing has been stored yet — absence is not an error.
   */
  list(namespace: string): Promise<readonly string[]>
  /** The stored text, or `undefined` if there is none. */
  get(name: string): Promise<string | undefined>
  set(name: string, value: string): Promise<void>
  remove(name: string): Promise<void>
  /**
   * What a namespace currently occupies.
   *
   * For a pack that stores things a user accumulates — presets, captions,
   * saved prompts — so it can show what it is holding and offer to prune it,
   * rather than growing without bound until someone else's write fails.
   */
  usage(namespace: string): Promise<StorageUsage>
}
```
