Nodes/comfyui-obvpm/Value Presets (obvpm)
ComfyUI Node

Value Presets (obvpm)

Swap a whole settings block with one dropdown

By chanon·Created about a month ago·Updated a day ago· 42
Value Presets (obvpm)
    • bundle
    schema# one field per line: name: type [range] [= default] # types: text | int | float | bool | choice a, b, c | @Node.input steps: int 1..200 = 20 cfg: float 0..100 = 5.0 sampler: @SamplerName (obvpm).sampler_name
    presetcustom
    values{}
    presets{}
    names

    Here's how most people do presets in ComfyUI: build a little subgraph of primitives holding steps, CFG, sampler, scheduler, seed, and copy the whole block once per variant. Now the structure is duplicated four times and only the values differ, with nothing holding the copies together. Rename a field in one and the other three don't follow - and a bundle whose keys quietly stopped matching can still unpack by position, into the wrong meanings.

    Value Presets is the sane version. One node holds the structure once and as many sets of values as you like, and you pick between them from a dropdown.

    How it works

    You describe the fields once, in the schema widget, as text, one field per line:

    # name: type [range] [= default]
    steps: int 1..200 = 20
    cfg: float 0..100 = 5.0
    sampler: @SamplerName (obvpm).sampler_name
    mode: choice fast, balanced, full = balanced
    

    Types are text, int, float, bool, choice a, b, c, and @Node.input - that last one borrows another node's dropdown rather than copying it. Point a field at @LoraName (obvpm).lora_name and it tracks your actual LoRA folder instead of a list frozen the day you wrote the preset. Lines starting with # are ignored, a default follows =, and there's a "edit schema…" button that gives you a row per field with name, type and default, arrow buttons to reorder, and a searchable type picker that now offers the basic types plus every dropdown on your install, grouped by node pack (this pack first, ComfyUI core next, everyone else after).

    Under the hood, the per-field controls are built in the browser and mirrored into one hidden values widget as a JSON blob. That's a deliberate choice: ordinary widgets serialise by position, so a schema-driven pile of them would renumber every stored value the moment you moved a line. One opaque channel keyed by name can't do that.

    What makes editing cheap

    Everything is keyed by name, nothing by position:

    • Add a field and every saved preset gains it at its default.
    • Remove one and the leftover value is ignored, not shifted onto whatever line came next.
    • Reorder the schema and nothing moves at all.
    • Rename a field in the schema editor and the stored value follows to the new name - in the node's own values and in every preset.

    That last one is what makes this worth using over a pile of primitives - and it's the plumbing layer's standing advice applied to a whole settings block: one source, fanned out.

    Inputs and outputs

    You'll set preset - which saved set is loaded, with custom meaning "whatever I set by hand". Selecting a preset writes its values into the controls and leaves them editable; change one and the node doesn't quietly detach the label - it keeps naming the preset and marks itself modified. Right call: the name is a label, the values are what run.

    values and presets are the hidden storages the UI owns; names mirrors the schema's field names so a downstream Unbundle traces this node exactly as it traces a Bundle. Leave all three alone.

    The one output - bundle - is an ordinary OBVPM_BUNDLE, keyed by name in schema order. So Unbundle works on it unchanged, and hiding fields in Unbundle's config dialog is how you take a subset for one branch. If you only need steps and cfg at the sampler and the rest elsewhere, that's a checkbox, not a second node.

    Limits and honesty about the schema

    A schema is text that arrives inside shared workflows, so it's parsed strictly and only ever as data - nothing is imported or evaluated from it. The documented exception: @Node.input references a class you already have installed, and reading its dropdown does run that class's INPUT_TYPES. Fields cap at 64, schema text at 64KB, presets at 1MB; you'll never meet those numbers, they exist so a malformed schema fails fast.

    Installing it

    cd ComfyUI/custom_nodes
    git clone https://github.com/chanon/comfyui-obvpm
    

    Restart ComfyUI, or install via Manager by searching comfyui-obvpm. No extra Python dependencies - the JS that draws the schema editor ships with the pack. The class id is ValuePresets (obvpm); search obvpm in the node menu to see the family.

    Common issues

    "Schema line N" errors. The parser quotes the offending line and says what it couldn't read. The most common cause is a = inside a choice list - a default also starts with =, so choice options can't contain one.

    Your presets are gone. They live in the node, which means they live in the workflow file. Hand someone the workflow and they get your presets; open a different workflow and you get that one's. There's no global preset store to fall back on.

    A borrowed dropdown is empty. @Node.input reads a live list from a class that must be installed and loaded - no LoRA folder, no lora names, whether or not you ever opened a loader node.

    A node from a workflow saved before 2026-09-13. 0.2.0 renamed every class id in this pack after a clash with another pack's Bundle node. Old workflows are migrated automatically on open; if the node still misbehaves, delete it and make a new one.

    Categoryobvpm/bundle

    Inputs (5)

    NameTypeDefaultDescription
    schemaSTRING# one field per line: name: type [range] [= default] # types: text | int | float | bool | choice a, b, c | @Node.input steps: int 1..200 = 20 cfg: float 0..100 = 5.0 sampler: @SamplerName (obvpm).sampler_name The template: one field per line, written name: type [range] [= default]. Types are text, int, float, bool, 'choice a, b, c', and @Node.input to borrow another node's dropdown. Lines starting with # are ignored. A default follows '=', so a choice cannot contain one.
    presetSTRINGcustomWhich saved set is loaded. 'custom' is whatever you have set by hand; the name is a label on the values, which are what actually runs.
    valuesoptSTRING{}
    presetsoptSTRING{}
    namesoptSTRING

    Outputs (1)

    NameTypeDescription
    bundleOBVPM_BUNDLEThe fields as one value, keyed by name, in schema order.