Core generator distinguishes which fields need to be stored or not (via encoded flag).
diff --git a/dev/core_generator/lib/src/definition.dart b/dev/core_generator/lib/src/definition.dart
index 6951988..fdfbea2 100644
--- a/dev/core_generator/lib/src/definition.dart
+++ b/dev/core_generator/lib/src/definition.dart
@@ -165,10 +165,17 @@
         code.writeln('static const uint16_t ${property.name}PropertyKey = '
             '${property.key.intValue};');
       }
-      code.writeln('private:');
+      if (properties.any((prop) => !prop.isEncoded)) {
+        code.writeln('private:');
+      }
 
       // Write fields.
       for (final property in properties) {
+        if (property.isEncoded) {
+          // Encoded properties don't store data, it's up to the implementation
+          // to decode and store what it needs.
+          continue;
+        }
         code.writeln('${property.type.cppName} m_${property.capitalizedName}');
 
         var initialize = property.initialValueRuntime ??
@@ -186,19 +193,37 @@
       // Write getter/setters.
       code.writeln('public:');
       for (final property in properties) {
-        code.writeln((property.isVirtual ? 'virtual' : 'inline') +
-            ' ${property.type.cppGetterName} ${property.name}() const ' +
-            (property.isGetOverride ? 'override' : '') +
-            '{ return m_${property.capitalizedName}; }');
+        if (property.isEncoded) {
+          // Encoded properties just have a pure virtual decoder that needs to
+          // be implemented. Also requires an implemention of copyPropertyName
+          // as that will no longer automatically be copied by the generated
+          // code.
+          code.writeln((property.isSetOverride ? '' : 'virtual ') +
+              'void decode${property.capitalizedName}' +
+              '(${property.type.cppName} value) ' +
+              (property.isSetOverride ? 'override' : '') +
+              '= 0;');
+          code.writeln((property.isSetOverride ? '' : 'virtual ') +
+              'void copy${property.capitalizedName}' +
+              '(const ${_name}Base& object) ' +
+              (property.isSetOverride ? 'override' : '') +
+              '= 0;');
+        } else {
+          code.writeln((property.isVirtual ? 'virtual' : 'inline') +
+              ' ${property.type.cppGetterName} ${property.name}() const ' +
+              (property.isGetOverride ? 'override' : '') +
+              '{ return m_${property.capitalizedName}; }');
 
-        code.writeln('void ${property.name}(${property.type.cppName} value) ' +
-            (property.isSetOverride ? 'override' : '') +
-            '{'
-                'if(m_${property.capitalizedName} == value)'
-                '{return;}'
-                'm_${property.capitalizedName} = value;'
-                '${property.name}Changed();'
-                '}');
+          code.writeln(
+              'void ${property.name}(${property.type.cppName} value) ' +
+                  (property.isSetOverride ? 'override' : '') +
+                  '{'
+                      'if(m_${property.capitalizedName} == value)'
+                      '{return;}'
+                      'm_${property.capitalizedName} = value;'
+                      '${property.name}Changed();'
+                      '}');
+        }
 
         code.writeln();
       }
@@ -211,8 +236,12 @@
     if (properties.isNotEmpty || _extensionOf == null) {
       code.writeln('void copy(const ${_name}Base& object) {');
       for (final property in properties) {
-        code.writeln('m_${property.capitalizedName} = '
-            'object.m_${property.capitalizedName};');
+        if (property.isEncoded) {
+          code.writeln('copy${property.capitalizedName}(object);');
+        } else {
+          code.writeln('m_${property.capitalizedName} = '
+              'object.m_${property.capitalizedName};');
+        }
       }
       if (_extensionOf != null) {
         code.writeln('${_extensionOf.name}::'
@@ -227,8 +256,13 @@
       code.writeln('switch (propertyKey){');
       for (final property in properties) {
         code.writeln('case ${property.name}PropertyKey:');
-        code.writeln('m_${property.capitalizedName} = '
-            '${property.type.runtimeCoreType}::deserialize(reader);');
+        if (property.isEncoded) {
+          code.writeln('decode${property.capitalizedName}'
+              '(${property.type.runtimeCoreType}::deserialize(reader));');
+        } else {
+          code.writeln('m_${property.capitalizedName} = '
+              '${property.type.runtimeCoreType}::deserialize(reader);');
+        }
         code.writeln('return true;');
       }
       code.writeln('}');
@@ -400,18 +434,23 @@
     ctxCode.writeln('} return nullptr; }');
 
     var usedFieldTypes = <FieldType, List<Property>>{};
+    var getSetFieldTypes = <FieldType, List<Property>>{};
     for (final definition in runtimeDefinitions) {
       for (final property in definition.properties) {
         usedFieldTypes[property.type] ??= [];
         usedFieldTypes[property.type].add(property);
+        if (!property.isEncoded) {
+          getSetFieldTypes[property.type] ??= [];
+          getSetFieldTypes[property.type].add(property);
+        }
       }
     }
-    for (final fieldType in usedFieldTypes.keys) {
+    for (final fieldType in getSetFieldTypes.keys) {
       ctxCode
           .writeln('static void set${fieldType.capitalizedName}(Core* object, '
               'int propertyKey, ${fieldType.cppName} value){');
       ctxCode.writeln('switch (propertyKey) {');
-      var properties = usedFieldTypes[fieldType];
+      var properties = getSetFieldTypes[fieldType];
       for (final property in properties) {
         ctxCode.writeln('case ${property.definition.name}Base'
             '::${property.name}PropertyKey:');
@@ -421,12 +460,12 @@
       }
       ctxCode.writeln('}}');
     }
-    for (final fieldType in usedFieldTypes.keys) {
+    for (final fieldType in getSetFieldTypes.keys) {
       ctxCode.writeln(
           'static ${fieldType.cppName} get${fieldType.capitalizedName}('
           'Core* object, int propertyKey){');
       ctxCode.writeln('switch (propertyKey) {');
-      var properties = usedFieldTypes[fieldType];
+      var properties = getSetFieldTypes[fieldType];
       for (final property in properties) {
         ctxCode.writeln('case ${property.definition.name}Base'
             '::${property.name}PropertyKey:');
@@ -451,25 +490,7 @@
     }
 
     ctxCode.writeln('default: return -1;}}');
-    /*Core makeCoreInstance(int typeKey) {
-    switch (typeKey) {
-      case KeyedObjectBase.typeKey:
-        return KeyedObject();
-      case KeyedPropertyBase.typeKey:
-        return KeyedProperty();*/
-    // Put our fields in.
-    // var usedFieldTypes = <FieldType>{};
-    // for (final definition in definitions.values) {
-    //   for (final property in definition.properties) {
-    //     usedFieldTypes.add(property.type);
-    //   }
-    // }
-    // // Find fields we use.
 
-    // for (final fieldType in usedFieldTypes) {
-    //   ctxCode.writeln('static ${fieldType.runtimeCoreType} '
-    //       '${fieldType.uncapitalizedName}Type;');
-    // }
     ctxCode.writeln('};}');
 
     var output = generatedHppPath;
diff --git a/dev/core_generator/lib/src/property.dart b/dev/core_generator/lib/src/property.dart
index be707a4..76b3be8 100644
--- a/dev/core_generator/lib/src/property.dart
+++ b/dev/core_generator/lib/src/property.dart
@@ -1,5 +1,4 @@
 import 'package:colorize/colorize.dart';
-import 'package:core_generator/src/comment.dart';
 import 'package:core_generator/src/definition.dart';
 import 'package:core_generator/src/field_type.dart';
 import 'package:core_generator/src/key.dart';
@@ -20,6 +19,7 @@
   bool isCoop = true;
   bool isSetOverride = false;
   bool isGetOverride = false;
+  bool isEncoded = false;
   FieldType typeRuntime;
 
   factory Property(Definition type, String name, Map<String, dynamic> data) {
@@ -28,7 +28,7 @@
     }
 
     var fieldType =
-        FieldType.find(data["typeRuntime"]) ?? FieldType.find(data["type"]);
+        FieldType.find(data['typeRuntime']) ?? FieldType.find(data['type']);
 
     if (fieldType == null) {
       color('Invalid field type ${data['type']} for $name.', front: Styles.RED);
@@ -39,7 +39,11 @@
 
   Property.make(
       this.definition, this.name, this.type, Map<String, dynamic> data) {
-    dynamic descriptionValue = data["description"];
+    dynamic encodedValue = data['encoded'];
+    if (encodedValue is bool) {
+      isEncoded = encodedValue;
+    }
+    dynamic descriptionValue = data['description'];
     if (descriptionValue is String) {
       description = descriptionValue;
     }
@@ -89,153 +93,11 @@
     if (rt is String) {
       typeRuntime = FieldType.find(rt);
     }
-    key = Key.fromJSON(data["key"]) ?? Key.forProperty(this);
+    key = Key.fromJSON(data['key']) ?? Key.forProperty(this);
   }
 
   FieldType getExportType() => typeRuntime ?? type;
 
-  String generateCode(bool forRuntime) {
-    bool exportAnimates = false;
-    var exportType = getExportType();
-    String propertyKey = '${name}PropertyKey';
-    var code = StringBuffer('  /// ${'-' * 74}\n');
-    code.write(comment('${capitalize(name)} field with key ${key.intValue}.',
-        indent: 1));
-    if (initialValueRuntime != null || initialValue != null) {
-      code.writeln(
-          '${exportType.cppName} _$name = ${initialValueRuntime ?? initialValue};');
-    } else {
-      code.writeln('${exportType.cppName} _$name;');
-    }
-    if (exportAnimates) {
-      code.writeln('${exportType.cppName} _${name}Animated;');
-      code.writeln('KeyState _${name}KeyState = KeyState.none;');
-    }
-    code.writeln('static const int $propertyKey = ${key.intValue};');
-
-    if (description != null) {
-      code.write(comment(description, indent: 1));
-    }
-    if (exportAnimates) {
-      code.write(comment(
-          'Get the [_$name] field value.'
-          'Note this may not match the core value '
-          'if animation mode is active.',
-          indent: 1));
-      code.writeln(
-          '${exportType.cppName} get $name => _${name}Animated ?? _$name;');
-      code.write(
-          comment('Get the non-animation [_$name] field value.', indent: 1));
-      code.writeln('${exportType.cppName} get ${name}Core => _$name;');
-    } else {
-      code.writeln('${exportType.cppName} get $name => _$name;');
-    }
-    code.write(comment('Change the [_$name] field value.', indent: 1));
-    code.write(comment(
-        '[${name}Changed] will be invoked only if the '
-        'field\'\s value has changed.',
-        indent: 1));
-    code.writeln(
-        '''set $name${exportAnimates ? 'Core' : ''}(${exportType.cppName} value) {
-        if(${exportType.equalityCheck('_$name', 'value')}) { return; }
-        ${exportType.cppName} from = _$name;
-        _$name = value;''');
-    // Property change callbacks to the context don't propagate at runtime.
-    if (!forRuntime) {
-      code.writeln('onPropertyChanged($propertyKey, from, value);');
-      if (!isCoop) {
-        code.writeln(
-            'context?.editorPropertyChanged(this, $propertyKey, from, value);');
-      }
-    }
-    // Change callbacks do as we use those to trigger dirty states.
-    code.writeln('''
-        ${name}Changed(from, value);
-      }''');
-    if (exportAnimates) {
-      code.writeln('''set $name(${exportType.cppName} value) {
-        if(context != null && context.isAnimating && $name != value) {
-          _${name}Animate(value, true);
-          return;
-        }
-        ${name}Core = value;
-      }''');
-
-      code.writeln(
-          '''void _${name}Animate(${exportType.cppName} value, bool autoKey) {
-        if (_${name}Animated == value) {
-          return;
-        }
-        ${exportType.cppName} from = $name;
-        _${name}Animated = value;
-        ${exportType.cppName} to = $name;
-        onAnimatedPropertyChanged($propertyKey, autoKey, from, to);
-        ${name}Changed(from, to);
-      }''');
-
-      code.writeln(
-          '${exportType.cppName} get ${name}Animated => _${name}Animated;');
-      code.writeln('''set ${name}Animated(${exportType.cppName} value) =>
-                        _${name}Animate(value, false);''');
-      code.writeln('KeyState get ${name}KeyState => _${name}KeyState;');
-      code.writeln('''set ${name}KeyState(KeyState value) {
-        if (_${name}KeyState == value) {
-          return;
-        }
-        _${name}KeyState = value;
-        // Force update anything listening on this property.
-        onAnimatedPropertyChanged($propertyKey, false, _${name}Animated, _${name}Animated);
-      }''');
-    }
-    code.writeln('void ${name}Changed('
-        '${exportType.cppName} from, ${exportType.cppName} to);\n');
-
-    return code.toString();
-  }
-
-  Map<String, dynamic> serialize() {
-    Map<String, dynamic> data = <String, dynamic>{'type': type.name};
-    if (typeRuntime != null) {
-      data['typeRuntime'] = typeRuntime.name;
-    }
-
-    if (initialValue != null) {
-      data['initialValue'] = initialValue;
-    }
-    if (initialValueRuntime != null) {
-      data['initialValueRuntime'] = initialValueRuntime;
-    }
-    if (isGetOverride) {
-      data['overrideGet'] = true;
-    }
-    if (isSetOverride) {
-      data['overrideSet'] = true;
-    }
-    if (animates) {
-      data['animates'] = true;
-    }
-    if (group != null) {
-      data['group'] = group;
-    }
-    data['key'] = key.serialize();
-    if (description != null) {
-      data['description'] = description;
-    }
-    if (isNullable) {
-      data['nullable'] = true;
-    }
-    if (!isRuntime) {
-      data['runtime'] = false;
-    }
-    if (!isCoop) {
-      data['coop'] = false;
-    }
-    if (isVirtual) {
-      data['virtual'] = true;
-    }
-    return data;
-  }
-
   @override
   String toString() {
     return '$name(${key.intValue})';
diff --git a/dev/defs/shapes/mesh.json b/dev/defs/shapes/mesh.json
index e4986d5..857676b 100644
--- a/dev/defs/shapes/mesh.json
+++ b/dev/defs/shapes/mesh.json
@@ -29,6 +29,7 @@
     },
     "triangleIndexBytes": {
       "type": "Bytes",
+      "encoded": true,
       "key": {
         "int": 223,
         "string": "triangleindexbytes"
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp
index cef092c..d7d46a3 100644
--- a/include/rive/generated/core_registry.hpp
+++ b/include/rive/generated/core_registry.hpp
@@ -784,9 +784,6 @@
         static void
         setBytes(Core* object, int propertyKey, std::vector<uint8_t> value) {
             switch (propertyKey) {
-                case MeshBase::triangleIndexBytesPropertyKey:
-                    object->as<MeshBase>()->triangleIndexBytes(value);
-                    break;
                 case FileAssetContentsBase::bytesPropertyKey:
                     object->as<FileAssetContentsBase>()->bytes(value);
                     break;
@@ -1162,8 +1159,6 @@
         }
         static std::vector<uint8_t> getBytes(Core* object, int propertyKey) {
             switch (propertyKey) {
-                case MeshBase::triangleIndexBytesPropertyKey:
-                    return object->as<MeshBase>()->triangleIndexBytes();
                 case FileAssetContentsBase::bytesPropertyKey:
                     return object->as<FileAssetContentsBase>()->bytes();
             }
diff --git a/include/rive/generated/shapes/mesh_base.hpp b/include/rive/generated/shapes/mesh_base.hpp
index 5a2aa1e..ff9b93d 100644
--- a/include/rive/generated/shapes/mesh_base.hpp
+++ b/include/rive/generated/shapes/mesh_base.hpp
@@ -28,31 +28,21 @@
 
         static const uint16_t triangleIndexBytesPropertyKey = 223;
 
-    private:
-        std::vector<uint8_t> m_TriangleIndexBytes;
-
     public:
-        inline const std::vector<uint8_t>& triangleIndexBytes() const {
-            return m_TriangleIndexBytes;
-        }
-        void triangleIndexBytes(std::vector<uint8_t> value) {
-            if (m_TriangleIndexBytes == value) {
-                return;
-            }
-            m_TriangleIndexBytes = value;
-            triangleIndexBytesChanged();
-        }
+        virtual void decodeTriangleIndexBytes(std::vector<uint8_t> value) = 0;
+        virtual void copyTriangleIndexBytes(const MeshBase& object) = 0;
 
         Core* clone() const override;
         void copy(const MeshBase& object) {
-            m_TriangleIndexBytes = object.m_TriangleIndexBytes;
+            copyTriangleIndexBytes(object);
             ContainerComponent::copy(object);
         }
 
         bool deserialize(uint16_t propertyKey, BinaryReader& reader) override {
             switch (propertyKey) {
                 case triangleIndexBytesPropertyKey:
-                    m_TriangleIndexBytes = CoreBytesType::deserialize(reader);
+                    decodeTriangleIndexBytes(
+                        CoreBytesType::deserialize(reader));
                     return true;
             }
             return ContainerComponent::deserialize(propertyKey, reader);
diff --git a/include/rive/shapes/mesh.hpp b/include/rive/shapes/mesh.hpp
index ee3b0f2..1b531e5 100644
--- a/include/rive/shapes/mesh.hpp
+++ b/include/rive/shapes/mesh.hpp
@@ -12,7 +12,8 @@
         StatusCode onAddedDirty(CoreContext* context) override;
         void markDrawableDirty();
         void addVertex(MeshVertex* vertex);
-
+        void decodeTriangleIndexBytes(std::vector<uint8_t> value) override;
+        void copyTriangleIndexBytes(const MeshBase& object) override;
 #ifdef TESTING
         std::vector<MeshVertex*>& vertices() { return m_Vertices; }
 #endif
diff --git a/src/shapes/mesh.cpp b/src/shapes/mesh.cpp
index 207410c..e84b27e 100644
--- a/src/shapes/mesh.cpp
+++ b/src/shapes/mesh.cpp
@@ -21,4 +21,12 @@
     parent()->as<Image>()->setMesh(this);
 
     return StatusCode::Ok;
+}
+
+void Mesh::decodeTriangleIndexBytes(std::vector<uint8_t> value) {
+    // decode the triangle index bytes
+}
+
+void Mesh::copyTriangleIndexBytes(const MeshBase& object) {
+    // copy the triangle indices from object
 }
\ No newline at end of file