mcpField
C
comfyui-node-datatypes

comfyui-node-datatypes

github.com/jtydhr88Development308115 installsUpdated 2w ago
Repository

About comfyui-node-datatypes

A reference to ComfyUI node data types for developers building custom nodes. It documents tensor, model, 3D, widget, and special type structures.

Key features

  1. 01Image, mask, and latent formats
  2. 02Audio, video, and conditioning structures
  3. 03Model and 3D type reference
  4. 04Node widget types
  5. 05Special and dynamic types

Use cases

  1. 01Check node input and output formats
  2. 02Process ComfyUI image or video data
  3. 03Define types for custom nodes

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/jtydhr88/comfyui-custom-node-skills --skill comfyui-node-datatypes

After installing, type/comfyui-node-datatypesin Claude Code to confirm it loaded.

Details

From the project README

ComfyUI Data Types

ComfyUI uses specific data types for node inputs and outputs. Understanding tensor shapes and data formats is essential.

Complete Type Reference

Tensor/Data Types
TypeV3 ClassFormatDescription
IMAGEio.Imagetorch.Tensor [B,H,W,C] float32 0-1Batch of RGB images
MASKio.Masktorch.Tensor [H,W] or [B,H,W] float32 0-1Grayscale masks
LATENTio.Latent{"samples": Tensor[B,C,H,W] or [B,C,T,H,W], "noise_mask"?: Tensor, "batch_index"?: list[int], "type"?: str}Latent space (4D image / 5D video)
CONDITIONINGio.Conditioninglist[tuple[Tensor, PooledDict]]Text conditioning with pooled outputs
AUDIOio.Audio{"waveform": Tensor[B,C,T], "sample_rate": int}Audio data
VIDEOio.VideoVideoInput ABCVideo data (abstract base class)
SIGMASio.Sigmastorch.Tensor 1D, length steps+1Noise schedule
NOISEio.NoiseObject with generate_noise()Noise generator
LORA_MODELio.LoraModeldict[str, torch.Tensor]LoRA weight deltas
LOSS_MAPio.LossMap{"loss": list[torch.Tensor]}Loss map
TRACKSio.Tracks{"track_path": Tensor, "track_visibility": Tensor}Motion tracking data
WAN_CAMERA_EMBEDDINGio.WanCameraEmbeddingtorch.TensorWAN camera embeddings
LATENT_OPERATIONio.LatentOperationCallable[[Tensor], Tensor]Latent transform function
TIMESTEPS_RANGEio.TimestepsRangetuple[int, int]Range 0.0-1.0
DICTio.DictdictGeneric dictionary
ARRAYio.ArraylistGeneric list/array
LAYERSio.Layers{"version": int, "canvas"?: (w, h), "layers": list[LayerItem]}Layer stack (v0.31+); LayerItem: image tensor, type: "raster", z_index, optional x, y, mask, name, opacity, blend_mode (io.Layers.BlendMode), visible, flip_h/v, rotation, w, h
Model Types (opaque, typically pass-through)
TypeV3 ClassPython Type
MODELio.ModelModelPatcher
CLIPio.ClipCLIP
VAEio.VaeVAE
CONTROL_NETio.ControlNetControlNet
CLIP_VISIONio.ClipVisionClipVisionModel
CLIP_VISION_OUTPUTio.ClipVisionOutputClipVisionOutput
STYLE_MODELio.StyleModelStyleModel
GLIGENio.GligenModelPatcher (wrapping Gligen)
UPSCALE_MODELio.UpscaleModelImageModelDescriptor
BACKGROUND_REMOVALio.BackgroundRemovalBackgroundRemovalModel (e.g. BiRefNet)
LATENT_UPSCALE_MODELio.LatentUpscaleModelAny
SAMPLERio.SamplerSampler
GUIDERio.GuiderCFGGuider
HOOKSio.HooksHookGroup
HOOK_KEYFRAMESio.HookKeyframesHookKeyframeGroup
MODEL_PATCHio.ModelPatchAny
AUDIO_ENCODERio.AudioEncoderAny
AUDIO_ENCODER_OUTPUTio.AudioEncoderOutputAny
PHOTOMAKERio.PhotomakerAny
POINTio.PointAny
FACE_ANALYSISio.FaceAnalysisAny
BBOXio.BBOXAny
SEGSio.SEGSAny
3D Types
TypeV3 ClassPython TypeDescription
MESHio.MeshMESH(vertices, faces)3D mesh with vertices + faces tensors
VOXELio.VoxelVOXEL(data)Voxel data tensor
SPLATio.SplatSPLATGaussian splat data
FILE_3Dio.File3DAnyFile3DAny supported 3D format
FILE_3D_GLBio.File3DGLBFile3DBinary glTF
FILE_3D_GLTFio.File3DGLTFFile3DJSON-based glTF
FILE_3D_FBXio.File3DFBXFile3DFBX format
FILE_3D_OBJio.File3DOBJFile3DOBJ format
FILE_3D_STLio.File3DSTLFile3DSTL format (3D printing)
FILE_3D_USDZio.File3DUSDZFile3DApple AR format
FILE_3D_PLYio.File3DPLYFile3DPLY (point cloud / splat)
FILE_3D_SPLATio.File3DSPLATFile3D.splat gaussian splat file
FILE_3D_SPZio.File3DSPZFile3DCompressed splat (.spz)
FILE_3D_KSPLATio.File3DKSPLATFile3D.ksplat format
FILE_3D_SPLAT_ANYio.File3DSplatAnyFile3DAny splat format
FILE_3D_POINT_CLOUD_ANYio.File3DPointCloudAnyFile3DAny point cloud format
SVGio.SVGSVGScalable vector graphics
LOAD_3Dio.Load3DModel3DDict (see below)3D model with renders
LOAD_3D_ANIMATIONio.Load3DAnimationSame as Load3DAnimated 3D model
LOAD3D_CAMERAio.Load3DCameraCameraInfo (see below)3D camera info
LOAD3D_MODEL_INFOio.Load3DModelInfolist[Model3DTransform]Per-model transforms (position/quaternion/scale)

Load3D.Model3DDict: {"image": str, "mask": str, "normal": str, "camera_info": CameraInfo, "recording"?: str, "model_3d_info"?: list[Model3DTransform]}

Load3DCamera.CameraInfo (right-handed, Y-up, camera looks down -Z): required keys position, target, zoom, cameraType ('perspective' | 'orthographic'); optional keys quaternion (camera world rotation), useCustomUp (bool, v0.40+: use the quaternion's up instead of world +Y), fov (vertical, degrees, perspective only), aspect, near, far, frustum (orthographic only: {left, right, top, bottom}).

Load3DModelInfo.Model3DTransform: {"position": dict, "quaternion": dict, "scale": dict} in world space.

Widget Types (create UI controls)
TypeV3 ClassPython TypeDescription
INTio.IntintInteger with min/max/step
FLOATio.FloatfloatFloat with min/max/step/round
STRINGio.StringstrText (single/multi-line)
BOOLEANio.BooleanboolToggle with labels
COMBOio.CombostrDropdown selection
COMBO (multi)io.MultiCombolist[str]Multi-select dropdown
COLORio.Colorstr (hex)Color picker, default #ffffff
COLORSio.Colorslist[str] (hex)Color palette (list of colors)
BOUNDING_BOXio.BoundingBox{"x": int, "y": int, "width": int, "height": int}Rectangle region
BOUNDING_BOXESio.BoundingBoxeslist[{"x", "y", "width", "height", "metadata": dict}]Multiple labeled regions
CURVEio.Curvelist[tuple[float, float]]Spline curve points
RANGEio.RangeRangeInput (min/max + optional midpoint)Levels/range editor with gradient display
IMAGECOMPAREio.ImageComparedictImage comparison widget
WEBCAMio.WebcamstrWebcam capture widget
HISTOGRAMio.Histogramlist[int]Histogram bin counts
VIDEO_EDITio.VideoEdit{"trim"?: {start_time, duration}, "crop"?: {x, y, width, height}}Video trim/crop editor (features=["trim","crop"])
RESOLUTION_PREVIEWio.ResolutionPreviewdictLive resolution preview bound to other widgets by name
COMPOSITORio.Compositordict (LayerState)Layer-editor state, default {}
CAMERA_INFO_STATEio.CameraInfoStatedictCamera editor state (input only)
Special Types
TypeV3 ClassDescription
* (ANY)io.AnyTypeMatches any type
COMFY_MULTITYPED_V3io.MultiTypeAccept multiple specific types on one input
COMFY_MATCHTYPE_V3io.MatchTypeGeneric type matching across inputs/outputs
COMFY_AUTOGROW_V3io.AutogrowDynamic growing inputs
COMFY_DYNAMICCOMBO_V3io.DynamicComboCombo that reveals sub-inputs per option
COMFY_DYNAMICSLOT_V3io.DynamicSlotConnection slot that reveals sub-inputs when connected (not yet used by core nodes)
COMFY_DYNAMICGROUP_V3(internal _DynamicGroup)Repeated widget rows; not exported, unstable — do not use (frontend support since v1.57.0)
FLOW_CONTROLio.FlowControlInternal testing only
ACCUMULATIONio.AccumulationInternal testing only

IMAGE Type

Images are torch.Tensor with shape [B, H, W, C]:

  • B = batch size (1 for single image)
  • H = height in pixels
  • W = width in pixels
  • C = channels (3 for RGB, values 0.0-1.0)
import torch
import numpy as np
from PIL import Image as PILImage

class ImageProcessor(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id="ImageProcessor",
            display_name="Image Processor",
            category="image",
            inputs=[io.Image.Input("image")],
            outputs=[io.Image.Output("IMAGE")],
        )

    @classmethod
    def execute(cls, image):
        b, h, w, c = image.shape
        result = torch.clamp(image * 1.5, 0.0, 1.0)
        return io.NodeOutput(result)
Loading / Saving Images
from PIL import ImageOps

# Load from file → tensor
def load_image(path):
    img = PILImage.open(path)
    img = ImageOps.exif_transpose(img)   # fix rotation from camera EXIF
    if img.mode == "I":                  # handle 16-bit images
        img = img.point(lambda i: i * (1 / 255))
    img = img.convert("RGB")
    return torch.from_numpy(np.array(img).astype(np.float32) / 255.0).unsqueeze(0)

# Tensor → save to file
def save_image(tensor, path):
    if tensor.dim() == 4:
        tensor = tensor[0]
    PILImage.fromarray(np.clip(255.0 * tensor.cpu().numpy(), 0, 255).astype(np.uint8)).save(path)

# Batch operations
batch = torch.cat([img1, img2], dim=0)    # stack into batch
single = image[i]                          # extract from batch [H,W,C]
single_batch = image.unsqueeze(0)          # add batch dim [1,H,W,C]

MASK Type

torch.Tensor with shape [H, W] or [B, H, W], values 0.0-1.0.

# Invert mask
inverted = 1.0 - mask

# Mask ↔ Image conversion
alpha = mask.unsqueeze(0).unsqueeze(-1)                   # [1,H,W,1]
gray_mask = 0.299*img[:,:,:,0] + 0.587*img[:,:,:,1] + 0.114*img[:,:,:,2]
image_from_mask = mask.unsqueeze(-1).repeat(1, 1, 1, 3)  # [B,H,W,3]

# Ensure batch dim
if mask.dim() == 2:
    mask = mask.unsqueeze(0)  # [1, H, W]

LATENT Type

Dict with typed keys:

class LatentDict(TypedDict):
    samples: torch.Tensor       # [B, C, H, W] (image) or [B, C, T, H, W] (video) - required
    noise_mask: NotRequired[torch.Tensor]
    batch_index: NotRequired[list[int]]
    type: NotRequired[str]      # only for "audio", "hunyuan3dv2"

Image models (SD1.5, SDXL, SD3, Flux): 4D [B, C, H, W] — SD1.5/SDXL = 4 channels, SD3/Flux = 16 channels. Latent dimensions are 1/8 of pixel dims.

Video models (Hunyuan Video, Wan, Cosmos, LTX Video, Mochi): 5D [B, C, T, H, W] — T is the temporal (frame) dimension.

samples = latent["samples"]
# Check dimensionality:
if samples.ndim == 5:
    B, C, T, H, W = samples.shape   # video latent
else:
    B, C, H, W = samples.shape      # image latent

# Always preserve extra keys when modifying:
result = latent.copy()
result["samples"] = modified_samples

CONDITIONING Type

list[tuple[Tensor, PooledDict]] — a list of (cond_tensor, metadata_dict) pairs.

The PooledDict contains many optional keys for different models:

class PooledDict(TypedDict):
    pooled_output: torch.Tensor
    control: NotRequired[ControlNet]
    area: NotRequired[tuple[int, ...]]
    strength: NotRequired[float]           # default 1.0
    mask: NotRequired[torch.Tensor]
    start_percent: NotRequired[float]      # 0.0-1.0
    end_percent: NotRequired[float]        # 0.0-1.0
    guidance: NotRequired[float]           # Flux-like models
    hooks: NotRequired[HookGroup]
    # ... many more model-specific keys (SDXL, SVD, WAN, etc.)

Combine conditioning: result = cond_a + cond_b (list concatenation).

VIDEO Type

VideoInput is an abstract base class with methods:

class VideoInput(ABC):
    def get_components(self) -> VideoComponents    # images tensor + audio + frame_rate
    def save_to(self, path, format=VideoContainer.AUTO, codec=VideoCodec.AUTO, metadata=None,
                bit_depth=None,     # None keeps native depth
                crf=None,           # H.264/AV1 CRF; None -> 18 (H.264) / 24 (AV1)
                color_space=None,   # "sRGB" | "HDR" (BT.2020 HLG) | "HDR PQ"
                preset=None)        # H.264 encoder preset, e.g. "ultrafast"
    def as_trimmed(self, start_time=None, duration=None, strict_duration=False) -> VideoInput | None
    def as_cropped(self, x=0, y=0, width=0, height=0) -> VideoInput  # clamped, even-aligned; empty/full rect = self
    def get_stream_source(self) -> str | BytesIO
    def get_dimensions(self) -> tuple[int, int]     # (width, height)
    def get_duration(self) -> float                  # seconds
    def get_frame_count(self) -> int
    def get_frame_rate(self) -> Fraction
    def get_container_format(self) -> str
    def get_bit_depth(self) -> int                   # max component bits of the stream, e.g. 8/10/12 (default 8)
    def get_color_space(self) -> str                 # "sRGB" | "HDR" | "HDR PQ" | "auto" (unknown)

High bit depth and HDR (v0.34+) are supported end-to-end: loaders report get_bit_depth() / get_color_space(), save nodes preserve them. Codecs: Types.VideoCodec.AUTO/H264/AV1; containers: Types.VideoContainer.AUTO/MP4/MKV/WEBM.

Concrete implementations (via from comfy_api.latest import InputImpl):

  • VideoFromFile(file, *, start_time=0, duration=0, crop=None) — path or BytesIO; crop=(x, y, w, h)
  • VideoFromComponents(components, bit_depth=8, color_space="sRGB") — from tensors
  • VideoFromList(videos, complete_audio=None, codec=AUTO) — concatenation (v0.36+)

3D Types

File3D
from comfy_api.latest import Types

# File3D wraps a 3D file (disk path or BytesIO stream)
file_3d = Types.File3D(source="/path/to/model.glb", file_format="glb")
file_3d.format              # "glb"
file_3d.is_disk_backed      # True
file_3d.get_data()          # BytesIO
file_3d.get_bytes()         # raw bytes
file_3d.save_to("/output/model.glb")
MESH, VOXEL and SPLAT
from comfy_api.latest import Types

mesh = Types.MESH(vertices=torch.tensor(...), faces=torch.tensor(...))  # (B,N,3), (B,M,3)
voxel = Types.VOXEL(data=torch.tensor(...), voxel_colors=None, resolution=None)
splat = Types.SPLAT(...)   # gaussian splat data

MESH optional fields (pass by keyword — metallic_roughness was inserted before vertex_counts in v0.34, breaking positional calls): uvs (B,N,2), vertex_colors (B,N,3|4), texture (B,H,W,3 baseColor), metallic_roughness (B,H,W,3; G=roughness, B=metallic), vertex_counts/face_counts (B,) real lengths for padded batches (both or neither), unlit, normals (B,N,3), tangents (B,N,4), normal_map, occlusion_in_mr, material (dict), emissive.

Widget Types with Special Features

Color
io.Color.Input("color", default="#ff0000", socketless=True)
# Value is a hex string like "#ff0000"
Colors (palette)
io.Colors.Input("palette", default=["#ff0000", "#00ff00"], socketless=True)
# Value is list[str] of hex colors
BoundingBox
io.BoundingBox.Input("bbox",
    default={"x": 0, "y": 0, "width": 512, "height": 512},
    socketless=True,
    component="my_component",  # optional custom UI component
)
# Value is {"x": int, "y": int, "width": int, "height": int}
BoundingBoxes (multiple regions)
io.BoundingBoxes.Input("regions", default=[], socketless=True)
# Value is list of {"x": int, "y": int, "width": int, "height": int, "metadata": dict}
Curve
from comfy_api.input import CurveInput

io.Curve.Input("curve",
    default=[(0.0, 0.0), (1.0, 1.0)],  # linear
    socketless=True,
)
# In execute(), normalize the raw value first:
curve = CurveInput.from_raw(curve)
Range (levels editor)
from comfy_api.input import RangeInput

io.Range.Input("levels",
    default={"min": 0.0, "max": 1.0},
    display=None,                # widget visualization mode
    gradient_stops=None,         # gradient background for the slider
    show_midpoint=True,          # show gamma midpoint handle
    midpoint_scale=None,
    value_min=0.0, value_max=1.0,  # UI bounds
)
# In execute(), normalize with RangeInput.from_raw(value):
# .min_val, .max_val, .midpoint (gamma = -log2(midpoint), 0.5 = linear)
# .to_lut(size) generates a GIMP-style levels lookup table
MultiCombo
io.MultiCombo.Input("tags",
    options=["tag1", "tag2", "tag3"],
    default=["tag1"],
    placeholder="Select tags...",
    chip=True,  # show as chips
)
# Value is list[str]
Webcam
io.Webcam.Input("webcam_capture")
# Value is str (captured image data)
ImageCompare
io.ImageCompare.Input("comparison", socketless=True)
# Value is dict

Custom Types

# Simple: create inline custom type
MyData = io.Custom("MY_DATA_TYPE")

# Use in inputs/outputs
io.Schema(
    inputs=[MyData.Input("data")],
    outputs=[MyData.Output("MY_DATA")],
)
Advanced: @comfytype decorator

For custom types with type hints or custom Input/Output classes:

from comfy_api.latest._io import comfytype, ComfyTypeIO

@comfytype(io_type="MY_DATA_TYPE")
class MyData(ComfyTypeIO):
    Type = dict[str, Any]  # type hint for the data

RAM cache sizing: the default RAM-pressure cache measures CPU tensors in outputs (also inside lists/tuples/dicts); opaque objects count as a small default. For a custom class holding large tensors, add def _comfy_cache_tensors(self): return (self.a, self.b) (as core MESH/VOXEL/SPLAT do, v0.35+) so it is sized and evicted correctly.

AnyType / Wildcard

# Accept any single type (always a connection input, no widget)
io.AnyType.Input("anything")

# Accept specific multiple types
io.MultiType.Input("data", types=[io.Image, io.Mask, io.Latent])

# MultiType with widget override (shows widget for first type)
io.MultiType.Input(
    io.Float.Input("value", default=1.0),
    types=[io.Float, io.Int],
)

Imports from comfy_api.latest

from comfy_api.latest import (
    ComfyExtension,  # extension registration
    ComfyAPI,        # runtime API (progress, node replacement)
    io,              # all io types (io.Image, io.Schema, io.ComfyNode, etc.)
    ui,              # UI output helpers (ui.PreviewImage, ui.SavedImages, etc.)
    Input,           # Input.Image (ImageInput), Input.Audio, Input.Mask, Input.Latent, Input.Video
    InputImpl,       # InputImpl.VideoFromFile, .VideoFromComponents, .VideoFromList
    Types,           # Types.MESH, Types.VOXEL, Types.File3D, Types.VideoCodec, etc.
)

Tensor Safety

When checking if a tensor exists, always use is not None instead of truthiness:

# CORRECT
if image is not None:
    process(image)

# WRONG — multi-element tensors don't support bool()
if image:       # raises RuntimeError
    process(image)

# For boolean conditions on tensors, use .all() or .any()
if (mask > 0.5).all():
    ...

Type Conversion Patterns

# IMAGE [B,H,W,C] → MASK [B,H,W]
mask = 0.299 * image[:,:,:,0] + 0.587 * image[:,:,:,1] + 0.114 * image[:,:,:,2]

# MASK [B,H,W] → IMAGE [B,H,W,C]
image = mask.unsqueeze(-1).repeat(1, 1, 1, 3)

# Resize image tensor
import torch.nn.functional as F
resized = F.interpolate(
    image.permute(0, 3, 1, 2),  # [B,C,H,W] for interpolate
    size=(new_h, new_w), mode='bilinear', align_corners=False
).permute(0, 2, 3, 1)  # back to [B,H,W,C]

See Also

  • comfyui-node-basics - Node class structure and registration
  • comfyui-node-inputs - Input configuration details (widget options)
  • comfyui-node-outputs - Output types and UI outputs
  • comfyui-node-advanced - MatchType, MultiType, Autogrow, DynamicCombo

Comments

Markdown supported