I wanted to use _get_property_list() instead of exports because I wanted to better organize my properties, but I can't manage to set it up correctly: every time that I change the script file, the properties reset to the default value.

This is approximately what I've done:

tool
extends Node2D

var my_prop: String

# ...

func _get_property_list():
	var properties = []
	
	properties.append({
		name = "Custom Category",
		type = TYPE_NIL,
		usage = PROPERTY_USAGE_CATEGORY
	})
	
	properties.append({
		name = "my_prop",
		type = TYPE_STRING,
		usage = PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_EDITOR
	})
	
	return properties

func _set(property: String, value) -> bool:
	var success: bool = true
	
	match property:
		"my_prop":
			door_connector_id = value
		_:
			success = false
	
	if success:
		property_list_changed_notify()
	
	return success


func _get(property: String):
	var value = null
	
	match property:
		"my_prop":
			value = my_prop
	
	return value

If I change this file, adding code, my_prop resets to an empty string in the editor. What am I doing wrong?

This seems like a bad design, and a hack. I wouldn't recommend it. _get_property_list() is probably used internally by the engine, and when it gets called you are resetting properties to an empty array. So I'm not sure that makes sense. But it doesn't seem like a good way to do it anyway.

@cybereality Is this never actually done? Because I have defined nodes that inherit from other custom nodes, and they have their own properties. So using exports is pretty cumbersome, because it stacks the properties without categories or similar, and I would really like that. But I understand that it's not a good design using _get_property_list().

I mean, I think you could get it to work. It just seems like an unintended feature. The main problem is that you are setting your properties to an empty array when the method is called, which is probably why they are being reset.

So, you mean, I should do something like this?

# ...

var my_prop: String
var properties: Array = [
	{
		name = "Custom Category",
		type = TYPE_NIL,
		usage = PROPERTY_USAGE_CATEGORY
	},
	{
		name = "my_prop",
		type = TYPE_STRING,
		usage = PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_EDITOR
	}
]

func _get_property_list():
	return properties

## _set() as above

## _get() as above

Because I continue to have the same problem.

Looking at the code, I do not see anything right off that would indicate why it's not working. Maybe the custom category is causing the issue?

I'm not sure if it will help, but here's a snippet of how I've used _GetPropertiesList in C# for my IK plugin:

public override bool _Set(string property, object value)
    {
        // ===========================================
        // ===== AUTOMATION
        if (property == "settings/auto_get_twisted_skeleton") {
            // Code to set the skeleton here
            PropertyListChangedNotify();
            return true;
        }
        else if (property == "settings/path_to_twisted_skeleton") {
            path_to_twisted_skeleton = (NodePath)value;
            return true;
        }
        else if (property == "settings/auto_calcualte_bone_length") {
            auto_calcualte_bone_length = (bool)value;
            // Code to calculate the bone length (if true) here
            return true;
        }
        // ===========================================
        // ===== BONE DATA
        if (property == "bone_data/bone_name") {
            bone_name = (string)value;
            // Code to set the bone name, get the id, etc, here
            PropertyListChangedNotify();
            return true;
        }
        else if (property == "bone_data/bone_id") {
            bone_id = (int)value;
            if (bone_id <= -1) {
                bone_id = -1;
                PropertyListChangedNotify();
                return true;
            }
            // Code to set the bone id, name, etc, here
            PropertyListChangedNotify();
            return true;
        }
        // more properties defined - just removed for this example
        // ===========================================

        // I'm not totally sure if this is needed, but I found it was sometimes needed to get normal node properties, ones from built-in nodes, working correctly in the inspector
        try {
            return base._Set(property, value);
        } catch {
            return false;
        }
    }

    public override object _Get(string property)
    {
        // ===========================================
        // ===== AUTOMATION
        if (property == "settings/auto_get_twisted_skeleton") {
            return auto_get_twisted_skeleton;
        }
        else if (property == "settings/path_to_twisted_skeleton") {
            return path_to_twisted_skeleton;
        }
        else if (property == "settings/auto_calcualte_bone_length") {
            return auto_calcualte_bone_length;
        }
        // ===========================================
        // ===== BONE DATA
        if (property == "bone_data/bone_name") {
            return bone_name;
        }
        else if (property == "bone_data/bone_id") {
            return bone_id;
        }
	// more properties defined - just removed for this example
        // ===========================================
        
        try {
            return base._Get(property);
        } catch {
            return false;
        }
    }

    public override Godot.Collections.Array _GetPropertyList()
    {
        Godot.Collections.Array list = new Godot.Collections.Array();
        Godot.Collections.Dictionary tmp_dict;

        // ===========================================
        // ===== AUTOMATION
        tmp_dict = new Godot.Collections.Dictionary();
        tmp_dict.Add("name", "settings/auto_get_twisted_skeleton");
        tmp_dict.Add("type", Variant.Type.Bool);
        tmp_dict.Add("hint", PropertyHint.None);
        tmp_dict.Add("usage", PropertyUsageFlags.Default);
        list.Add(tmp_dict);

        if (auto_get_twisted_skeleton == false) {
            tmp_dict = new Godot.Collections.Dictionary();
            tmp_dict.Add("name", "settings/path_to_twisted_skeleton");
            tmp_dict.Add("type", Variant.Type.NodePath);
            tmp_dict.Add("hint", PropertyHint.ResourceType);
            tmp_dict.Add("hint_string", "Skeleton");
            tmp_dict.Add("usage", PropertyUsageFlags.Default);
            list.Add(tmp_dict);
        }

        tmp_dict = new Godot.Collections.Dictionary();
        tmp_dict.Add("name", "settings/auto_calcualte_bone_length");
        tmp_dict.Add("type", Variant.Type.Bool);
        tmp_dict.Add("hint", PropertyHint.None);
        tmp_dict.Add("usage", PropertyUsageFlags.Default);
        list.Add(tmp_dict);

        // ===========================================
        // ===== BONE DATA
        tmp_dict = new Godot.Collections.Dictionary();
        tmp_dict.Add("name", "bone_data/bone_name");
        tmp_dict.Add("type", Variant.Type.String);
        tmp_dict.Add("hint", PropertyHint.None);
        tmp_dict.Add("usage", PropertyUsageFlags.Default);
        list.Add(tmp_dict);

        tmp_dict = new Godot.Collections.Dictionary();
        tmp_dict.Add("name", "bone_data/bone_id");
        tmp_dict.Add("type", Variant.Type.Int);
        tmp_dict.Add("hint", PropertyHint.None);
        tmp_dict.Add("usage", PropertyUsageFlags.Default);
        list.Add(tmp_dict);

	// More properties defined below, but they use the same format	
        // ===========================================

        return list;
    }

I've removed a little of the code that is not relevant (and to fit the max character limit for posting), but that's how I've used it and it seems to save fine in the Godot editor. I'm not sure why your code is not working though, it seems pretty similar to how I've done it.

@TwistedTwigleg Thanks for the answer, looking at your code I was able to understand why mine was not working as intended. It was a silly mistake.

You used as usage PropertyUsageFlags.Default, while I was using PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_EDITOR. Using PROPERTY_USAGE_DEFAULT ultimately solves the problem.

It was my first time doing this, I was referencing some code and used some flags he was using without really understand their use.

Thanks again.

Awesome! I'm glad you were able to figure it out :smile:

a year later