How to programmatically add empties?

Hi, I want to write a plugin that allows me to procedurally generate a world based on a graph of nodes with xyz positions (i.e. can be place in a scene) as well as some attached metadata (biome information, references to connected nodes, etc), but am already failing with how to model/implement that node graph. The best recommendation I got on IRC was using empties for the nodes, but when I try adding an empty to a scene, I get RuntimeError: Error: ID type 'OBJECT' is not valid for an object.

My code currently looks like this:

    bl_info = {
        "name": "collator",
        "author": "phryk",
        "version": (0, 0),
        "blender": (2, 80, 0),
        "location": "View3D > Add > Mesh > New PoI",
        "description": "Adds a new Point of Interest",
        "warning": "",
        "doc_url": "",
        "category": "Add Mesh",
    import bpy
    from bpy.types import Operator
    from bpy.props import FloatVectorProperty
    from bpy_extras.object_utils import AddObjectHelper, object_data_add
    from mathutils import Vector
    def add_object(self, context):
        poi ="New Point of Interest", None)
        poi.empty_display_type = 'PLAIN_AXES'
        poi.location = Vector([0,0,0])
        object_data_add(context, poi, operator=self)
    class OBJECT_OT_add_object(Operator, AddObjectHelper):
        """Create a new Point of Interest"""
        bl_idname = "mesh.add_poi"
        bl_label = "Add Point of Interest"
        bl_options = {'REGISTER', 'UNDO'}
        scale: FloatVectorProperty(
            default=(1.0, 1.0, 1.0),
        def execute(self, context):
            add_object(self, context)
            return {'FINISHED'}
    # Registration
    def add_object_button(self, context):
            text="Point of Interest",
    # This allows you to right click on a button and link to documentation
    def add_object_manual_map():
        url_manual_prefix = ""
        url_manual_mapping = (
            ("bpy.ops.mesh.add_object", "scene_layout/object/types.html"),
        return url_manual_prefix, url_manual_mapping
    def register():
    def unregister():
    if __name__ == "__main__":

And the generated traceback like this:

    Python: Traceback (most recent call last):
      File "/home/phryk/devel/gfx/blender/poi.blend/Text", line 45, in execute
      File "/home/phryk/devel/gfx/blender/poi.blend/Text", line 27, in add_object
      File "/usr/local/share/blender/2.91/scripts/modules/bpy_extras/", line 127, in object_data_add
        obj_new =, obdata)
    RuntimeError: Error: ID type 'OBJECT' is not valid for an object
    location: <unknown location>:-1

I’m not sure I’m interpreting this right – does this mean adding an empty just isn’t valid? If so, what would be a good fit for an object type to use instead?

bpy.ops.object.empty_add() adds an empty at the current 3d-cursor, bpy.ops.object.empty_add(location=(1,1,1)) adds another one at (1,1,1).

Just try it in the Scripting workspace via the Console.

empty = bpy.context.object = "ThatsMyEmpty"

renames the last one added to “ThatsMyEmpty”.