TA logoThe Technical Artist
Home/Houdini/Digital Assets (HDAs)
houdini - working
hdas
how tas ship tools

Houdini Digital Assets

Workingtutorial~8 min readreviewed 2026-07-05

An HDA turns a node network into a single tool with an artist-facing interface. It is the difference between "here's my scene file, good luck" and something a team can actually use. We'll go through the lifecycle: create it, expose parameters, edit it safely, version it, and ship it.

What An HDA Actually Is

Two ideas make the whole thing click. First: an HDA turns a node network into a node type - after you create one, "rock scatter" shows up in the TAB menu next to Scatter and PolyExtrude like it belongs there. Second, and more important: the definition (the network inside, stored in a .hda file) is separate from the instances (every copy placed in every scene). Instances don't contain the network; they reference the definition. Fix a bug in the definition once, and every instance in every scene on every artist's machine picks it up on load.

That's the superpower and the danger in one mechanism - it's why HDAs are how studios ship Houdini tools, and why the Editing Section below exists.

Diagram of the HDA lifecycle: build the network, collapse to subnet, create digital asset, promote parameters, ship the .hda file
fig 21 - the hda lifecycle

Create Your First HDA

The worked example for this page: a rock scatterer - geometry in, rocks scattered on it, artist controls for density, scale, and seed. Any network works; this one exercises everything.

  1. Build the network like someone else will read it. Inside a Geometry object: your input geometry flows through scatter -> attribrandomize (scale/orient) -> copytopoints (with a rock in the second input). End with a Null named OUT - the see-through habit from the workflows page matters double inside an HDA.
  2. Collapse it into a subnet. Select the nodes -> Shift+C (or right-click -> Collapse Selected Into Subnet). Check the subnet's inputs: input 1 should be the surface, input 2 the rock geometry. Wire order here becomes the public input order of your tool - get it right now, changing it after shipping breaks scenes.
  3. Create the asset. Right-click the subnet -> Create Digital Asset... Three fields matter:
    • Operator Name - use the namespaced form from day one: studio::rock_scatter::1.0 (author/studio :: tool :: version). Renaming an operator later is a scene-breaking event; namespacing now is free.
    • Operator Label - what artists see in the TAB menu: "Rock Scatter".
    • Save to Library - the .hda file path. For real tools, a version-controlled shared location (see Sharing); for experiments, $HOME/houdini/otls is fine.
  4. Accept, and the Type Properties window opens. That's the parameter interface editor - the next section. You can close it and come back anytime (right-click the node -> Type Properties).

Hit TAB in a fresh geometry network and type "rock" - there's your tool, in the menu, like it always belonged there.

Placeholder with instructions to screenshot the TAB menu showing your custom HDA entry
placeholder - the moment it becomes a real node

The Parameter Interface - Exposing What Artists Touch

A fresh HDA has no parameters: the network inside works, but every control is buried where artists shouldn't dig. Promotion lifts chosen parameters from internal nodes up to the HDA's own interface - the tool's public API. Open Type Properties -> Parameters tab. Three ways to promote, in order of everyday usefulness:

  1. Drag and drop. With Type Properties open, drag any parameter from any node inside the HDA straight into the parameter list. Houdini creates the interface parameter and links the internal one to it (the internal parm turns into a channel reference - you'll see it go green).
  2. Right-click a parameter -> Promote Parameter. Same result, no window juggling - fastest when you're inside the network and spot something artists will need.
  3. Build manually. In the Parameters tab, use the "Create Parameters" palette (float, int, ramp, menu, button...) to add an interface parm, then reference it from inside with ch("../paramname"). This is how you expose one slider that drives several internal values - often the mark of a well-designed tool.
Placeholder with instructions to screenshot the Type Properties parameters tab during a parameter promotion
placeholder - promotion in the type properties window

For the rock scatter, promote: Scatter's Force Total Count (relabel it "Rock Count"), the randomize ranges as a "Scale Min/Max" pair, and the Global Seed. Then shape the interface like a real tool:

  • Folders group, labels translate. Create folder parms (Placement / Variation / Advanced) and drag parameters into them. Rename every label into artist language - the internal name stays stable for scripting; the label is UX.
  • Ranges and defaults are design decisions. Set slider ranges to values that can't look broken, and defaults that demo well on first drop - the same rule as Geometry Nodes groups and master materials, because it's the same discipline.
  • Tooltips on everything promoted. The Help field in each parameter's settings. Future-you is the main beneficiary.
  • Conditional UI: in a parameter's settings, Disable When { use_custom_rocks == 0 } greys it out; Hide When removes it. An interface that hides irrelevant controls reads as professional; forty always-on sliders reads as homework.
  • Ramps and menus are free wins. A ramp parameter (referenced with chramp() in a wrangle - see the VEX sheet) gives artists a whole art-directable curve; an Ordered Menu beats a mystery integer every time.
  • Buttons run callbacks. A button parm with a Python callback is how "Bake to Disk" and "Reset Seed" happen:
Python - button callback (in the parameter's Callback Script field)
# kwargs["node"] is the HDA instance whose button was pressed
node = kwargs["node"]
node.parm("seed").set(__import__("random").randint(0, 99999))
print(f"{node.name()}: rerolled seed")
Placeholder with instructions to screenshot the finished HDA parameter interface next to the viewport result
placeholder - the finished interface: what the artist experiences

Editing An HDA Safely

Place an instance and try to dive inside - it's locked. That's the definition/instance separation protecting you: instances are read-only views of the definition. The edit loop:

  1. Unlock: right-click the instance -> Allow Editing of Contents. The node badge changes; this instance is now editable and diverged from the definition.
  2. Make your changes - fix the bug, add the node, adjust the internal wiring.
  3. Commit: right-click -> Save Node Type. Your edits become the new definition; the instance re-locks; everyone downstream gets the fix. Change of heart instead? Match Current Definition throws your edits away and re-syncs to the saved version - the closest thing node-land has to git checkout --.
The classic mistake

Editing an unlocked instance for a week and never saving the node type. Your scene works; every other scene still runs the old definition; and your changes live in exactly one file, unversioned. If a node shows the "unlocked" badge at the end of the day, either Save Node Type or Match Current Definition - never leave it drifting. (And the .hda file itself belongs in version control, where a bad definition save is a revert instead of a tragedy.)

Versioning - The Namespace Payoff

Remember studio::rock_scatter::1.0? Here's the payoff. When a change would break existing scenes - removed parameters, different input meanings, changed defaults that alter output - don't overwrite: Type Properties -> Basic tab -> Save As, bump to ::2.0. Both versions now coexist: old scenes keep cooking with 1.0 exactly as they were lit and approved; new work gets 2.0 from the TAB menu (Houdini offers the newest by default). Safe changes - bug fixes, added parameters with harmless defaults, speedups - just save into the current version.

The judgment call in one line: "would a scene built last month look different after this change?" Yes -> new version. No -> same version. When in doubt, bump; disk space is cheaper than re-approving forty shots.

Using & Sharing HDAs

An .hda file becomes available three ways, in ascending order of professionalism:

  • Install from file: File -> Import -> Houdini Digital Asset, or drop the file into $HOME/houdiniXX.X/otls. Fine for personal tools and trying things.
  • Shared path: put your studio's otls/ folder on $HOUDINI_OTLSCAN_PATH (usually via a packages JSON). Every artist who launches Houdini gets the whole toolbox automatically - this plus version control is the standard studio setup.
  • Scripted install - for pipeline launchers and render farms:
Python - install & inspect from code
import hou

hou.hda.installFile(r"P:/studio/otls/studio.rock_scatter.1.0.hda")

# what's installed, and which definitions live in a file?
for defn in hou.hda.definitionsInFile(r"P:/studio/otls/studio.rock_scatter.1.0.hda"):
 print(defn.nodeTypeName(), "-", defn.description())

Two distribution notes worth their scars: embedded HDAs (saved inside the .hip, "Embedded" in the library dropdown) are for one-off experiments only - they silently fork from the library version and produce the "works in my scene" genre of bug. And if your tool targets engines, the same .hda loads in Unreal/Unity via Houdini Engine - your promoted parameters become editor UI there too, which quietly doubles the audience of every interface decision you made above.

Design Guidelines (The Checklist)

  • Define clear inputs and outputs - label them in Type Properties ("Base Geometry", "Guide Curves"); end the internal network at a Null named OUT.
  • Promote few, promote well - every exposed parameter is support surface. Five great controls beat twenty-five accurate ones.
  • Help tab + tooltips - fill in the Help tab in Type Properties; an HDA with real help text feels like something you can actually ship.
  • Version with namespaces - studio::tool::X.Y, bump on breaking changes, keep old versions installed.
  • Validate inputs loudly - fail with a sentence, not a cryptic downstream explosion:
VEX - input validation wrangle (Detail mode, first node inside)
int npts = npoints(0);

if (npts == 0)
 error("No input geometry. Connect a surface to input 1.");

if (!hasattrib(0, "point", "N"))
 warning("No normals on input - rocks will orient to world up.");

// stash metadata for downstream nodes
setdetailattrib(0, "_input_ptcount", npts);
HDA Best Practice

Store team HDAs in a shared $HOUDINI_PATH library under version control. Embedded HDAs are for project one-offs only. And treat the parameter interface like API surface - because scenes, scripts, and Houdini Engine sessions all depend on those names staying put.