Hairstyle Changer
Try any haircut or hair color before you commit. Upload a selfie and use the hairstyle changer workflow to a variety of hair options.
About
Hairstyle Changer is a ComfyUI workflow that lets you preview different haircuts and hair colors on a selfie before you commit. It starts with LoadImage to bring in your photo, then routes your style choices through ComfySwitchNode and preset strings defined in PrimitiveStringMultiline. Those choices are combined with StringConcatenate and, when needed, pulled from a structured presets block via JsonExtractString. The edited result is displayed in PreviewAny and saved through SaveImage.
At the core is a custom processing node (id: d4c06663-cb1a-4a41-a36b-f09c93e55e7e) that applies the hairstyle/color change using the inputs prepared upstream. CustomCombo is used to package the image and text parameters into a clean input for that node. This parameterized design makes it easy to A/B test looks simply by flipping the ComfySwitchNode or tweaking the preset strings—keeping your original photo intact while focusing changes on hair appearance.
What you provide
- Reading the workflow
Runs in ComfyUI. This workflow can't run on this page. Open it in Comfy Cloud with everything loaded, or download it for your own ComfyUI.
Try in CloudInside the workflow
A read-only preview of the node graph. Open it in Comfy Cloud or download it to edit the nodes.
Loading the graph
Open full-size workflow preview- Author
- Rob
Requires custom nodes
- Reading the workflow
Use this workflow from code
Generated from the workflow graph and inputs when possible; stored examples are shown otherwise. Review credentials and endpoints before running code, which may incur charges.
What you need
- Runs on
- Comfy Cloud
- Key
- COMFY_API_KEY
Needs a paid Cloud plan and credits.
# Python 3.10+: pip install comfy-sdk==0.4.0
import os
from comfy_sdk import Comfy
api_key = os.environ.get("COMFY_API_KEY", "").strip()
if not api_key: raise RuntimeError("Set COMFY_API_KEY")
os.environ.setdefault("COMFY_BASE_URL", "https://cloud.comfy.org")
client = Comfy(api_key=api_key)
workflow = client.workflows.from_json({
"132": {
"class_type": "LoadImage",
"inputs": {
"image": "e6bc10130ce0e9dbd3573751650d9f01c2687728383958094ab49d0b35ccfeae.png"
},
"_meta": {
"title": "Load Image"
}
},
"197": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "Qwen_Edit_2511",
"images": [
"196:175",
0
]
},
"_meta": {
"title": "Save Image"
}
},
"234": {
"class_type": "StringConcatenate",
"inputs": {
"delimiter": " ",
"string_a": "Keep the exact same facial identity, facial features, expression, pose, clothing, background, lighting, and composition as the original photo. Only change the subject's hair.",
"string_b": [
"258",
0
]
},
"_meta": {
"title": "Assemble Prompt"
}
},
"235": {
"class_type": "PreviewAny",
"inputs": {
"source": [
"234",
0
]
},
"_meta": {
"title": "Preview as Text"
}
},
"252": {
"class_type": "CustomCombo",
"inputs": {
"choice": "Long Waves"
},
"_meta": {
"title": "Hairstyle"
}
},
"253": {
"class_type": "JsonExtractString",
"inputs": {
"json_string": "{\n \"Buzz Cut\": \"Change the subject's hairstyle to a very short, even buzz cut with a clean hairline.\",\n \"Slicked Back\": \"Change the subject's hairstyle to medium-length hair combed straight back with a polished sheen.\",\n \"Side Part\": \"Change the subject's hairstyle to a classic neatly combed side part with a crisp parting line.\",\n \"Textured Quiff\": \"Change the subject's hairstyle to a voluminous textured quiff with short faded sides.\",\n \"Curly Top\": \"Change the subject's hairstyle to tight natural curls on top with tapered short sides.\",\n \"Afro\": \"Change the subject's hairstyle to a full, rounded natural afro with soft, even volume.\",\n \"Long Waves\": \"Change the subject's hairstyle to shoulder-length loose, flowing waves with natural body.\",\n \"Long Straight\": \"Change the subject's hairstyle to long, sleek, straight hair falling past the shoulders.\",\n \"Bob Cut\": \"Change the subject's hairstyle to a chin-length blunt bob with a smooth, even edge.\",\n \"Pixie Cut\": \"Change the subject's hairstyle to a short, cropped pixie cut with soft feathered layers.\",\n \"Box Braids\": \"Change the subject's hairstyle to neat medium-width box braids falling to the shoulders.\",\n \"High Ponytail\": \"Change the subject's hairstyle to hair pulled up into a sleek high ponytail.\",\n \"Top Knot Bun\": \"Change the subject's hairstyle to hair gathered into a neat top-knot bun.\",\n \"Mohawk\": \"Change the subject's hairstyle to a bold upright mohawk with closely shaved sides.\",\n \"Bald\": \"Remove the subject's hair entirely, giving them a cleanly shaved bald head with natural scalp texture.\"\n}",
"key": [
"252",
0
]
},
"_meta": {
"title": "Hairstyle Prompt (JSON)"
}
},
"256": {
"class_type": "StringConcatenate",
"inputs": {
"delimiter": " ",
"string_a": [
"253",
0
],
"string_b": [
"263",
0
]
},
"_meta": {
"title": "Hairstyle + Color"
}
},
"257": {
"class_type": "PrimitiveStringMultiline",
"inputs": {
"value": "Change the subject's hairstyle to shoulder-length loose waves colored copper red. Keep everything else exactly the same."
},
"_meta": {
"title": "Custom Prompt Text"
}
},
"258": {
"class_type": "ComfySwitchNode",
"inputs": {
"on_false": [
"256",
0
],
"on_true": [
"257",
0
],
"switch": False
},
"_meta": {
"title": "Custom Prompt?"
}
},
"262": {
"class_type": "CustomCombo",
"inputs": {
"choice": "Platinum White"
},
"_meta": {
"title": "Hair Color"
}
},
"263": {
"class_type": "JsonExtractString",
"inputs": {
"json_string": "{\n \"Keep Original Color\": \"Keep the subject's original hair color.\",\n \"Blonde\": \"The hair is colored blonde.\",\n \"Dark Brown\": \"The hair is colored dark brown.\",\n \"Black\": \"The hair is colored black.\",\n \"Auburn Red\": \"The hair is colored auburn red.\",\n \"Platinum White\": \"The hair is colored platinum white.\",\n \"Silver Gray\": \"The hair is colored silver gray.\"\n}",
"key": [
"262",
0
]
},
"_meta": {
"title": "Hair Color Prompt (JSON)"
}
},
"196:152": {
"class_type": "CFGNorm",
"inputs": {
"model": [
"196:155",
0
],
"pre_cfg": False,
"strength": 1
},
"_meta": {
"title": "CFGNorm"
}
},
"196:155": {
"class_type": "ModelSamplingAuraFlow",
"inputs": {
"model": [
"196:166",
0
],
"sampling": "flow",
"shift": 3.1
},
"_meta": {
"title": "ModelSamplingAuraFlow"
}
},
"196:156": {
"class_type": "VAELoader",
"inputs": {
"vae_name": "qwen_image_vae.safetensors"
},
"_meta": {
"title": "Load VAE"
}
},
"196:161": {
"class_type": "TextEncodeQwenImageEditPlus",
"inputs": {
"clip": [
"196:167",
0
],
"image1": [
"196:176",
0
],
"prompt": [
"235",
0
],
"vae": [
"196:156",
0
]
},
"_meta": {
"title": "TextEncodeQwenImageEditPlus (Positive)"
}
},
"196:162": {
"class_type": "LoraLoaderModelOnly",
"inputs": {
"lora_name": "Qwen-Image-Edit-2511-Lightning-4steps-V1.0-bf16.safetensors",
"model": [
"196:152",
0
],
"strength_model": 1
},
"_meta": {
"title": "Load LoRA"
}
},
"196:163": {
"class_type": "PrimitiveFloat",
"inputs": {
"value": 4
},
"_meta": {
"title": "CFG"
}
},
"196:164": {
"class_type": "PrimitiveFloat",
"inputs": {
"value": 1
},
"_meta": {
"title": "CFG"
}
},
"196:165": {
"class_type": "VAEEncode",
"inputs": {
"pixels": [
"196:176",
0
],
"vae": [
"196:156",
0
]
},
"_meta": {
"title": "VAE Encode"
}
},
"196:166": {
"class_type": "UNETLoader",
"inputs": {
"unet_name": "qwen_image_edit_2511_bf16.safetensors",
"weight_dtype": "default"
},
"_meta": {
"title": "Load Diffusion Model"
}
},
"196:167": {
"class_type": "CLIPLoader",
"inputs": {
"clip_name": "qwen_2.5_vl_7b_fp8_scaled.safetensors",
"device": "default",
"type": "qwen_image"
},
"_meta": {
"title": "Load CLIP"
}
},
"196:168": {
"class_type": "ComfySwitchNode",
"inputs": {
"on_false": [
"196:152",
0
],
"on_true": [
"196:162",
0
],
"switch": [
"196:171",
0
]
},
"_meta": {
"title": "Switch (Model)"
}
},
"196:169": {
"class_type": "PrimitiveInt",
"inputs": {
"value": 6
},
"_meta": {
"title": "Steps"
}
},
"196:170": {
"class_type": "PrimitiveInt",
"inputs": {
"value": 30
},
"_meta": {
"title": "Steps"
}
},
"196:171": {
"class_type": "PrimitiveBoolean",
"inputs": {
"value": True
},
"_meta": {
"title": "Enable 4steps LoRA?"
}
},
"196:172": {
"class_type": "ComfySwitchNode",
"inputs": {
"on_false": [
"196:163",
0
],
"on_true": [
"196:164",
0
],
"switch": [
"196:171",
0
]
},
"_meta": {
"title": "Switch (CFG)"
}
},
"196:173": {
"class_type": "ComfySwitchNode",
"inputs": {
"on_false": [
"196:170",
0
],
"on_true": [
"196:169",
0
],
"switch": [
"196:171",
0
]
},
"_meta": {
"title": "Switch (Steps)"
}
},
"196:174": {
"class_type": "KSampler",
"inputs": {
"cfg": [
"196:172",
0
],
"denoise": 1,
"latent_image": [
"196:165",
0
],
"model": [
"196:168",
0
],
"negative": [
"196:229",
0
],
"positive": [
"196:161",
0
],
"sampler_name": "euler",
"scheduler": "simple",
"seed": 935848232837487,
"steps": [
"196:173",
0
]
},
"_meta": {
"title": "KSampler"
}
},
"196:175": {
"class_type": "VAEDecode",
"inputs": {
"samples": [
"196:174",
0
],
"vae": [
"196:156",
0
]
},
"_meta": {
"title": "VAE Decode"
}
},
"196:176": {
"class_type": "FluxKontextImageScale",
"inputs": {
"image": [
"132",
0
]
},
"_meta": {
"title": "FluxKontextImageScale"
}
},
"196:229": {
"class_type": "TextEncodeQwenImageEditPlus",
"inputs": {
"clip": [
"196:167",
0
],
"image1": [
"196:176",
0
],
"prompt": "",
"vae": [
"196:156",
0
]
},
"_meta": {
"title": "Text Encode Qwen Image Edit Plus"
}
}
})
# run() also passes api_key to the partner nodes in this workflow.
job = client.run(workflow, api_key=api_key)Questions & answers
A front-facing photo with even lighting, minimal shadows, and hair clearly visible. Avoid hats, heavy hair accessories, or obstructions. A neutral background helps the processor focus on hair.
It is configured to target hair appearance via the custom processor (node id d4c06663-cb1a-4a41-a36b-f09c93e55e7e) and its text parameters. Minor spill to nearby regions can occur; if you see drift, try a tighter crop around the head, reduce extreme style changes in the preset text, and rerun.
Edit PrimitiveStringMultiline to include new preset entries (e.g., keys like "copper_lob" or "icy_blonde"). Use JsonExtractString to pull those entries, and ensure your ComfySwitchNode points to the correct keys. StringConcatenate will assemble the final parameter string for the processor.
Install the extension that provides the custom node with id d4c06663-cb1a-4a41-a36b-f09c93e55e7e and restart ComfyUI. Check the ComfyUI console/log for the exact repository or package name if it’s indicated by your setup.