Log inGet started

Preset (asset type)

Updated October 10, 2026

When to use one

  • The same settings recur across many assets or entities (e.g. "pixel art": every sprite texture with filter = nearest and no mipmaps; a tuned locomotion set for every character of one rig).
  • Designers swap configurations without editing code.
  • A configuration needs a stable name other content can refer to.

If you're capturing a whole entity assembly, use a .bundle. If you're factoring shared logic (not configuration), use a .module.

Where it lives

  • Source: /zero/source/.../<Name>.preset/
  • Identity: <Name> (the .preset suffix strips).
  • Folder shape:
    • preset.yaml — the target and the values. Required.
    • README.md — instance documentation. Required.
    • .metadata — agent-editable tags + free-form fields (title, description). Required.

The document

target: { __ref: "8cc28fc8-ed63-48ee-83a6-15f4192e27e1", type: assetType, name: texture }
values:
  filter: nearest
  generateMipmaps: false
  • target names the declarer by a guid envelope: __ref is its guid; type and the plain name tell a person reading the file what it points at.
  • values holds only the fields this preset sets. Fields it leaves out keep whatever the asset or component has.

A component preset has the same shape, its target the component:

target: { __ref: "18dfece3-fe1b-4630-b7ad-0996f5043a2e", type: component, name: Locomotion }
values:
  walkSpeed: 1.4
  runSpeed: 2.5

How to create one

-- Capture what an asset has now:
local p = preset.capture("hero_sprite", "pixel_art")
-- Capture a component's live values on an entity:
local tint = preset.capture({ entity = id, component = "Model" }, "red_tint")
-- Both land at /zero/source/presets/<name>.preset/ (an absolute `.preset`
-- path lands where it says; `{ overwrite = true }` re-authors an existing
-- preset, keeping its guid).

-- Or author it directly:
asset.create("preset", "pixel_art", {
    target = "@builtin::assetTypes.texture",   -- an identity or a guid
    values = { filter = "nearest" },
})

Each checks every value against the target's schema and refuses the whole write on any violation, naming each one.

How to use one

  • preset.values(p) returns (values, report). Every value is checked against the target's current schema: valid fields are returned, and each invalid one is listed by path in report.rejected.
  • preset.apply(p, into) writes the valid values into an asset the target governs, or into a component on an entity named { entity = <id>, component = <name> } whose component is the target. A struct group merges field by field; any other field is written whole. Applying to something another declarer governs raises, naming both.
  • preset.setValues(p, values) replaces the whole values table, checked first. Inside an undo operation the write is recorded into it, so undoing the operation puts back what preset.yaml held.
  • A preset is a snapshot, not a live binding: applying it writes values once.
preset.apply("pixel_art", "enemy_sprite")
preset.apply("synty", { entity = id, component = "Locomotion" })
local values, report = preset.values("synty")

Defaults for new things — the Preset Manager

A world's Preset Manager rows make a preset the starting point for what is created from then on:

local p = preset.capture("hero_sprite", "pixel_art")   -- or asset.create("preset", …)
preset.addDefault(p, { filter = "sprites/**" })       -- new things only

Each row names a declarer, a preset for it, and a filter. For a declarer, the first row whose filter matches the new thing's path applies. The rows live in .world_settings as [[presets]] blocks, sync with the world, and are edited in the Settings panel's Preset Manager section.

The layers a new thing's settings take, each over the ones before it, field by field (a struct group merges field by field):

  1. the schema's declared defaults;
  2. the type's own settingsDefaults;
  3. the first matching Preset Manager row;
  4. the creating importer's settings for the type;
  5. the values the caller passed.

A component takes its public defaults, then the row, then the values the caller passed.

Rows change only new things. A row's values are stamped into what is created while it stands. Editing or removing a row changes nothing that already exists, and opening a world applies nothing to its saved content. A re-import or an overwrite = true re-authoring keeps the settings the asset already holds.

The three creation moments, and what each filter reads:

MomentThe row's declarerThe filter reads
asset.createan assetType whose instances share its schemathe new asset's path under /source (sprites/hero.texture); a runtime asset reads the path the same call would give a durable one
an importan importerthe source file's path under /source at its first import (sprites/hero.png)
component.adda componentthe entity's hierarchy path, names from the root down joined with / (level/props/crate)

A component takes a row when it is added in edit mode, and in any mode inside a scene build (a scene's build.luau, a SceneModule placement). A component put back from existing content (a saved scene, a peer's record, a template, a bundle's hierarchy, what a component's own code adds outside a build) takes none. A rebuild restates a component only when its code states it differently, so a row edit alone changes nothing a build already placed.

Checking what applies:

  • preset.defaults() lists the rows in order, each resolved, with every problem that keeps it from applying.
  • preset.defaultFor(declarer, path) returns the values a new thing at path would take from the rows, and the row that gives them.

In the Inspector

Selecting a preset shows, beside its identity:

  • Target — the declarer as an asset chip and the guid the preset names it by, with a notice when the target changed since the preset pinned it or does not resolve.
  • Values — every field of the target's current schema as its typed control, the way the target's own Settings section shows it. An edit is checked against that schema, written with preset.setValues, and is one undo step. A stored value the schema refuses is named with the reason. Beneath the fields, Apply to selection writes the preset into every selected asset its target governs and into the target component on every selected entity, as one undo step.

A preset still in the component-only form shows its values read-only, with a note that its next local edit converts it.

Every asset's and component card's Settings section offers Save as preset, which captures the values it shows as a preset targeting their declarer and selects it, and Apply preset…, which lists the presets targeting that declarer and applies the one picked.

How the target stays bound

  • The preset resolves its target by the envelope's guid, and the preset's .refs pins that guid together with the target's checksum, so the link survives the target being renamed or moved. The envelope's name is never an identity, so the guid is the preset's one link to its target.
  • Every read re-checks the values against the target's schema as it is now. report.pinned is true once .refs pins the target; report.targetChanged is true while the target's checksum differs from the one pinned, and a field the target dropped or retyped is then rejected by name while the rest still apply.
  • A target written as an identity string is accepted on read, resolves by identity and reads as unpinned; the preset's next local edit rewrites it as the envelope.

Discovery

  • asset.list("preset") — every registered preset.
  • asset.inspect("<name>").detail — target, targetGuid, format, values, rejected, pinned, targetChanged.
  • asset.validate("<name>") — preset.target_unresolved when the target does not resolve, preset.value_rejected for each value its target rejects.

Authoring conventions

  • Name presets by the configuration they capture (pixel_art, hdr_skybox, synty), not by the declarer.
  • Keep values to the fields that define the configuration; fields left out stay as each asset or component has them.

Common pitfalls

  • Hand-edited values. A value the target's schema rejects is reported, not applied, and every edit of the file warns naming it.
  • A table-valued component field (a Field.table) is written whole by preset.apply, so a preset holding one provides every key of it.
  • .assetType / .importer / .component — the declarers whose settings a preset holds.
  • .bundle — for multi-component / multi-entity assemblies.
  • asset-type
  • reference