diff --git a/rigify/__init__.py b/rigify/__init__.py
index dd55be0459221a87acbf0a1823b153b77989255a..4b926c4891aa12ae4713a35c24adf8f6e9b9eb9e 100644
--- a/rigify/__init__.py
+++ b/rigify/__init__.py
@@ -530,6 +530,10 @@ def register():
         description="Forces Rigify to delete and rebuild all the rig widgets. if unset, only missing widgets will be created",
         default=False)
 
+    bpy.types.Armature.rigify_mirror_widgets = BoolProperty(name="Mirror Widgets",
+        description="Make widgets for left and right side bones linked duplicates with negative X scale for the right side, based on bone name symmetry",
+        default=True)
+
     bpy.types.Armature.rigify_target_rig = PointerProperty(type=bpy.types.Object,
         name="Rigify Target Rig",
         description="Defines which rig to overwrite. If unset, a new one called 'rig' will be created",
diff --git a/rigify/generate.py b/rigify/generate.py
index ad8f43b59a942239910e0c672c155d9cc27743be..a2d9a5d14ca637b2651348e6ae7767a03fa5310b 100644
--- a/rigify/generate.py
+++ b/rigify/generate.py
@@ -25,7 +25,7 @@ import time
 from .utils.errors import MetarigError
 from .utils.bones import new_bone
 from .utils.layers import ORG_LAYER, MCH_LAYER, DEF_LAYER, ROOT_LAYER
-from .utils.naming import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, ROOT_NAME, make_original_name
+from .utils.naming import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, ROOT_NAME, make_original_name, change_name_side, get_name_side, Side
 from .utils.widgets import WGT_PREFIX
 from .utils.widgets_special import create_root_widget
 from .utils.mechanism import refresh_all_drivers
@@ -165,27 +165,44 @@ class Generator(base_generate.BaseGenerator):
                 for obj in list(old_collection.objects):
                     bpy.data.objects.remove(obj)
 
-            # Rename widgets and collection if renaming
-            if self.rig_old_name:
-                old_prefix = WGT_PREFIX + self.rig_old_name + "_"
-                new_prefix = WGT_PREFIX + self.obj.name + "_"
+            # Rename the collection
+            old_collection.name = new_group_name
 
-                for obj in list(old_collection.objects):
-                    if obj.name.startswith(old_prefix):
-                        new_name = new_prefix + obj.name[len(old_prefix):]
-                    elif obj.name == wgts_group_name:
-                        new_name = new_group_name
-                    else:
-                        continue
+        # Create/find widget collection
+        self.widget_collection = ensure_widget_collection(self.context, new_group_name)
+        self.use_mirror_widgets = self.metarig.data.rigify_mirror_widgets
 
-                    obj.data.name = new_name
-                    obj.name = new_name
+        # Build tables for existing widgets
+        self.old_widget_table = {}
+        self.new_widget_table = {}
+        self.widget_mirror_mesh = {}
 
-                old_collection.name = new_group_name
+        if not self.metarig.data.rigify_force_widget_update and self.obj.pose:
+            # Find all widgets from the collection referenced by the old rig
+            known_widgets = set(obj.name for obj in self.widget_collection.objects)
 
-        # Create/find widget collection
-        self.widget_collection = ensure_widget_collection(self.context, new_group_name)
-        self.wgts_group_name = new_group_name
+            for bone in self.obj.pose.bones:
+                if bone.custom_shape and bone.custom_shape.name in known_widgets:
+                    self.old_widget_table[bone.name] = bone.custom_shape
+
+            # Rename widgets in case the rig was renamed
+            name_prefix = WGT_PREFIX + self.obj.name + "_"
+
+            for bone_name, widget in self.old_widget_table.items():
+                old_data_name = change_name_side(widget.name, get_name_side(widget.data.name))
+
+                widget.name = name_prefix + bone_name
+
+                # If the mesh name is the same as the object, rename it too
+                if widget.data.name == old_data_name:
+                    widget.data.name = change_name_side(widget.name, get_name_side(widget.data.name))
+
+            # Find meshes for mirroring
+            if self.use_mirror_widgets:
+                for bone_name, widget in self.old_widget_table.items():
+                    mid_name = change_name_side(bone_name, Side.MIDDLE)
+                    if bone_name != mid_name:
+                        self.widget_mirror_mesh[mid_name] = widget.data
 
 
     def __duplicate_rig(self):
@@ -373,6 +390,11 @@ class Generator(base_generate.BaseGenerator):
         # Assign shapes to bones
         # Object's with name WGT-<bone_name> get used as that bone's shape.
         for bone in self.obj.pose.bones:
+            # First check the table built by create_widget
+            if bone.name in self.new_widget_table:
+                bone.custom_shape = self.new_widget_table[bone.name]
+                continue
+
             # Object names are limited to 63 characters... arg
             wgt_name = (WGT_PREFIX + self.obj.name + '_' + bone.name)[:63]
 
diff --git a/rigify/rigs/widgets.py b/rigify/rigs/widgets.py
index ef0bb544639b67c83935000a641fc6165ab78d24..0c434593604264f1f3fbd0282e8d84717afd639f 100644
--- a/rigify/rigs/widgets.py
+++ b/rigify/rigs/widgets.py
@@ -97,7 +97,7 @@ def create_ikarrow_widget(rig, bone_name, size=1.0, bone_transform_name=None, ro
 
 def create_hand_widget(rig, bone_name, size=1.0, bone_transform_name=None):
     # Create hand widget
-    obj = create_widget(rig, bone_name, bone_transform_name)
+    obj = create_widget(rig, bone_name, bone_transform_name, subsurf=2)
     if obj is not None:
         verts = [(0.0*size, 1.5*size, -0.7000000476837158*size), (1.1920928955078125e-07*size, -0.25*size, -0.6999999284744263*size), (0.0*size, -0.25*size, 0.7000000476837158*size), (-1.1920928955078125e-07*size, 1.5*size, 0.6999999284744263*size), (5.960464477539063e-08*size, 0.7229999899864197*size, -0.699999988079071*size), (-5.960464477539063e-08*size, 0.7229999899864197*size, 0.699999988079071*size), (1.1920928955078125e-07*size, -2.9802322387695312e-08*size, -0.699999988079071*size), (0.0*size, 2.9802322387695312e-08*size, 0.699999988079071*size), ]
         edges = [(1, 2), (0, 3), (0, 4), (3, 5), (4, 6), (1, 6), (5, 7), (2, 7)]
@@ -107,8 +107,6 @@ def create_hand_widget(rig, bone_name, size=1.0, bone_transform_name=None):
         mesh.from_pydata(verts, edges, faces)
         mesh.update()
 
-        mod = obj.modifiers.new("subsurf", 'SUBSURF')
-        mod.levels = 2
         return obj
     else:
         return None
@@ -116,7 +114,7 @@ def create_hand_widget(rig, bone_name, size=1.0, bone_transform_name=None):
 
 def create_foot_widget(rig, bone_name, size=1.0, bone_transform_name=None):
     # Create hand widget
-    obj = create_widget(rig, bone_name, bone_transform_name)
+    obj = create_widget(rig, bone_name, bone_transform_name, subsurf=2)
     if obj is not None:
         verts = [(-0.6999998688697815*size, -0.5242648720741272*size, 0.0*size), (-0.7000001072883606*size, 1.2257349491119385*size, 0.0*size), (0.6999998688697815*size, 1.2257351875305176*size, 0.0*size), (0.7000001072883606*size, -0.5242648720741272*size, 0.0*size), (-0.6999998688697815*size, 0.2527350187301636*size, 0.0*size), (0.7000001072883606*size, 0.2527352571487427*size, 0.0*size), (-0.7000001072883606*size, 0.975735068321228*size, 0.0*size), (0.6999998688697815*size, 0.9757352471351624*size, 0.0*size), ]
         edges = [(1, 2), (0, 3), (0, 4), (3, 5), (4, 6), (1, 6), (5, 7), (2, 7), ]
@@ -126,8 +124,6 @@ def create_foot_widget(rig, bone_name, size=1.0, bone_transform_name=None):
         mesh.from_pydata(verts, edges, faces)
         mesh.update()
 
-        mod = obj.modifiers.new("subsurf", 'SUBSURF')
-        mod.levels = 2
         return obj
     else:
         return None
diff --git a/rigify/ui.py b/rigify/ui.py
index c9592677f8b3d150747816867af5b23e918aa440..c801ac25c1ae3e4181c1ae2744805615203d5e7c 100644
--- a/rigify/ui.py
+++ b/rigify/ui.py
@@ -177,6 +177,7 @@ class DATA_PT_rigify_buttons(bpy.types.Panel):
                 if armature_id_store.rigify_generate_mode == 'new':
                     row.enabled = False
 
+                col.prop(armature_id_store, "rigify_mirror_widgets")
                 col.prop(armature_id_store, "rigify_finalize_script", text="Run Script")
 
         elif obj.mode == 'EDIT':
diff --git a/rigify/utils/widgets.py b/rigify/utils/widgets.py
index a8691349436dbfec7c33ccb181517fed23eeb19e..3f0bf25224a67ec5cc15319c41b5533d8432d56e 100644
--- a/rigify/utils/widgets.py
+++ b/rigify/utils/widgets.py
@@ -28,6 +28,7 @@ from itertools import count
 
 from .errors import MetarigError
 from .collections import ensure_widget_collection
+from .naming import change_name_side, get_name_side, Side
 
 WGT_PREFIX = "WGT-"  # Prefix for widget objects
 
@@ -56,47 +57,113 @@ def obj_to_bone(obj, rig, bone_name, bone_transform_name=None):
     elif bone.custom_shape_transform:
         bone = bone.custom_shape_transform
 
-    shape_mat = Matrix.Translation(loc) @ (Euler(rot).to_matrix() @ Matrix.Diagonal(scale)).to_4x4()
+    shape_mat = Matrix.LocRotScale(loc, Euler(rot), scale)
 
     obj.rotation_mode = 'XYZ'
     obj.matrix_basis = rig.matrix_world @ bone.bone.matrix_local @ shape_mat
 
 
-def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None, widget_force_new=False):
+def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None, widget_force_new=False, subsurf=0):
     """ Creates an empty widget object for a bone, and returns the object.
     """
     assert rig.mode != 'EDIT'
 
-    obj_name = widget_name or WGT_PREFIX + rig.name + '_' + bone_name
+    from ..base_generate import BaseGenerator
+
     scene = bpy.context.scene
-    collection = ensure_widget_collection(bpy.context, 'WGTS_' + rig.name)
+    bone = rig.pose.bones[bone_name]
+
+    # Access the current generator instance when generating (ugh, globals)
+    generator = BaseGenerator.instance
+
+    if generator:
+        collection = generator.widget_collection
+    else:
+        collection = ensure_widget_collection(bpy.context, 'WGTS_' + rig.name)
+
+    use_mirror = generator and generator.use_mirror_widgets
+
+    if use_mirror:
+        bone_mid_name = change_name_side(bone_name, Side.MIDDLE)
+
+    obj_name = widget_name or WGT_PREFIX + rig.name + '_' + bone_name
     reuse_mesh = None
 
     # Check if it already exists in the scene
     if not widget_force_new:
-        if obj_name in scene.objects:
+        obj = None
+
+        if generator:
+            # Check if the widget was already generated
+            if bone_name in generator.new_widget_table:
+                return None
+
+            # If re-generating, check widgets used by the previous rig
+            obj = generator.old_widget_table.get(bone_name)
+
+        if not obj:
+            # Search the scene by name
+            obj = scene.objects.get(obj_name)
+
+        if obj:
+            # Record the generated widget
+            if generator:
+                generator.new_widget_table[bone_name] = obj
+
+            # Re-add to the collection if not there for some reason
+            if obj.name not in collection.objects:
+                collection.objects.link(obj)
+
+            # Flip scale for originally mirrored widgets
+            if obj.scale.x < 0 and bone.custom_shape_scale_xyz.x > 0:
+                bone.custom_shape_scale_xyz.x *= -1
+
             # Move object to bone position, in case it changed
-            obj = scene.objects[obj_name]
             obj_to_bone(obj, rig, bone_name, bone_transform_name)
 
             return None
 
-        # Delete object if it exists in blend data but not scene data.
-        # This is necessary so we can then create the object without
-        # name conflicts.
-        if obj_name in bpy.data.objects:
-            bpy.data.objects.remove(bpy.data.objects[obj_name])
-
         # Create a linked duplicate of the widget assigned in the metarig
         reuse_widget = rig.pose.bones[bone_name].custom_shape
         if reuse_widget:
+            subsurf = 0
             reuse_mesh = reuse_widget.data
 
-    # Create mesh object
-    mesh = reuse_mesh or bpy.data.meshes.new(obj_name)
+        # Create a linked duplicate with the mirror widget
+        if not reuse_mesh and use_mirror and bone_mid_name != bone_name:
+            reuse_mesh = generator.widget_mirror_mesh.get(bone_mid_name)
+
+    # Create an empty mesh datablock if not linking
+    if reuse_mesh:
+        mesh = reuse_mesh
+
+    elif use_mirror and bone_mid_name != bone_name:
+        # When mirroring, untag side from mesh name, and remember it
+        mesh = bpy.data.meshes.new(change_name_side(obj_name, Side.MIDDLE))
+
+        generator.widget_mirror_mesh[bone_mid_name] = mesh
+
+    else:
+        mesh = bpy.data.meshes.new(obj_name)
+
+    # Create the object
     obj = bpy.data.objects.new(obj_name, mesh)
     collection.objects.link(obj)
 
+    # Add the subdivision surface modifier
+    if subsurf > 0:
+        mod = obj.modifiers.new("subsurf", 'SUBSURF')
+        mod.levels = subsurf
+
+    # Record the generated widget
+    if generator:
+        generator.new_widget_table[bone_name] = obj
+
+    # Flip scale for right side if mirroring widgets
+    if use_mirror and get_name_side(bone_name) == Side.RIGHT:
+        if bone.custom_shape_scale_xyz.x > 0:
+            bone.custom_shape_scale_xyz.x *= -1
+
     # Move object to bone position and set layers
     obj_to_bone(obj, rig, bone_name, bone_transform_name)
 
@@ -187,7 +254,7 @@ def widget_generator(generate_func=None, *, register=None, subsurf=0):
     """
     @functools.wraps(generate_func)
     def wrapper(rig, bone_name, bone_transform_name=None, widget_name=None, widget_force_new=False, **kwargs):
-        obj = create_widget(rig, bone_name, bone_transform_name, widget_name=widget_name, widget_force_new=widget_force_new)
+        obj = create_widget(rig, bone_name, bone_transform_name, widget_name=widget_name, widget_force_new=widget_force_new, subsurf=subsurf)
         if obj is not None:
             geom = GeometryData()
 
@@ -197,10 +264,6 @@ def widget_generator(generate_func=None, *, register=None, subsurf=0):
             mesh.from_pydata(geom.verts, geom.edges, geom.faces)
             mesh.update()
 
-            if subsurf:
-                mod = obj.modifiers.new("subsurf", 'SUBSURF')
-                mod.levels = subsurf
-
             return obj
         else:
             return None