diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 20604a93b..a74d8a9ba 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -23,8 +23,14 @@ set(CMAKE_CXX_STANDARD_REQUIRED   YES)
 macro(add_example_executable name)
     project(${name})
 
+    set(options NOLINK)
+    set(oneValueArgs)
+    set(multiValueArgs SOURCES)
+    cmake_parse_arguments(ADD_EXAMPLE "${options}" "${oneValueArgs}"
+                          "${multiValueArgs}" ${ARGN})
+
     set(_Example_Sources
-        ${ARGN}
+        ${ADD_EXAMPLE_SOURCES}
     )
 
     #source_group("" FILES ${_Example_Sources})
@@ -69,7 +75,10 @@ macro(add_example_executable name)
 
     find_package(imgui REQUIRED)
     find_package(imgui_node_editor REQUIRED)
-    target_link_libraries(${name} PRIVATE imgui imgui_node_editor application)
+    target_link_libraries(${name} PRIVATE imgui application)
+    if (NOT ADD_EXAMPLE_NOLINK)
+        target_link_libraries(${name} PRIVATE imgui_node_editor)
+    endif()
 
     set(_ExampleBinDir ${CMAKE_BINARY_DIR}/bin)
 
@@ -132,3 +141,4 @@ add_subdirectory(simple-example)
 add_subdirectory(widgets-example)
 add_subdirectory(basic-interaction-example)
 add_subdirectory(blueprints-example)
+add_subdirectory(idtypes-example)
diff --git a/examples/application/source/renderer_ogl3.cpp b/examples/application/source/renderer_ogl3.cpp
index 72a7dfc32..9aba29d98 100644
--- a/examples/application/source/renderer_ogl3.cpp
+++ b/examples/application/source/renderer_ogl3.cpp
@@ -4,6 +4,7 @@
 
 # include "platform.h"
 # include <algorithm>
+# include <cstdint>
 
 # if PLATFORM(WINDOWS)
 #     define NOMINMAX
diff --git a/examples/basic-interaction-example/CMakeLists.txt b/examples/basic-interaction-example/CMakeLists.txt
index 4c79ebaac..2f70a3f33 100644
--- a/examples/basic-interaction-example/CMakeLists.txt
+++ b/examples/basic-interaction-example/CMakeLists.txt
@@ -1,3 +1,3 @@
 add_example_executable(basic-interaction-example
-    basic-interaction-example.cpp
-)
\ No newline at end of file
+    SOURCES basic-interaction-example.cpp
+)
diff --git a/examples/blueprints-example/CMakeLists.txt b/examples/blueprints-example/CMakeLists.txt
index 37ca1ec12..42a9c1552 100644
--- a/examples/blueprints-example/CMakeLists.txt
+++ b/examples/blueprints-example/CMakeLists.txt
@@ -1,4 +1,4 @@
-add_example_executable(blueprints-example
+add_example_executable(blueprints-example SOURCES
     blueprints-example.cpp
     utilities/builders.h
     utilities/drawing.h
diff --git a/examples/canvas-example/CMakeLists.txt b/examples/canvas-example/CMakeLists.txt
index 11f1c89a3..df31b6255 100644
--- a/examples/canvas-example/CMakeLists.txt
+++ b/examples/canvas-example/CMakeLists.txt
@@ -1,5 +1,5 @@
-add_example_executable(canvas-example
+add_example_executable(canvas-example SOURCES
     canvas-example.cpp
 )
 
-#target_link_libraries(Canvas PRIVATE imgui_canvas)
\ No newline at end of file
+#target_link_libraries(Canvas PRIVATE imgui_canvas)
diff --git a/examples/idtypes-example/CMakeLists.txt b/examples/idtypes-example/CMakeLists.txt
new file mode 100644
index 000000000..502108239
--- /dev/null
+++ b/examples/idtypes-example/CMakeLists.txt
@@ -0,0 +1,19 @@
+add_example_executable(idtypes-example NOLINK
+    SOURCES idtypes-example.cpp
+)
+
+set(CMAKE_CXX_STANDARD            14)
+set(CMAKE_CXX_STANDARD_REQUIRED   YES)
+
+add_library(imgui_node_editor_customids STATIC
+    ${IMGUI_NODE_EDITOR_SOURCES}
+)
+
+target_include_directories(imgui_node_editor_customids PUBLIC
+    ${IMGUI_NODE_EDITOR_ROOT_DIR}
+)
+
+target_link_libraries(imgui_node_editor_customids PUBLIC imgui)
+
+target_compile_definitions(imgui_node_editor_customids PUBLIC IMGUI_NODE_EDITOR_USER_CONFIG="${CMAKE_CURRENT_SOURCE_DIR}/edconfig.h")
+target_link_libraries(idtypes-example PRIVATE imgui_node_editor_customids)
diff --git a/examples/idtypes-example/edconfig.h b/examples/idtypes-example/edconfig.h
new file mode 100644
index 000000000..66048622b
--- /dev/null
+++ b/examples/idtypes-example/edconfig.h
@@ -0,0 +1,67 @@
+#ifndef EDCONFIG_H
+#define EDCONFIG_H
+
+#include <string>
+
+struct CustomNodeId
+{
+    std::string name;
+
+    CustomNodeId(const CustomNodeId &id) = default;
+    CustomNodeId &operator=(const CustomNodeId &id) = default;
+
+    bool operator<(const CustomNodeId &id) const;
+    bool operator==(const CustomNodeId &id) const;
+
+    std::string AsString() const;
+    static CustomNodeId FromString(const char *str, const char *end);
+    bool IsValid() const;
+
+    static const CustomNodeId Invalid;
+};
+
+struct CustomPinId
+{
+    enum class Direction {
+        In,
+        Out
+    };
+    std::string nodeName;
+    Direction direction;
+    int pinIndex;
+
+    CustomPinId(const CustomPinId &id) = default;
+    CustomPinId &operator=(const CustomPinId &id) = default;
+
+    bool operator<(const CustomPinId &id) const;
+    bool operator==(const CustomPinId &id) const;
+
+    std::string AsString() const;
+    static CustomPinId FromString(const char *str, const char *end);
+    bool IsValid() const;
+
+    static const CustomPinId Invalid;
+};
+
+struct CustomLinkId
+{
+    CustomPinId in, out;
+
+    CustomLinkId(const CustomLinkId &id) = default;
+    CustomLinkId &operator=(const CustomLinkId &id) = default;
+
+    bool operator<(const CustomLinkId &id) const;
+    bool operator==(const CustomLinkId &id) const;
+
+    std::string AsString() const;
+    static CustomLinkId FromString(const char *str, const char *end);
+    bool IsValid() const;
+
+    static const CustomLinkId Invalid;
+};
+
+#define IMGUI_NODE_EDITOR_CUSTOM_NODEID CustomNodeId
+#define IMGUI_NODE_EDITOR_CUSTOM_PINID CustomPinId
+#define IMGUI_NODE_EDITOR_CUSTOM_LINKID CustomLinkId
+
+#endif
diff --git a/examples/idtypes-example/idtypes-example.cpp b/examples/idtypes-example/idtypes-example.cpp
new file mode 100644
index 000000000..2fb4c5956
--- /dev/null
+++ b/examples/idtypes-example/idtypes-example.cpp
@@ -0,0 +1,212 @@
+# include <imgui.h>
+# include <imgui_node_editor.h>
+# include <application.h>
+# include <vector>
+# include <unordered_map>
+
+#include "edconfig.h"
+
+namespace ed = ax::NodeEditor;
+
+struct Pin
+{
+};
+
+struct Node
+{
+    std::vector<Pin> InPins;
+    std::vector<Pin> OutPins;
+};
+
+bool CustomNodeId::operator<(const CustomNodeId &id) const
+{
+    return name < id.name;
+}
+
+bool CustomNodeId::operator==(const CustomNodeId &id) const
+{
+    return name == id.name;
+}
+
+std::string CustomNodeId::AsString() const
+{
+    return name;
+}
+
+CustomNodeId CustomNodeId::FromString(const char *str, const char *end)
+{
+    return CustomNodeId{ std::string(str, end) };
+}
+
+bool CustomNodeId::IsValid() const
+{
+    return !name.empty();
+}
+
+const CustomNodeId CustomNodeId::Invalid = {};
+
+bool CustomPinId::operator<(const CustomPinId &id) const
+{
+    if (nodeName < id.nodeName) return true;
+    else if (nodeName > id.nodeName) return false;
+
+    if (int(direction) < int(id.direction)) return true;
+    else if (int(direction) > int(id.direction)) return false;
+
+    return pinIndex < id.pinIndex;
+}
+
+bool CustomPinId::operator==(const CustomPinId &id) const
+{
+    return nodeName == id.nodeName && direction == id.direction && pinIndex == id.pinIndex;
+}
+
+std::string CustomPinId::AsString() const
+{
+    return nodeName + ";" + std::to_string(int(direction)) + ";" + std::to_string(pinIndex);
+}
+
+CustomPinId CustomPinId::FromString(const char *str, const char *end)
+{
+    std::string string(str, end);
+    auto sep1 = string.find(';');
+
+    if (sep1 == 0 || sep1 == std::string::npos) {
+        return Invalid;
+    }
+
+    auto sep2 = string.find(';', sep1 + 1);
+    if (sep2 == sep1 + 1 || sep2 == std::string::npos) {
+        return Invalid;
+    }
+
+    char *e;
+    auto nodeName = std::string(string.data(), sep1);
+    int dir = std::strtol(string.data() + sep1 + 1, &e, 10);
+    if (dir > 1) {
+        return Invalid;
+    }
+
+    int pin = std::strtol(string.data() + sep2 + 1, &e, 10);
+    return CustomPinId{ nodeName, CustomPinId::Direction(dir), pin };
+}
+
+bool CustomPinId::IsValid() const
+{
+    return !nodeName.empty();
+}
+
+const CustomPinId CustomPinId::Invalid = {};
+
+
+bool CustomLinkId::operator<(const CustomLinkId &id) const
+{
+    if (in < id.in) return true;
+    if (in == id.in) return out < id.out;
+    return false;
+}
+
+bool CustomLinkId::operator==(const CustomLinkId &id) const
+{
+    return in == id.in && out == id.out;
+}
+
+std::string CustomLinkId::AsString() const
+{
+    return in.AsString() + "->" + out.AsString();
+}
+
+CustomLinkId CustomLinkId::FromString(const char *str, const char *end)
+{
+    std::string string(str, end);
+    auto sep = string.find("->");
+    if (sep == 0 || sep == std::string::npos) {
+        return Invalid;
+    }
+
+    auto in = CustomPinId::FromString(str, str + sep);
+    auto out = CustomPinId::FromString(str + sep + 2, end);
+    return { in, out };
+}
+
+bool CustomLinkId::IsValid() const
+{
+    return in.IsValid() && out.IsValid();
+}
+
+const CustomLinkId CustomLinkId::Invalid = {};
+
+struct Example:
+    public Application
+{
+    using Application::Application;
+
+    void OnStart() override
+    {
+        ed::Config config;
+        config.SettingsFile = "IdTypes.json";
+        m_Context = ed::CreateEditor(&config);
+
+        m_Nodes.insert({ "foo", Node{ { {}, {} }, { {} } } });
+        m_Nodes.insert({ "bar", Node{ { {} }, { {}, {}, {} } } });
+    }
+
+    void OnStop() override
+    {
+        ed::DestroyEditor(m_Context);
+    }
+
+    void OnFrame(float deltaTime) override
+    {
+        auto& io = ImGui::GetIO();
+
+        ImGui::Text("FPS: %.2f (%.2gms)", io.Framerate, io.Framerate ? 1000.0f / io.Framerate : 0.0f);
+
+        ImGui::Separator();
+
+        ed::SetCurrentEditor(m_Context);
+        ed::Begin("My Editor", ImVec2(0.0, 0.0f));
+        // Start drawing nodes.
+        for (auto &n: m_Nodes) {
+            ed::BeginNode(CustomNodeId{ n.first });
+                ImGui::TextUnformatted(n.first.c_str());
+
+                ImGui::BeginGroup();
+                for (int i = 0; i < n.second.InPins.size(); ++i) {
+                    ed::BeginPin(CustomPinId{ n.first, CustomPinId::Direction::In, i }, ed::PinKind::Input);
+                        ImGui::Text("-> In");
+                    ed::EndPin();
+                }
+                ImGui::EndGroup();
+
+                ImGui::SameLine();
+
+                ImGui::BeginGroup();
+                for (int i = 0; i < n.second.OutPins.size(); ++i) {
+                    ed::BeginPin(CustomPinId{ n.first, CustomPinId::Direction::Out, i }, ed::PinKind::Output);
+                        ImGui::Text("Out ->");
+                    ed::EndPin();
+                }
+                ImGui::EndGroup();
+
+            ed::EndNode();
+        }
+        ed::End();
+        ed::SetCurrentEditor(nullptr);
+
+	    //ImGui::ShowMetricsWindow();
+    }
+
+    ed::EditorContext* m_Context = nullptr;
+    std::unordered_map<std::string, Node> m_Nodes;
+};
+
+int Main(int argc, char** argv)
+{
+    Example exampe("IdTypes", argc, argv);
+
+    if (exampe.Create())
+        return exampe.Run();
+
+    return 0;
+}
diff --git a/examples/simple-example/CMakeLists.txt b/examples/simple-example/CMakeLists.txt
index 152330b68..69a61972c 100644
--- a/examples/simple-example/CMakeLists.txt
+++ b/examples/simple-example/CMakeLists.txt
@@ -1,3 +1,3 @@
-add_example_executable(simple-example
+add_example_executable(simple-example SOURCES
     simple-example.cpp
-)
\ No newline at end of file
+)
diff --git a/examples/widgets-example/CMakeLists.txt b/examples/widgets-example/CMakeLists.txt
index 1f1080a4f..3cbe5f5bd 100644
--- a/examples/widgets-example/CMakeLists.txt
+++ b/examples/widgets-example/CMakeLists.txt
@@ -1,3 +1,3 @@
-add_example_executable(widgets-example
+add_example_executable(widgets-example SOURCES
     widgets-example.cpp
-)
\ No newline at end of file
+)
diff --git a/imgui_node_editor.cpp b/imgui_node_editor.cpp
index 3e1539286..7be349d43 100644
--- a/imgui_node_editor.cpp
+++ b/imgui_node_editor.cpp
@@ -454,6 +454,79 @@ static void ImDrawList_PolyFillScanFlood(ImDrawList *draw, std::vector<ImVec2>*
 }
 */
 
+namespace ax {
+namespace NodeEditor {
+namespace Detail {
+
+std::string Serialize(NodeId n)
+{
+#ifdef IMGUI_NODE_EDITOR_CUSTOM_NODEID
+    return n.AsString();
+#else
+    return std::to_string(n.Get());
+#endif
+}
+
+std::string Serialize(PinId n)
+{
+#ifdef IMGUI_NODE_EDITOR_CUSTOM_PINID
+    return n.AsString();
+#else
+    return std::to_string(n.Get());
+#endif
+}
+
+std::string Serialize(LinkId n)
+{
+#ifdef IMGUI_NODE_EDITOR_CUSTOM_LINKID
+    return n.AsString();
+#else
+    return std::to_string(n.Get());
+#endif
+}
+
+std::string ObjectId::AsString() const
+{
+    switch (m_Type) {
+        case ObjectType::Pin: return Serialize(AsPinId());
+        case ObjectType::Node: return Serialize(AsNodeId());
+        case ObjectType::Link: return Serialize(AsLinkId());
+        case ObjectType::None: break;
+    }
+    return {};
+}
+
+NodeId DeserializeNodeId(const char *str, const char *end)
+{
+#ifdef IMGUI_NODE_EDITOR_CUSTOM_NODEID
+    return NodeId::FromString(str, end);
+#else
+    return NodeId(reinterpret_cast<void*>(strtoull(str, nullptr, 10)));
+#endif
+}
+
+PinId DeserializePinId(const char *str, const char *end)
+{
+#ifdef IMGUI_NODE_EDITOR_CUSTOM_PINID
+    return PinId::FromString(str, end);
+#else
+    return PinId(reinterpret_cast<void*>(strtoull(str, nullptr, 10)));
+#endif
+}
+
+LinkId DeserializeLinkId(const char *str, const char *end)
+{
+#ifdef IMGUI_NODE_EDITOR_CUSTOM_LINKID
+    return LinkId::FromString(str, end);
+#else
+    return LinkId(reinterpret_cast<void*>(strtoull(str, nullptr, 10)));
+#endif
+}
+
+}
+}
+}
+
 static void ImDrawList_AddBezierWithArrows(ImDrawList* drawList, const ImCubicBezierPoints& curve, float thickness,
     float startArrowSize, float startArrowWidth, float endArrowSize, float endArrowWidth,
     bool fill, ImU32 color, float strokeThickness, const ImVec2* startDirHint = nullptr, const ImVec2* endDirHint = nullptr)
@@ -1082,12 +1155,12 @@ ed::EditorContext::EditorContext(const ax::NodeEditor::Config* config)
     , m_DeleteItemsAction(this)
     , m_AnimationControllers{ &m_FlowAnimationController }
     , m_FlowAnimationController(this)
-    , m_HoveredNode(0)
-    , m_HoveredPin(0)
-    , m_HoveredLink(0)
-    , m_DoubleClickedNode(0)
-    , m_DoubleClickedPin(0)
-    , m_DoubleClickedLink(0)
+    , m_HoveredNode(NodeId::Invalid)
+    , m_HoveredPin(PinId::Invalid)
+    , m_HoveredLink(LinkId::Invalid)
+    , m_DoubleClickedNode(NodeId::Invalid)
+    , m_DoubleClickedPin(PinId::Invalid)
+    , m_DoubleClickedLink(LinkId::Invalid)
     , m_BackgroundClickButtonIndex(-1)
     , m_BackgroundDoubleClickButtonIndex(-1)
     , m_IsInitialized(false)
@@ -1242,12 +1315,12 @@ void ed::EditorContext::End()
     auto  control     = BuildControl(m_CurrentAction && m_CurrentAction->IsDragging()); // NavigateAction.IsMovingOverEdge()
     //auto& editorStyle = GetStyle();
 
-    m_HoveredNode             = control.HotNode && m_CurrentAction == nullptr ? control.HotNode->m_ID : 0;
-    m_HoveredPin              = control.HotPin  && m_CurrentAction == nullptr ? control.HotPin->m_ID  : 0;
-    m_HoveredLink             = control.HotLink && m_CurrentAction == nullptr ? control.HotLink->m_ID : 0;
-    m_DoubleClickedNode       = control.DoubleClickedNode ? control.DoubleClickedNode->m_ID : 0;
-    m_DoubleClickedPin        = control.DoubleClickedPin  ? control.DoubleClickedPin->m_ID  : 0;
-    m_DoubleClickedLink       = control.DoubleClickedLink ? control.DoubleClickedLink->m_ID : 0;
+    m_HoveredNode             = control.HotNode && m_CurrentAction == nullptr ? control.HotNode->m_ID : NodeId::Invalid;
+    m_HoveredPin              = control.HotPin  && m_CurrentAction == nullptr ? control.HotPin->m_ID  : PinId::Invalid;
+    m_HoveredLink             = control.HotLink && m_CurrentAction == nullptr ? control.HotLink->m_ID : LinkId::Invalid;
+    m_DoubleClickedNode       = control.DoubleClickedNode ? control.DoubleClickedNode->m_ID : NodeId::Invalid;
+    m_DoubleClickedPin        = control.DoubleClickedPin  ? control.DoubleClickedPin->m_ID  : PinId::Invalid;
+    m_DoubleClickedLink       = control.DoubleClickedLink ? control.DoubleClickedLink->m_ID : LinkId::Invalid;
     m_BackgroundClickButtonIndex       = control.BackgroundClickButtonIndex;
     m_BackgroundDoubleClickButtonIndex = control.BackgroundDoubleClickButtonIndex;
 
@@ -1581,7 +1654,7 @@ void ed::EditorContext::End()
     if (!m_CurrentAction && m_IsFirstFrame && !m_Settings.m_Selection.empty())
     {
         ClearSelection();
-        for (auto id : m_Settings.m_Selection)
+        for (auto &id : m_Settings.m_Selection)
             if (auto object = FindObject(id))
                 SelectObject(object);
     }
@@ -2105,7 +2178,7 @@ ed::Link* ed::EditorContext::FindLink(LinkId id)
     return FindItemIn(m_Links, id);
 }
 
-ed::Object* ed::EditorContext::FindObject(ObjectId id)
+ed::Object* ed::EditorContext::FindObject(const ObjectId &id)
 {
     if (id.IsNodeId())
         return FindNode(id.AsNodeId());
@@ -2367,16 +2440,14 @@ ed::Control ed::EditorContext::BuildControl(bool allowOffscreen)
     };
 
     // Emits invisible button and returns true if it is clicked.
-    auto emitInteractiveAreaEx = [&activeId](ObjectId id, const ImRect& rect, ImGuiButtonFlags extraFlags) -> int
+    auto emitInteractiveAreaEx = [&activeId](const std::string &idString, const ImRect& rect, ImGuiButtonFlags extraFlags) -> int
     {
-        char idString[33] = { 0 }; // itoa can output 33 bytes maximum
-        snprintf(idString, 32, "%p", id.AsPointer());
         ImGui::SetCursorScreenPos(rect.Min);
 
         // debug
         //if (id < 0) return ImGui::Button(idString, to_imvec(rect.size));
 
-        auto buttonIndex = invisibleButtonEx(idString, rect.GetSize(), extraFlags);
+        auto buttonIndex = invisibleButtonEx(idString.c_str(), rect.GetSize(), extraFlags);
 
         // #debug
         //ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(0, 255, 0, 64));
@@ -2387,15 +2458,15 @@ ed::Control ed::EditorContext::BuildControl(bool allowOffscreen)
         return buttonIndex;
     };
 
-    auto emitInteractiveArea = [&emitInteractiveAreaEx, extraFlags](ObjectId id, const ImRect& rect)
+    auto emitInteractiveArea = [&emitInteractiveAreaEx, extraFlags](const std::string &idString, const ImRect& rect)
     {
-        return emitInteractiveAreaEx(id, rect, extraFlags);
+        return emitInteractiveAreaEx(idString, rect, extraFlags);
     };
 
     // Check input interactions over area.
-    auto checkInteractionsInArea = [this, &emitInteractiveArea, &hotObject, &activeObject, &clickedObject, &doubleClickedObject](ObjectId id, const ImRect& rect, Object* object)
+    auto checkInteractionsInArea = [this, &emitInteractiveArea, &hotObject, &activeObject, &clickedObject, &doubleClickedObject](const std::string &idString, const ImRect& rect, Object* object)
     {
-        if (emitInteractiveArea(id, rect) >= 0)
+        if (emitInteractiveArea(idString, rect) >= 0)
             clickedObject = object;
         if (!doubleClickedObject && ImGui::IsMouseDoubleClicked(m_Config.DragButtonIndex) && ImGui::IsItemHovered())
             doubleClickedObject = object;
@@ -2421,14 +2492,14 @@ ed::Control ed::EditorContext::BuildControl(bool allowOffscreen)
         {
             if (!pin->m_IsLive) continue;
 
-            checkInteractionsInArea(pin->m_ID, pin->m_Bounds, pin);
+            checkInteractionsInArea(Serialize(pin->m_ID), pin->m_Bounds, pin);
         }
 
         // Check for interactions with node.
         if (node->m_Type == NodeType::Group)
         {
             // Node with a hole
-            ImGui::PushID(node->m_ID.AsPointer());
+            ImGui::PushID(Serialize(node->m_ID).c_str());
 
             static const NodeRegion c_Regions[] =
             {
@@ -2448,13 +2519,13 @@ ed::Control ed::EditorContext::BuildControl(bool allowOffscreen)
                 auto bounds = node->GetRegionBounds(region);
                 if (ImRect_IsEmpty(bounds))
                     continue;
-                checkInteractionsInArea(NodeId(static_cast<int>(region)), bounds, node);
+                checkInteractionsInArea(std::to_string(static_cast<int>(region)), bounds, node);
             }
 
             ImGui::PopID();
         }
         else
-            checkInteractionsInArea(node->m_ID, node->m_Bounds, node);
+            checkInteractionsInArea(Serialize(node->m_ID), node->m_Bounds, node);
     }
 
     // Links are not regular widgets and must be done manually since
@@ -2494,7 +2565,7 @@ ed::Control ed::EditorContext::BuildControl(bool allowOffscreen)
     };
 
     // Check for interaction with background.
-    auto backgroundClickButonIndex        = emitInteractiveAreaEx(NodeId(0), editorRect, backgroundExtraFlags);
+    auto backgroundClickButonIndex        = emitInteractiveAreaEx("background", editorRect, backgroundExtraFlags);
     auto backgroundDoubleClickButtonIndex = isMouseDoubleClickOverBackground();
     auto isBackgroundActive               = ImGui::IsItemActive();
     auto isBackgroundHot                  = !hotObject;
@@ -2610,13 +2681,13 @@ void ed::EditorContext::ShowMetrics(const Control& control)
     ImGui::Text("Live Nodes: %d", liveNodeCount);
     ImGui::Text("Live Pins: %d", livePinCount);
     ImGui::Text("Live Links: %d", liveLinkCount);
-    ImGui::Text("Hot Object: %s (%p)", getHotObjectName(), control.HotObject ? control.HotObject->ID().AsPointer() : nullptr);
+    ImGui::Text("Hot Object: %s (%s)", getHotObjectName(), control.HotObject ? control.HotObject->ID().AsString().c_str() : nullptr);
     if (auto node = control.HotObject ? control.HotObject->AsNode() : nullptr)
     {
         ImGui::SameLine();
         ImGui::Text("{ x=%g y=%g w=%g h=%g }", node->m_Bounds.Min.x, node->m_Bounds.Min.y, node->m_Bounds.GetWidth(), node->m_Bounds.GetHeight());
     }
-    ImGui::Text("Active Object: %s (%p)", getActiveObjectName(), control.ActiveObject ? control.ActiveObject->ID().AsPointer() : nullptr);
+    ImGui::Text("Active Object: %s (%s)", getActiveObjectName(), control.ActiveObject ? control.ActiveObject->ID().AsString().c_str() : nullptr);
     if (auto node = control.ActiveObject ? control.ActiveObject->AsNode() : nullptr)
     {
         ImGui::SameLine();
@@ -2779,9 +2850,9 @@ std::string ed::Settings::Serialize()
 {
     json::value result;
 
-    auto serializeObjectId = [](ObjectId id)
+    auto serializeObjectId = [](const ObjectId &id)
     {
-        auto value = std::to_string(reinterpret_cast<uintptr_t>(id.AsPointer()));
+        auto value = id.AsString();
         switch (id.Type())
         {
             default:
@@ -2849,16 +2920,16 @@ bool ed::Settings::Parse(const std::string& string, Settings& settings)
     {
         auto separator = str.find_first_of(':');
         auto idStart   = str.c_str() + ((separator != std::string::npos) ? separator + 1 : 0);
-        auto id        = reinterpret_cast<void*>(strtoull(idStart, nullptr, 10));
+        auto *end      = str.c_str() + str.size();
         if (str.compare(0, separator, "node") == 0)
-            return ObjectId(NodeId(id));
+            return ObjectId(DeserializeNodeId(idStart, end));
         else if (str.compare(0, separator, "link") == 0)
-            return ObjectId(LinkId(id));
+            return ObjectId(DeserializeLinkId(idStart, end));
         else if (str.compare(0, separator, "pin") == 0)
-            return ObjectId(PinId(id));
+            return ObjectId(DeserializePinId(idStart, end));
         else
             // fallback to old format
-            return ObjectId(NodeId(id)); //return ObjectId();
+            return ObjectId(DeserializeNodeId(idStart, end)); //return ObjectId();
     };
 
     //auto& settingsObject = settingsValue.get<json::object>();
@@ -3791,7 +3862,7 @@ void ed::SizeAction::ShowMetrics()
 
     ImGui::Text("%s:", GetName());
     ImGui::Text("    Active: %s", m_IsActive ? "yes" : "no");
-    ImGui::Text("    Node: %s (%p)", getObjectName(m_SizedNode), m_SizedNode ? m_SizedNode->m_ID.AsPointer() : nullptr);
+    ImGui::Text("    Node: %s (%s)", getObjectName(m_SizedNode), m_SizedNode ? Serialize(m_SizedNode->m_ID).c_str() : nullptr);
     if (m_SizedNode && m_IsActive)
     {
         ImGui::Text("    Bounds: { x=%g y=%g w=%g h=%g }", m_SizedNode->m_Bounds.Min.x, m_SizedNode->m_Bounds.Min.y, m_SizedNode->m_Bounds.GetWidth(), m_SizedNode->m_Bounds.GetHeight());
@@ -3997,7 +4068,7 @@ void ed::DragAction::ShowMetrics()
 
     ImGui::Text("%s:", GetName());
     ImGui::Text("    Active: %s", m_IsActive ? "yes" : "no");
-    ImGui::Text("    Node: %s (%p)", getObjectName(m_DraggedObject), m_DraggedObject ? m_DraggedObject->ID().AsPointer() : nullptr);
+    ImGui::Text("    Node: %s (%s)", getObjectName(m_DraggedObject), m_DraggedObject ? m_DraggedObject->ID().AsString().c_str() : nullptr);
 }
 
 
@@ -4594,12 +4665,12 @@ bool ed::CreateItemAction::Process(const Control& control)
     {
         const auto draggingFromSource = (m_DraggedPin->m_Kind == PinKind::Output);
 
-        ed::Pin cursorPin(Editor, 0, draggingFromSource ? PinKind::Input : PinKind::Output);
+        ed::Pin cursorPin(Editor, PinId::Invalid, draggingFromSource ? PinKind::Input : PinKind::Output);
         cursorPin.m_Pivot    = ImRect(ImGui::GetMousePos(), ImGui::GetMousePos());
         cursorPin.m_Dir      = -m_DraggedPin->m_Dir;
         cursorPin.m_Strength =  m_DraggedPin->m_Strength;
 
-        ed::Link candidate(Editor, 0);
+        ed::Link candidate(Editor, LinkId::Invalid);
         candidate.m_Color    = m_LinkColor;
         candidate.m_StartPin = draggingFromSource ? m_DraggedPin : &cursorPin;
         candidate.m_EndPin   = draggingFromSource ? &cursorPin : m_DraggedPin;
@@ -4842,7 +4913,7 @@ ed::CreateItemAction::Result ed::CreateItemAction::QueryNode(PinId* pinId)
     if (!m_InActive || m_CurrentStage == None || m_ItemType != Node)
         return Indeterminate;
 
-    *pinId = m_LinkStart ? m_LinkStart->m_ID : 0;
+    *pinId = m_LinkStart ? m_LinkStart->m_ID : PinId::Invalid;
 
     Editor->SetUserContext(true);
 
@@ -5006,7 +5077,8 @@ bool ed::DeleteItemsAction::QueryLink(LinkId* linkId, PinId* startId, PinId* end
     if (!QueryItem(&objectId, Link))
         return false;
 
-    if (auto id = objectId.AsLinkId())
+    auto id = objectId.AsLinkId();
+    if (id.IsValid())
         *linkId = id;
     else
         return false;
@@ -5029,7 +5101,8 @@ bool ed::DeleteItemsAction::QueryNode(NodeId* nodeId)
     if (!QueryItem(&objectId, Node))
         return false;
 
-    if (auto id = objectId.AsNodeId())
+    auto id = objectId.AsNodeId();
+    if (id.IsValid())
         *nodeId = id;
     else
         return false;
diff --git a/imgui_node_editor.h b/imgui_node_editor.h
index a173cde30..a895a66bd 100644
--- a/imgui_node_editor.h
+++ b/imgui_node_editor.h
@@ -24,6 +24,9 @@
 # define IMGUI_NODE_EDITOR_VERSION      "0.9.2"
 # define IMGUI_NODE_EDITOR_VERSION_NUM  000902
 
+#ifdef IMGUI_NODE_EDITOR_USER_CONFIG
+#include IMGUI_NODE_EDITOR_USER_CONFIG
+#endif
 
 //------------------------------------------------------------------------------
 namespace ax {
@@ -31,9 +34,23 @@ namespace NodeEditor {
 
 
 //------------------------------------------------------------------------------
+#ifdef IMGUI_NODE_EDITOR_CUSTOM_NODEID
+using NodeId = IMGUI_NODE_EDITOR_CUSTOM_NODEID;
+#else
 struct NodeId;
+#endif
+
+#ifdef IMGUI_NODE_EDITOR_CUSTOM_LINKID
+using LinkId = IMGUI_NODE_EDITOR_CUSTOM_LINKID;
+#else
 struct LinkId;
+#endif
+
+#ifdef IMGUI_NODE_EDITOR_CUSTOM_PINID
+using PinId = IMGUI_NODE_EDITOR_CUSTOM_PINID;
+#else
 struct PinId;
+#endif
 
 
 //------------------------------------------------------------------------------
@@ -465,7 +482,11 @@ struct SafePointerType
     template <typename T = void> explicit SafePointerType(T* ptr): SafePointerType(reinterpret_cast<uintptr_t>(ptr)) {}
     template <typename T = void> T* AsPointer() const { return reinterpret_cast<T*>(this->Get()); }
 
+    inline bool IsValid() const { return *this != Invalid; }
+
     explicit operator bool() const { return *this != Invalid; }
+
+    inline bool operator<(Tag o) const { return AsPointer() < o.AsPointer(); }
 };
 
 template <typename Tag>
@@ -485,20 +506,26 @@ inline bool operator!=(const SafePointerType<Tag>& lhs, const SafePointerType<Ta
 
 } // namespace Details
 
+#ifndef IMGUI_NODE_EDITOR_CUSTOM_NODEID
 struct NodeId final: Details::SafePointerType<NodeId>
 {
     using SafePointerType::SafePointerType;
 };
+#endif
 
+#ifndef IMGUI_NODE_EDITOR_CUSTOM_LINKID
 struct LinkId final: Details::SafePointerType<LinkId>
 {
     using SafePointerType::SafePointerType;
 };
+#endif
 
+#ifndef IMGUI_NODE_EDITOR_CUSTOM_PINID
 struct PinId final: Details::SafePointerType<PinId>
 {
     using SafePointerType::SafePointerType;
 };
+#endif
 
 
 //------------------------------------------------------------------------------
@@ -507,4 +534,4 @@ struct PinId final: Details::SafePointerType<PinId>
 
 
 //------------------------------------------------------------------------------
-# endif // __IMGUI_NODE_EDITOR_H__
\ No newline at end of file
+# endif // __IMGUI_NODE_EDITOR_H__
diff --git a/imgui_node_editor_api.cpp b/imgui_node_editor_api.cpp
index c8c7c3ff7..2208970f7 100644
--- a/imgui_node_editor_api.cpp
+++ b/imgui_node_editor_api.cpp
@@ -31,7 +31,7 @@ static int BuildIdList(C& container, I* list, int listSize, F&& accept)
 
             if (accept(object))
             {
-                list[count] = I(object->ID().AsPointer());
+                list[count] = I(object->ID());
                 ++count;
                 --listSize;}
         }
diff --git a/imgui_node_editor_internal.h b/imgui_node_editor_internal.h
index 4c57725f0..ed7519c83 100644
--- a/imgui_node_editor_internal.h
+++ b/imgui_node_editor_internal.h
@@ -30,7 +30,7 @@
 
 # include <vector>
 # include <string>
-
+# include <variant>
 
 //------------------------------------------------------------------------------
 namespace ax {
@@ -147,23 +147,55 @@ using ax::NodeEditor::NodeId;
 using ax::NodeEditor::PinId;
 using ax::NodeEditor::LinkId;
 
-struct ObjectId final: Details::SafePointerType<ObjectId>
+struct ObjectId final
 {
-    using Super = Details::SafePointerType<ObjectId>;
-    using Super::Super;
-
-    ObjectId():                  Super(Invalid),              m_Type(ObjectType::None)   {}
-    ObjectId(PinId  pinId):      Super(pinId.AsPointer()),    m_Type(ObjectType::Pin)    {}
-    ObjectId(NodeId nodeId):     Super(nodeId.AsPointer()),   m_Type(ObjectType::Node)   {}
-    ObjectId(LinkId linkId):     Super(linkId.AsPointer()),   m_Type(ObjectType::Link)   {}
+    ObjectId():                  m_Type(ObjectType::None) {}
+    ObjectId(PinId  pinId):      m_Type(ObjectType::Pin)  { new (&m_Id.Pin) PinId(std::move(pinId)); }
+    ObjectId(NodeId nodeId):     m_Type(ObjectType::Node) { new (&m_Id.Node) NodeId(std::move(nodeId)); }
+    ObjectId(LinkId linkId):     m_Type(ObjectType::Link) { new (&m_Id.Link) LinkId(std::move(linkId)); }
+    ObjectId(ObjectId &&o): m_Type(o.m_Type) {
+        switch (m_Type) {
+            case ObjectType::Pin:
+                new (&m_Id.Pin) PinId(std::move(o.m_Id.Pin));
+                break;
+            case ObjectType::Node:
+                new (&m_Id.Node) NodeId(std::move(o.m_Id.Node));
+                break;
+            case ObjectType::Link:
+                new (&m_Id.Link) LinkId(std::move(o.m_Id.Link));
+                break;
+            case ObjectType::None:
+                break;
+        }
+    }
+    ObjectId(const ObjectId &o): m_Type(o.m_Type) {
+        switch (m_Type) {
+            case ObjectType::Pin:
+                new (&m_Id.Pin) PinId(o.m_Id.Pin);
+                break;
+            case ObjectType::Node:
+                new (&m_Id.Node) NodeId(o.m_Id.Node);
+                break;
+            case ObjectType::Link:
+                new (&m_Id.Link) LinkId(o.m_Id.Link);
+                break;
+            case ObjectType::None:
+                break;
+        }
+    }
+    ~ObjectId() {
+        clearId();
+    }
 
     explicit operator PinId()    const { return AsPinId();    }
     explicit operator NodeId()   const { return AsNodeId();   }
     explicit operator LinkId()   const { return AsLinkId();   }
 
-    PinId    AsPinId()    const { IM_ASSERT(IsPinId());    return PinId(AsPointer());    }
-    NodeId   AsNodeId()   const { IM_ASSERT(IsNodeId());   return NodeId(AsPointer());   }
-    LinkId   AsLinkId()   const { IM_ASSERT(IsLinkId());   return LinkId(AsPointer());   }
+    PinId    AsPinId()    const { IM_ASSERT(IsPinId());    return m_Id.Pin;    }
+    NodeId   AsNodeId()   const { IM_ASSERT(IsNodeId());   return m_Id.Node;   }
+    LinkId   AsLinkId()   const { IM_ASSERT(IsLinkId());   return m_Id.Link;   }
+
+    std::string AsString() const;
 
     bool IsPinId()    const { return m_Type == ObjectType::Pin;    }
     bool IsNodeId()   const { return m_Type == ObjectType::Node;   }
@@ -171,8 +203,86 @@ struct ObjectId final: Details::SafePointerType<ObjectId>
 
     ObjectType Type() const { return m_Type; }
 
+    bool operator==(const ObjectId &o) const {
+        if (m_Type != o.m_Type) {
+            return false;
+        }
+
+        switch (m_Type) {
+            case ObjectType::Pin: return m_Id.Pin == o.m_Id.Pin;
+            case ObjectType::Node: return m_Id.Node == o.m_Id.Node;
+            case ObjectType::Link: return m_Id.Link == o.m_Id.Link;
+            case ObjectType::None: break;
+        }
+        return true;
+    }
+
+    ObjectId &operator=(ObjectId &&o) {
+        clearId();
+
+        m_Type = o.m_Type;
+        switch (m_Type) {
+            case ObjectType::Pin:
+                new (&m_Id.Pin) PinId(std::move(o.m_Id.Pin));
+                break;
+            case ObjectType::Node:
+                new (&m_Id.Node) NodeId(std::move(o.m_Id.Node));
+                break;
+            case ObjectType::Link:
+                new (&m_Id.Link) LinkId(std::move(o.m_Id.Link));
+                break;
+            case ObjectType::None:
+                break;
+        }
+        return *this;
+    }
+    ObjectId &operator=(const ObjectId &o) {
+        clearId();
+
+        m_Type = o.m_Type;
+        switch (m_Type) {
+            case ObjectType::Pin:
+                new (&m_Id.Pin) PinId(o.m_Id.Pin);
+                break;
+            case ObjectType::Node:
+                new (&m_Id.Node) NodeId(o.m_Id.Node);
+                break;
+            case ObjectType::Link:
+                new (&m_Id.Link) LinkId(o.m_Id.Link);
+                break;
+            case ObjectType::None:
+                break;
+        }
+        return *this;
+    }
+
 private:
+    void clearId()
+    {
+        switch (m_Type) {
+            case ObjectType::Pin:
+                m_Id.Pin.~PinId();
+                break;
+            case ObjectType::Node:
+                m_Id.Node.~NodeId();
+                break;
+            case ObjectType::Link:
+                m_Id.Link.~LinkId();
+                break;
+            case ObjectType::None:
+                break;
+        }
+        m_Type = ObjectType::None;
+    }
+
     ObjectType m_Type;
+    union Id {
+        Id() {}
+        ~Id() {}
+        NodeId Node;
+        PinId Pin;
+        LinkId Link;
+    } m_Id;
 };
 
 struct EditorContext;
@@ -195,7 +305,7 @@ struct ObjectWrapper
 
     bool operator<(const ObjectWrapper& rhs) const
     {
-        return m_ID.AsPointer() < rhs.m_ID.AsPointer();
+        return m_ID < rhs.m_ID;
     }
 };
 
@@ -1381,7 +1491,7 @@ struct EditorContext
     Node*   FindNode(NodeId id);
     Pin*    FindPin(PinId id);
     Link*   FindLink(LinkId id);
-    Object* FindObject(ObjectId id);
+    Object* FindObject(const ObjectId &id);
 
     Node*  GetNode(NodeId id);
     Pin*   GetPin(PinId id, PinKind kind);
diff --git a/misc/cmake-modules/Findimgui_node_editor.cmake b/misc/cmake-modules/Findimgui_node_editor.cmake
index a8c295f7c..b70c37bb7 100644
--- a/misc/cmake-modules/Findimgui_node_editor.cmake
+++ b/misc/cmake-modules/Findimgui_node_editor.cmake
@@ -36,6 +36,8 @@ target_include_directories(imgui_node_editor PUBLIC
     ${IMGUI_NODE_EDITOR_ROOT_DIR}
 )
 
+set(IMGUI_NODE_EDITOR_SOURCES ${_imgui_node_editor_Sources} PARENT_SCOPE)
+
 target_link_libraries(imgui_node_editor PUBLIC imgui)
 
 source_group(TREE ${IMGUI_NODE_EDITOR_ROOT_DIR} FILES ${_imgui_node_editor_Sources})