---
title: "procPack — a family of procedural ops as content"
description: "A .procPack is a FAMILY of procedural operations authored as content: a folder whose init.luau returns a pack table."
section: "Types"
slug: "types-procpack"
canonical: "https://origozero.ai/docs/types-procpack"
updated: "2026-10-10T16:24:25.581366438+00:00"
tags: ["asset-type", "reference"]
---

# procPack — a family of procedural ops as content

This is the one path an op family reaches the registry through. Procgen's own
families are packs — `procPacks/` in this package holds one per family, and
terrain's `terrain.procPack` sits in its own package the same way — so the
packs shipped here are worked examples of exactly what you are writing, not a
separate mechanism the built-ins are exempt from.

```
MyPack.procPack/
  init.luau     -- returns the pack table
  README.md
  .metadata
```

## The pack table

```lua
local proc = require("@builtin::systems.procgen.proc")

local function transformed(ctx: any, geometry: any, params: { [string]: any }): any
    return proc.registry.get("geometry.transform").eval(ctx, { geometry = geometry }, params)
end

return {
    name = "myPack",                        -- the asset's own name when omitted
    description = "Moves and turns whole geometries.",
    ops = {
        ["myPack.lift"] = {
            description = "Lift a Geometry by a height.",
            inputs = { geometry = { type = "Geometry" } },
            params = { height = { type = "Float", default = 1.0 } },
            outputs = { geometry = { type = "Geometry", default = true } },
            eval = function(ctx, inputs, params)
                return transformed(ctx, inputs.geometry, { translate = { x = 0, y = params.height, z = 0 } })
            end,
        },
        ["myPack.spin"] = {
            description = "Turn a Geometry about the up axis.",
            inputs = { geometry = { type = "Geometry" } },
            params = { degrees = { type = "Float", default = 45 } },
            outputs = { geometry = { type = "Geometry", default = true } },
            eval = function(ctx, inputs, params)
                return transformed(ctx, inputs.geometry, { rotate = { x = 0, y = params.degrees, z = 0 } })
            end,
        },
    },
}
```

Each def is the same table every op in the registry is described by — typed
input sockets, params, typed output sockets, and an `eval(ctx, inputs, params)`
returning the outputs. `ctx` carries seed / nodeId / compositePath / hash /
checkpoint. Validation is the same for every pack, so a malformed socket or an
undeclared param type is refused here exactly as it is for the ones shipped in
this package.

## procPack or procNode

Both contribute ops that a graph evaluates, and both take the same def table.
They differ in how an op is addressed:

| | `.procNode` | `.procPack` |
|---|---|---|
| contributes | one op | a family |
| addressed by | the asset's name, or its ref from `asset.resolve(id, "procNode")`, which the graph records by guid | the string id the pack names it with |
| a graph writes | `g:node("n", "ScaleBy")` or `g:node("n", ref)` | `g:node("n", "myPack.lift")` |

Reach for a `.procPack` when the ops belong together and you want to read the
family's name at every call site; reach for a `.procNode` when one operation
stands on its own.

## Registration lifecycle

`init.luau` is compiled from its own source on every registration rather than
`require`d, so an edit lands immediately and anything it pulls in uses an
absolute require (`@builtin::systems.procgen.proc`). Every op's `version` is derived
from the pack's source, so editing the file re-keys the node caches of every
graph that reaches any op in the family — those graphs recook.

Editing the pack to REMOVE an op unregisters it. A graph still naming it errors
loudly at eval rather than cooking against a definition that no longer exists,
and deleting the `.procPack` folder unregisters the whole family the same way.

## In the Inspector

Operations: how many ops the pack registers, and each op's id, kind and what it does.
