Try it in your browserpreview Source

Members and fields✎

Reading a field off a value whose type says what it holds — and what to use when it does not.

Pending review

A value often holds other values: a table of settings, an instance with properties. Two nodes read one out, and which you want depends on a single question — does anything know what is in there?

NodeReadsWhen
Get MemberA field the type declaresThe type says what it holds: a Declare Type's fields, a Roblox class's properties. The list is offered, and for a type this file declares, a name not on it is refused before the file is written.
Get FieldAny key you nameNothing can promise what is in there: a dictionary filled and emptied as the program runs, or whatever require returns, which arrives untyped.
.…
Get FieldObjectnameField
Get Member is one line: the access it writes, one input, one output. Get Field takes the key as a pin, because the key is yours to name.

A type you declared

A type is declared by a Declare Type node — at the top of the file or where it sits — and what it is written as decides whether it has members to offer. A table of fixed fields does; everything else is a type Luau understands and this cannot enumerate.

Written asExampleMembers
Table of FieldsRows in the Inspector: throttle, numberEach row, with its type. The ordinary case, and the one the editor checks as you type it.
Custom Luau, a table{ throttle: number, aim: Vector3 }The same fields, read out of the text — so a type typed out by hand behaves as the rows do.
Custom Luau, anything else"idle" | "driving", (number) -> stringNone. There is no fixed field list to offer, and a Get Member on one is refused.
A dictionary type{ [string]: number }None: the keys are the program's business. That is Get Field's case.
Type of a Valuetypeof(Tuning)None here. Luau resolves it; Roswaal does not follow it, so its members are Get Field's.

The type's name is what travels. A Declare Local, a function's parameter, a variable or a Cast set to Input gives a pin typed Input, and every pin typed Input offers the same fields — the declaration is read wherever it sits in the file, including from inside a function.

How the type is written

The ordinary case. Input holds a throttle, a steer and an aim, each entered as a row; the local is that type; the pill reads aim and hands on a Vector3.

The Inspector for a Declare Type whose shape is Table of Fields.

    Declare Type at TopInputMake Dictionary−+throttlePair 1 Key1Pair 1 ValuesteerPair 2 Key0Pair 2 ValueaimPair 3 KeyVector3.zAxisPair 3 ValueScript StartDeclare Local (input)InputLocalinputNameValue.aimPrintValue
    Three fields declared, and one of them read off a local of that type.
    luau
    export type Input = {
    	throttle: number,
    	steer: number,
    	aim: Vector3,
    }
    
    local input: Input = {
    	throttle = 1,
    	steer = 0,
    	aim = Vector3.zAxis,
    }
    print(input.aim)

    The result is typed as the field is. aim gives a Vector3 pin and throttle a number one, so what comes out wires into a Vector3 node without a Cast.

    A type written as Custom Luau — click its Definition to open the code editor — behaves as the rows do, as long as what you wrote is a table of named fields. This is the shape to reach for when a field's own type is more than a name — { Player }, Model?.

    The Inspector for a Declare Type whose shape is Custom Luau.

      Declare Type at TopShotMake Dictionary−+damagePair 1 Key25Pair 1 ValuefromPair 2 KeyVector3.zeroPair 2 ValueScript StartDeclare Local (shot)ShotLocalshotNameValue.damagePrintValue
      The same thing, written as Luau instead of entered as rows.
      luau
      export type Shot = { damage: number, from: Vector3 }
      
      local shot: Shot = { damage = 25, from = Vector3.zero }
      print(shot.damage)

      A dictionary type, a union, a function type: Luau understands each of them, and none has a field list anybody can write down. Get Member offers nothing and refuses a member on one — Get Field is the node, and the key is a pin you fill in or wire.

      The Inspector for a Declare Type holding a dictionary type.

        Declare Type at TopScoresMake Dictionary−+alicePair 1 Key12Pair 1 ValuebobPair 2 Key9Pair 2 ValueScript StartDeclare Local (scores)ScoresLocalscoresNameValueGet FieldObjectaliceFieldPrintValue
        A table keyed by name: the keys are the program's business, so the key is a pin.
        luau
        export type Scores = { [string]: number }
        
        local scores: Scores = { alice = 12, bob = 9 }
        print(scores.alice)

        A type a module exports

        A module's exported types are offered here too, under the name this graph writes them as: require Tank.Config as Config, and a value annotated Config.Tuning offers that type's fields. The fields are read from the graph that declares them, so renaming one there changes what is offered here.

        Two files, one idea

        Declare Type at TopTuningMake Dictionary−+turnRatePair 1 Key45Pair 1 Valueaccelera…Pair 2 Key5Pair 2 ValueModule Exports−+tuning
        The module: it declares the type, and returns a table with that value on it.
        Script StartRequire ModuleReplicated…InTank.Con…PathAsGet FieldObjecttuningFieldDeclare Local (tuning)Config.TuningLocaltuningNameValuetuning.turnRatePrintValue
        The graph that requires it: Get Field takes the key, Get Member reads the type's field.
        luau
        local ReplicatedStorage = game:GetService("ReplicatedStorage")
        local Config = require(ReplicatedStorage.Tank.Config)
        local tuning: Config.Tuning = Config.tuning
        print(tuning.turnRate)

        Both nodes appear, and which is which is the point. The module's own table is a value nothing here can describe — Roswaal does not read the other file's returns — so Config.tuning is a Get Field. What comes out is annotated as a type that is described, so turnRate off it is a Get Member.

        Info

        This list comes from the daemon reading the other graph. With no project open behind the browser it is empty; the name still compiles, and the compiler does not check it.

        A Roblox instance

        An instance's properties come from the engine, so they need no declaring. A node that names a class hands back that class — New Instance set to Part gives a Part — and a Part's properties are then what Get Member offers.

        Script StartpartInstancePartClass….AnchoredBranchTrueFalseCondition
        New Instance gives a Part; the pill reads its Anchored, which is a boolean.
        luau
        local part: Part = Instance.new("Part")
        if part.Anchored then
        end

        Inherited properties are there too: Name and Parent come from Instance, and a Part offers them alongside its own. Get Service gives the service's own class, and the Find First Child and Ancestor nodes follow their Class Name, so each of those leads straight into a member without a cast first.

        Warning

        The property list is offered in Roblox graphs only, and is as of this release. A newer property can be typed in and compiles the same.

        Finding one

        GestureWhat you get
        Drag a wire out of a typed pinThat type's members in the node menu, under their own heading. Picking one places a Get Member already wired
        Type input. in either node searchEvery member of everything this graph names — a variable, a local, a parameter. Picking one places the getter and the Get Member on it, wired
        Select a Get MemberIts Member in the Inspector, as a list of what the wired type declares

        When there is no list

        A table whose keys come and go while the program runs has no fixed members, and nothing offers any: that is Get Field, which takes the key as a pin and reads whatever is there. A key that is not a Luau name — a number, or a string with a space in it — is Get Field's as well, since t.my key is not something Luau can write.

        Warning

        A member the type does not have is refused, for a type this file declares. Roblox properties and other modules' types are not checked.

        Not reviewed yet