Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Entity Metadata

mooR extension: Entity metadata is specific to mooR. It is not part of the original LambdaMOO object model or the classic MOO language. Classic MOO programmers will already know properties, verbs, property_info(), and verb_info(); the metadata functions described here are additional mooR builtins.

Most MOO programming starts with two familiar tools:

  • Properties, which store the state of an object.
  • Verbs, which store the programs that make an object do things.

Sometimes, though, you need to store information that is not really part of the object in that sense. You may want to remember who last edited a verb, what package an object came from, which tool generated a property, or which revision of a source file a verb was imported from.

You could put that information in ordinary properties, but that changes the object's public surface. You could put it in comments at the end of a verb, but then the information is mixed into executable source code. You could keep it in a manager object somewhere else, but then every tool needs to agree on its own storage scheme.

Entity metadata gives you a direct place to put this kind of information.

Metadata can be attached to:

  • an object
  • a property on an object
  • a verb on an object

Metadata is persistent database state. It is saved n disk, included in objdef export, and restored when objdef is imported again.

Because this is a mooR extension, code that uses these builtins will not run unchanged on an original LambdaMOO server or on systems that only implement the classic MOO builtin set.

What Metadata Is For

Metadata is best used for information about an entity, rather than information the object is trying to model in the game or application.

Good uses for metadata include:

  • "this object belongs to the core package"
  • "this verb was last modified by player #42"
  • "this property was generated by the web editor"
  • "this verb came from revision 2026.06.24"
  • "this object has already been handled by a migration"
  • "this verb has documentation stored at this URL"

Things that should probably remain ordinary properties include:

  • the health of a player
  • the description of a room
  • whether a door is locked
  • the contents of a container
  • gameplay state that verbs should naturally read and write

A simple rule of thumb: if players or normal object behavior care about the value, it is probably a property. If tools, package managers, editors, importers, or maintainers care about the value, it may be metadata.

A First Example

Suppose you are building a small package of objects and want to mark each object as belonging to that package.

set_object_metadata($generic_note, "package", "notes");
set_object_metadata($generic_note, "revision", "2026.06.24");

Later, a package manager or tool can read those values:

package = object_metadata($generic_note, "package");
revision = object_metadata($generic_note, "revision");

You can also fetch all metadata attached to the object:

all = object_metadata($generic_note);

That returns a map, such as:

["package" -> "notes", "revision" -> "2026.06.24"]

If a key is not present, the lookup returns the empty list:

object_metadata($generic_note, "missing")
=> {}

This follows the usual MOO convention of using {} for a missing optional value.

Metadata Keys

Metadata keys may be strings or symbols.

These two calls refer to the same metadata entry:

set_object_metadata($thing, "package", "core");
set_object_metadata($thing, 'package, "core");

Symbols are also a mooR extension, and they are optional in mooR. Some servers run without the symbol type enabled, so portable mooR code should use string keys:

set_object_metadata($thing, "package", "core");

If your server does use symbols, symbol keys are convenient and avoid spelling mistakes in code that already uses symbols heavily:

set_object_metadata($thing, 'package, "core");

Internally, mooR canonicalizes the key, so "package" and 'package name the same metadata slot.

Object Metadata

Object metadata is attached directly to an object.

map object_metadata(obj object)
any object_metadata(obj object, str|sym key)
none set_object_metadata(obj object, str|sym key, value)
none clear_object_metadata(obj object, str|sym key)

For example, an editor could mark which tool last touched an object:

set_object_metadata($lobby, "last_editor", "web-client");
set_object_metadata($lobby, "last_edited_at", time());
set_object_metadata($lobby, "last_edited_by", player);

A later tool could display that information:

metadata = object_metadata($lobby);
player:tell("Last edited by: ", metadata["last_edited_by"]);
player:tell("Last edited at: ", ctime(metadata["last_edited_at"]));

To remove one metadata entry:

clear_object_metadata($lobby, "last_editor");

Clearing a key that is not present is harmless.

Property Metadata

Property metadata is attached to a property as resolved on a particular object.

map property_metadata(obj object, str|sym prop_name)
any property_metadata(obj object, str|sym prop_name, str|sym key)
none set_property_metadata(obj object, str|sym prop_name, str|sym key, value)
none clear_property_metadata(obj object, str|sym prop_name, str|sym key)

For example, a building tool might store documentation for a property without putting that documentation into the property value itself:

set_property_metadata($door, "locked", "doc", "True if the door cannot be opened.");
set_property_metadata($door, "locked", "editor_widget", "checkbox");

The property can still hold the ordinary value:

$door.locked = true;

And the editor can separately ask how to present that property:

widget = property_metadata($door, "locked", "editor_widget");
if (widget == "checkbox")
  "Show a checkbox in the editor.";
endif

Property metadata is useful when the same value might be edited by tools that need extra hints. For example:

set_property_metadata($room, "ambient_light", "min", 0);
set_property_metadata($room, "ambient_light", "max", 100);
set_property_metadata($room, "ambient_light", "editor_widget", "slider");

The property itself remains just the value:

$room.ambient_light = 60;

Property Inheritance

Property metadata follows the property entity that the name resolves to on the object you pass.

This detail matters because MOO properties are inherited. Suppose $generic_door defines a locked property, and $red_door is a child of $generic_door.

add_property($generic_door, "locked", false, {player, "rw"});

If you attach metadata through the generic object, it describes the generic property:

set_property_metadata($generic_door, "locked", "doc", "Base lock state for doors.");

If you attach metadata through the child object, it describes the resolved property for that child:

set_property_metadata($red_door, "locked", "doc", "The red door starts locked in chapter one.");

Metadata is not automatically inherited merely because the property value is inherited. This avoids surprising edits where changing documentation or package state on a child silently changes the parent's metadata.

Verb Metadata

Verb metadata is attached to a specific verb definition.

map verb_metadata(obj object, str|int|sym verb_desc)
any verb_metadata(obj object, str|int|sym verb_desc, str|sym key)
none set_verb_metadata(obj object, str|int|sym verb_desc, str|sym key, value)
none clear_verb_metadata(obj object, str|int|sym verb_desc, str|sym key)

The verb_desc argument follows the same general conventions as other verb builtins. You can refer to a verb by name, by symbol, or by index where applicable.

For example, an editor can record modification information for a verb:

set_verb_metadata($thing, "look", "modified_by", player);
set_verb_metadata($thing, "look", "modified_at", time());
set_verb_metadata($thing, "look", "editor", "web-client");

Later:

who = verb_metadata($thing, "look", "modified_by");
when = verb_metadata($thing, "look", "modified_at");

player:tell("Last modified by ", who, " at ", ctime(when));

Some older cores stored this kind of information in comments appended to the end of verb source. That worked, but it had downsides: tools had to parse comments, comments could be edited by accident, and metadata became tangled with executable code.

With verb metadata, the verb body can stay focused on the program:

verb "look" (this none none) owner: WIZARD flags: "rxd"
  player:tell(this.description);
endverb

And the edit record can live beside it:

set_verb_metadata($thing, "look", "modified_by", player);
set_verb_metadata($thing, "look", "modified_at", time());

Permissions

Metadata uses the same basic authority model as the entity it describes.

For writes:

  • Object metadata can be changed by the object owner or by a wizard.
  • Property metadata follows the same authority as set_property_info().
  • Verb metadata follows the same authority as set_verb_info().

For reads:

  • Object metadata follows object visibility.
  • Property metadata follows property-info visibility.
  • Verb metadata follows verb-info visibility.

This is meant to keep metadata unsurprising. If you are allowed to change the owner or flags of a verb, you are also allowed to change metadata attached to that verb. If you are not allowed to inspect a verb, you should not be able to inspect the metadata attached to it either.

For metadata that should only be changed by trusted package-management code, use a wizard-owned manager object and expose a narrower API from MOO code.

Objdef Export

Metadata is included in objdef dump and reimport by default. This means objdef export behaves like a save/restore path for metadata as well as for objects, properties, and verbs.

The bracketed metadata syntax shown here is also a mooR objdef extension. It is not part of LambdaMOO textdump syntax, and older objdef tools that do not know about mooR metadata may reject or ignore it.

Metadata appears as a bracketed map on the declaration it belongs to:

object THING [ package -> "core", revision -> "2026.06.24" ]
  name: "Generic Thing"
  parent: ROOT
  owner: WIZARD

  property version (owner: WIZARD, flags: "rc") [ package -> "core" ] = "1.0";

  verb "look l" (this none none) owner: WIZARD flags: "rxd" [
    modified_at -> 1782150937,
    modified_by -> WIZARD
  ]
    player:tell(this.name);
  endverb
endobject

Short metadata maps may fit on one line:

property version (owner: WIZARD, flags: "rc") [ package -> "core" ] = "1.0";

Longer maps are written with one key -> value entry per line:

verb "look" (this none none) owner: WIZARD flags: "rxd" [
  editor -> "web-client",
  modified_at -> 1782150937,
  modified_by -> WIZARD
]
  player:tell(this.description);
endverb

Metadata keys in objdef may be bare identifiers, strings, or symbols:

package -> "core"
"external-id" -> "abc-123"
'package -> "core"

These forms canonicalize the same way as the built-in API.

See Object Definition File Format Reference and Importing and Exporting Objdef Databases for the surrounding objdef format and database workflow.

Package and Revision Examples

One useful pattern is to record package state in metadata while keeping ordinary object behavior unchanged.

For example:

set_object_metadata($generic_note, "package", "notes");
set_object_metadata($generic_note, "revision", "2026.06.24");

set_verb_metadata($generic_note, "look", "package", "notes");
set_verb_metadata($generic_note, "look", "revision", "2026.06.24");

A package manager could then ask whether a local verb still belongs to the package revision it expects:

if (verb_metadata($generic_note, "look", "revision") == "2026.06.24")
  "This verb claims to be from the expected package revision.";
else
  "This verb may have been changed or imported from another package revision.";
endif

Metadata does not perform the package-management policy by itself. It only gives MOO code and tools a durable place to store the facts they need.

Hashing Entity State

mooR also provides hash builtins for the same object, property, and verb surfaces that metadata usually annotates. These are intended for code that wants to record what was imported, then later ask whether the local database entity still matches that base state.

For example, a package importer can record the current value hash of a property:

set_property_metadata(
  $generic_note,
  "title",
  "base_hash",
  property_value_hash($generic_note, "title")
);

Later, package-management code can compare the stored base hash with the current hash:

if (property_metadata($generic_note, "title", "base_hash") == property_value_hash($generic_note, "title"))
  "The local property value still matches the imported base.";
else
  "The local property value has changed since import.";
endif

The hash builtins are deliberately split by surface:

object_attrs_hash($thing)
object_metadata_hash($thing)

property_value_hash($thing, "title")
property_info_hash($thing, "title")
property_metadata_hash($thing, "title")

verb_code_hash($thing, "look")
verb_info_hash($thing, "look")
verb_metadata_hash($thing, "look")

This lets MOO code distinguish different kinds of change. A changed property value, a changed property owner/flags tuple, and changed property metadata are different events for most package tools. The builtins do not merge or resolve those differences by themselves.

Each hash builtin uses the same algorithm arguments as value_hash(): SHA256 by default, an optional algorithm name, and an optional truth value for returning raw digest bytes as a binary value.

Lifecycle

Metadata is database state, so it follows the entities it annotates.

  • Metadata is included in objdef export checkpoints.
  • Recycling an object removes metadata attached to that object, its local verbs, and its local property entries.
  • Deleting a verb removes metadata attached to that verb.
  • Deleting a property definition removes metadata attached to affected property entries.
  • Renumbering an object moves metadata to the new object identity.
  • Metadata is not automatically copied when an object is cloned.

In practice, this means metadata is durable, but not magical. If you create a new object based on an old one and want the metadata copied too, your code should copy it explicitly.

Anonymous Objects

Metadata can be attached to anonymous objects while they are valid objects.

Object references stored inside metadata values count as references for anonymous-object garbage collection, just like object references stored in property values. For example, this metadata value keeps temporary_helper reachable while $manager is reachable:

temporary_helper = create($nothing, 1);
set_object_metadata($manager, "helper", temporary_helper);

Metadata attached to an anonymous object does not by itself keep that anonymous object alive. If the anonymous object is collected, its attached metadata is removed too.

Choosing Between Properties and Metadata

Use a property when the value is part of the object:

$door.locked = true;
$door.description = "A heavy oak door.";

Use metadata when the value is about the object, property, or verb:

set_property_metadata($door, "locked", "editor_widget", "checkbox");
set_verb_metadata($door, "open", "modified_by", player);
set_object_metadata($door, "package", "castle");

Both are persistent. The difference is meaning. Properties are the object's state. Metadata is tooling and descriptive state attached to the database entity.