diff --git a/e2e/tests/functional/plugins/plot/tagging.e2e.spec.js b/e2e/tests/functional/plugins/plot/tagging.e2e.spec.js
index cfb0ba1e1d..6b0fdd1176 100644
--- a/e2e/tests/functional/plugins/plot/tagging.e2e.spec.js
+++ b/e2e/tests/functional/plugins/plot/tagging.e2e.spec.js
@@ -41,7 +41,7 @@ test.describe('Plot Tagging', () => {
    * @param {Number} yEnd a telemetry item with a plot
    * @returns {Promise}
    */
-  async function createTags({ page, canvas, xEnd = 700, yEnd = 480 }) {
+  async function createTags({ page, canvas, xEnd = 700, yEnd = 520 }) {
     await canvas.hover({ trial: true });
 
     //Alt+Shift Drag Start to select some points to tag
@@ -284,7 +284,7 @@ test.describe('Plot Tagging', () => {
       page,
       canvas,
       xEnd: 700,
-      yEnd: 215
+      yEnd: 240
     });
     await basicTagsTests(page);
     await testTelemetryItem(page, alphaSineWave);
diff --git a/e2e/tests/performance/tagging.perf.spec.js b/e2e/tests/performance/tagging.perf.spec.js
index 8c527e4554..b3ceca7961 100644
--- a/e2e/tests/performance/tagging.perf.spec.js
+++ b/e2e/tests/performance/tagging.perf.spec.js
@@ -41,7 +41,7 @@ test.describe('Plot Tagging Performance', () => {
    * @param {Number} yEnd a telemetry item with a plot
    * @returns {Promise}
    */
-  async function createTags({ page, canvas, xEnd = 700, yEnd = 480 }) {
+  async function createTags({ page, canvas, xEnd = 700, yEnd = 520 }) {
     await canvas.hover({ trial: true });
 
     //Alt+Shift Drag Start to select some points to tag
@@ -265,7 +265,7 @@ test.describe('Plot Tagging Performance', () => {
       page,
       canvas,
       xEnd: 700,
-      yEnd: 215
+      yEnd: 240
     });
     await basicTagsTests(page);
     await testTelemetryItem(page, alphaSineWave);
diff --git a/src/api/annotation/AnnotationAPI.js b/src/api/annotation/AnnotationAPI.js
index c56d005c98..3c75f00009 100644
--- a/src/api/annotation/AnnotationAPI.js
+++ b/src/api/annotation/AnnotationAPI.js
@@ -100,7 +100,7 @@ export default class AnnotationAPI extends EventEmitter {
       creatable: false,
       cssClass: 'icon-notebook',
       initialize: function (domainObject) {
-        domainObject.targets = domainObject.targets || {};
+        domainObject.targets = domainObject.targets || [];
         domainObject._deleted = domainObject._deleted || false;
         domainObject.originalContextPath = domainObject.originalContextPath || '';
         domainObject.tags = domainObject.tags || [];
@@ -117,10 +117,10 @@ export default class AnnotationAPI extends EventEmitter {
    * @property {ANNOTATION_TYPES} annotationType the type of annotation to create (e.g., PLOT_SPATIAL)
    * @property {Tag[]} tags tags to add to the annotation, e.g., SCIENCE for science related annotations
    * @property {String} contentText Some text to add to the annotation, e.g. ("This annotation is about science")
-   * @property {Object<string, Object>} targets The targets ID keystrings and their specific properties.
-   * For plots, this will be a bounding box, e.g.: {maxY: 100, minY: 0, maxX: 100, minX: 0}
+   * @property {Array<Object>} targets The targets ID keystrings and their specific properties.
+   * For plots, this will be a bounding box, e.g.: {keyString: "d8385009-789d-457b-acc7-d50ba2fd55ea", maxY: 100, minY: 0, maxX: 100, minX: 0}
    * For notebooks, this will be an entry ID, e.g.: {entryId: "entry-ecb158f5-d23c-45e1-a704-649b382622ba"}
-   * @property {DomainObject>} targetDomainObjects the targets ID keystrings and the domain objects this annotation points to (e.g., telemetry objects for a plot)
+   * @property {DomainObject>[]} targetDomainObjects the domain objects this annotation points to (e.g., telemetry objects for a plot)
    */
   /**
    * @method create
@@ -141,11 +141,15 @@ export default class AnnotationAPI extends EventEmitter {
       throw new Error(`Unknown annotation type: ${annotationType}`);
     }
 
-    if (!Object.keys(targets).length) {
+    if (!targets.length) {
       throw new Error(`At least one target is required to create an annotation`);
     }
 
-    if (!Object.keys(targetDomainObjects).length) {
+    if (targets.some((target) => !target.keyString)) {
+      throw new Error(`All targets require a keyString to create an annotation`);
+    }
+
+    if (!targetDomainObjects.length) {
       throw new Error(`At least one targetDomainObject is required to create an annotation`);
     }
 
@@ -181,7 +185,7 @@ export default class AnnotationAPI extends EventEmitter {
     const success = await this.openmct.objects.save(createdObject);
     if (success) {
       this.emit('annotationCreated', createdObject);
-      Object.values(targetDomainObjects).forEach((targetDomainObject) => {
+      targetDomainObjects.forEach((targetDomainObject) => {
         this.#updateAnnotationModified(targetDomainObject);
       });
 
@@ -321,7 +325,10 @@ export default class AnnotationAPI extends EventEmitter {
   }
 
   #addTagMetaInformationToTags(tags) {
-    return tags.map((tagKey) => {
+    // Convert to Set and back to Array to remove duplicates
+    const uniqueTags = [...new Set(tags)];
+
+    return uniqueTags.map((tagKey) => {
       const tagModel = this.availableTags[tagKey];
       tagModel.tagID = tagKey;
 
@@ -363,7 +370,8 @@ export default class AnnotationAPI extends EventEmitter {
     const modelAddedToResults = await Promise.all(
       results.map(async (result) => {
         const targetModels = await Promise.all(
-          Object.keys(result.targets).map(async (targetID) => {
+          result.targets.map(async (target) => {
+            const targetID = target.keyString;
             const targetModel = await this.openmct.objects.get(targetID);
             const targetKeyString = this.openmct.objects.makeKeyString(targetModel.identifier);
             const originalPathObjects = await this.openmct.objects.getOriginalPath(targetKeyString);
@@ -410,13 +418,12 @@ export default class AnnotationAPI extends EventEmitter {
   #breakApartSeparateTargets(results) {
     const separateResults = [];
     results.forEach((result) => {
-      Object.keys(result.targets).forEach((targetID) => {
+      result.targets.forEach((target) => {
+        const targetID = target.keyString;
         const separatedResult = {
           ...result
         };
-        separatedResult.targets = {
-          [targetID]: result.targets[targetID]
-        };
+        separatedResult.targets = [target];
         separatedResult.targetModels = result.targetModels.filter((targetModel) => {
           const targetKeyString = this.openmct.objects.makeKeyString(targetModel.identifier);
 
diff --git a/src/api/annotation/AnnotationAPISpec.js b/src/api/annotation/AnnotationAPISpec.js
index 8c865be15d..0bc7932b9f 100644
--- a/src/api/annotation/AnnotationAPISpec.js
+++ b/src/api/annotation/AnnotationAPISpec.js
@@ -62,11 +62,12 @@ describe('The Annotation API', () => {
         key: 'anAnnotationKey',
         namespace: 'fooNameSpace'
       },
-      targets: {
-        'fooNameSpace:some-object': {
+      targets: [
+        {
+          keyString: 'fooNameSpace:some-object',
           entryId: 'fooBarEntry'
         }
-      }
+      ]
     };
 
     mockObjectProvider = jasmine.createSpyObj('mock provider', ['create', 'update', 'get']);
@@ -121,7 +122,7 @@ describe('The Annotation API', () => {
         tags: ['sometag'],
         contentText: 'fooContext',
         targetDomainObjects: [mockDomainObject],
-        targets: { fooTarget: {} }
+        targets: [{ keyString: 'fooTarget' }]
       };
       const annotationObject = await openmct.annotation.create(annotationCreationArguments);
       expect(annotationObject).toBeDefined();
@@ -136,7 +137,7 @@ describe('The Annotation API', () => {
         tags: ['sometag'],
         contentText: 'fooContext',
         targetDomainObjects: [mockDomainObject],
-        targets: { fooTarget: {} }
+        targets: [{ keyString: 'fooTarget' }]
       };
       openmct.annotation.setNamespaceToSaveAnnotations('fooNameSpace');
       const annotationObject = await openmct.annotation.create(annotationCreationArguments);
@@ -166,7 +167,7 @@ describe('The Annotation API', () => {
           tags: ['sometag'],
           contentText: 'fooContext',
           targetDomainObjects: [mockDomainObject],
-          targets: { fooTarget: {} }
+          targets: [{ keyString: 'fooTarget' }]
         };
         openmct.annotation.setNamespaceToSaveAnnotations('namespaceThatDoesNotExist');
         await openmct.annotation.create(annotationCreationArguments);
@@ -183,7 +184,7 @@ describe('The Annotation API', () => {
           tags: ['sometag'],
           contentText: 'fooContext',
           targetDomainObjects: [mockDomainObject],
-          targets: { fooTarget: {} }
+          targets: [{ keyString: 'fooTarget' }]
         };
         openmct.annotation.setNamespaceToSaveAnnotations('immutableProvider');
         await openmct.annotation.create(annotationCreationArguments);
@@ -202,7 +203,7 @@ describe('The Annotation API', () => {
         annotationType: openmct.annotation.ANNOTATION_TYPES.NOTEBOOK,
         tags: ['aWonderfulTag'],
         contentText: 'fooContext',
-        targets: { 'fooNameSpace:some-object': { entryId: 'fooBarEntry' } },
+        targets: [{ keyString: 'fooNameSpace:some-object', entryId: 'fooBarEntry' }],
         targetDomainObjects: [mockDomainObject]
       };
     });
@@ -272,17 +273,19 @@ describe('The Annotation API', () => {
     let comparator;
 
     beforeEach(() => {
-      targets = {
-        fooTarget: {
+      targets = [
+        {
+          keyString: 'fooTarget',
           foo: 42
         }
-      };
-      otherTargets = {
-        fooTarget: {
+      ];
+      otherTargets = [
+        {
+          keyString: 'fooTarget',
           bar: 42
         }
-      };
-      comparator = (t1, t2) => t1.fooTarget.foo === t2.fooTarget.bar;
+      ];
+      comparator = (t1, t2) => t1[0].foo === t2[0].bar;
     });
 
     it('can add a comparator function', () => {
diff --git a/src/api/objects/InMemorySearchProvider.js b/src/api/objects/InMemorySearchProvider.js
index 15e66a2e79..0ac546983d 100644
--- a/src/api/objects/InMemorySearchProvider.js
+++ b/src/api/objects/InMemorySearchProvider.js
@@ -435,7 +435,8 @@ class InMemorySearchProvider {
   }
 
   localIndexAnnotation(objectToIndex, model) {
-    Object.keys(model.targets).forEach((targetID) => {
+    model.targets.forEach((target) => {
+      const targetID = target.keyString;
       if (!this.localIndexedAnnotationsByDomainObject[targetID]) {
         this.localIndexedAnnotationsByDomainObject[targetID] = [];
       }
diff --git a/src/api/objects/InMemorySearchWorker.js b/src/api/objects/InMemorySearchWorker.js
index 121d3b1d26..38e65222be 100644
--- a/src/api/objects/InMemorySearchWorker.js
+++ b/src/api/objects/InMemorySearchWorker.js
@@ -57,7 +57,8 @@
   };
 
   function indexAnnotation(objectToIndex, model) {
-    Object.keys(model.targets).forEach((targetID) => {
+    model.targets.forEach((target) => {
+      const targetID = target.keyString;
       if (!indexedAnnotationsByDomainObject[targetID]) {
         indexedAnnotationsByDomainObject[targetID] = [];
       }
diff --git a/src/plugins/imagery/components/AnnotationsCanvas.vue b/src/plugins/imagery/components/AnnotationsCanvas.vue
index c979d80b0d..7d9d160eaa 100644
--- a/src/plugins/imagery/components/AnnotationsCanvas.vue
+++ b/src/plugins/imagery/components/AnnotationsCanvas.vue
@@ -33,6 +33,8 @@
 
 <script>
 import Flatbush from 'flatbush';
+import { toRaw } from 'vue';
+
 const EXISTING_ANNOTATION_STROKE_STYLE = '#D79078';
 const EXISTING_ANNOTATION_FILL_STYLE = 'rgba(202, 202, 142, 0.2)';
 const SELECTED_ANNOTATION_STROKE_COLOR = '#BD8ECC';
@@ -70,7 +72,9 @@ export default {
         // create a flatbush index for the annotations
         const builtAnnotationsIndex = new Flatbush(this.imageryAnnotations.length);
         this.imageryAnnotations.forEach((annotation) => {
-          const annotationRectangle = annotation.targets[this.keyString].rectangle;
+          const annotationRectangle = annotation.targets.find(
+            (target) => target.keyString === this.keyString
+          )?.rectangle;
           const annotationRectangleForPixelDepth =
             this.transformRectangleToPixelDense(annotationRectangle);
           const indexNumber = builtAnnotationsIndex.add(
@@ -141,20 +145,17 @@ export default {
       this.prepareExistingAnnotationSelection(incomingSelectedAnnotations);
     },
     prepareExistingAnnotationSelection(annotations) {
-      const targetDomainObjects = {};
-      targetDomainObjects[this.keyString] = this.domainObject;
-
-      const targetDetails = {};
+      const targetDetails = [];
       annotations.forEach((annotation) => {
-        Object.entries(annotation.targets).forEach(([key, value]) => {
-          targetDetails[key] = value;
+        annotation.targets.forEach((target) => {
+          targetDetails.push(toRaw(target));
         });
       });
       this.selectedAnnotations = annotations;
       this.drawAnnotations();
 
       return {
-        targetDomainObjects,
+        targetDomainObjects: [this.domainObject],
         targetDetails
       };
     },
@@ -292,9 +293,6 @@ export default {
       this.dragging = false;
       this.selectedAnnotations = [];
 
-      const targetDomainObjects = {};
-      targetDomainObjects[this.keyString] = this.domainObject;
-      const targetDetails = {};
       const rectangleFromCanvas = {
         x: this.newAnnotationRectangle.x,
         y: this.newAnnotationRectangle.y,
@@ -302,13 +300,16 @@ export default {
         height: this.newAnnotationRectangle.height
       };
       const rectangleWithoutPixelScale = this.transformRectangleFromPixelDense(rectangleFromCanvas);
-      targetDetails[this.keyString] = {
-        rectangle: rectangleWithoutPixelScale,
-        time: this.image.time
-      };
+      const targetDetails = [
+        {
+          rectangle: rectangleWithoutPixelScale,
+          time: this.image.time,
+          keyString: this.keyString
+        }
+      ];
       this.selectImageAnnotations({
         targetDetails,
-        targetDomainObjects,
+        targetDomainObjects: [this.domainObject],
         annotations: []
       });
     },
@@ -403,9 +404,10 @@ export default {
         if (annotation._deleted) {
           return;
         }
-        const rectangleForPixelDensity = this.transformRectangleToPixelDense(
-          annotation.targets[this.keyString].rectangle
-        );
+        const annotationRectangle = annotation.targets.find(
+          (target) => target.keyString === this.keyString
+        )?.rectangle;
+        const rectangleForPixelDensity = this.transformRectangleToPixelDense(annotationRectangle);
         if (this.isSelectedAnnotation(annotation)) {
           this.drawRectInCanvas(
             rectangleForPixelDensity,
diff --git a/src/plugins/inspectorViews/annotations/AnnotationsInspectorView.vue b/src/plugins/inspectorViews/annotations/AnnotationsInspectorView.vue
index c7b42428d9..d28bb10397 100644
--- a/src/plugins/inspectorViews/annotations/AnnotationsInspectorView.vue
+++ b/src/plugins/inspectorViews/annotations/AnnotationsInspectorView.vue
@@ -94,10 +94,10 @@ export default {
       return this?.selection?.[0]?.[0]?.context?.item;
     },
     targetDetails() {
-      return this?.selection?.[0]?.[0]?.context?.targetDetails ?? {};
+      return this?.selection?.[0]?.[0]?.context?.targetDetails ?? [];
     },
     shouldShowTagsEditor() {
-      const showingTagsEditor = Object.keys(this.targetDetails).length > 0;
+      const showingTagsEditor = this.targetDetails?.length > 0;
 
       if (showingTagsEditor) {
         return true;
@@ -106,7 +106,7 @@ export default {
       return false;
     },
     targetDomainObjects() {
-      return this?.selection?.[0]?.[0]?.context?.targetDomainObjects ?? {};
+      return this?.selection?.[0]?.[0]?.context?.targetDomainObjects ?? [];
     },
     selectedAnnotations() {
       return this?.selection?.[0]?.[0]?.context?.annotations;
@@ -167,9 +167,8 @@ export default {
       this.unobserveEntries = {};
 
       this.selection = selection;
-      const targetKeys = Object.keys(this.targetDomainObjects);
-      targetKeys.forEach((targetKey) => {
-        const targetObject = this.targetDomainObjects[targetKey];
+      this.targetDomainObjects.forEach((targetObject) => {
+        const targetKey = targetObject.keyString;
         this.lastLocalAnnotationCreations[targetKey] = targetObject?.annotationLastCreated ?? 0;
         if (!this.unobserveEntries[targetKey]) {
           this.unobserveEntries[targetKey] = this.openmct.objects.observe(
diff --git a/src/plugins/inspectorViews/annotations/tags/TagEditor.vue b/src/plugins/inspectorViews/annotations/tags/TagEditor.vue
index c434150958..97e71b29e3 100644
--- a/src/plugins/inspectorViews/annotations/tags/TagEditor.vue
+++ b/src/plugins/inspectorViews/annotations/tags/TagEditor.vue
@@ -69,12 +69,12 @@ export default {
       default: null
     },
     targets: {
-      type: Object,
+      type: Array,
       required: true,
       default: null
     },
     targetDomainObjects: {
-      type: Object,
+      type: Array,
       required: true,
       default: null
     },
@@ -201,11 +201,8 @@ export default {
         const contentText = `${this.annotationType} tag`;
 
         // need to get raw version of target domain objects for comparisons to work
-        const rawTargetDomainObjects = {};
-        Object.keys(this.targetDomainObjects).forEach((targetDomainObjectKey) => {
-          rawTargetDomainObjects[targetDomainObjectKey] = toRaw(
-            this.targetDomainObjects[targetDomainObjectKey]
-          );
+        const rawTargetDomainObjects = this.targetDomainObjects.map((targetDomainObject) => {
+          return toRaw(targetDomainObject);
         });
         const annotationCreationArguments = {
           name: contentText,
diff --git a/src/plugins/notebook/actions/ExportNotebookAsTextAction.js b/src/plugins/notebook/actions/ExportNotebookAsTextAction.js
index fbabe444a7..b359e9914b 100644
--- a/src/plugins/notebook/actions/ExportNotebookAsTextAction.js
+++ b/src/plugins/notebook/actions/ExportNotebookAsTextAction.js
@@ -33,7 +33,9 @@ export default class ExportNotebookAsTextAction {
   getTagsForEntry(entry, domainObjectKeyString, annotations) {
     const foundTags = [];
     annotations.forEach((annotation) => {
-      const target = annotation.targets?.[domainObjectKeyString];
+      const target = annotation.targets.find(
+        (annotationTarget) => annotationTarget.keyString === domainObjectKeyString
+      );
       if (target?.entryId === entry.id) {
         annotation.tags.forEach((tag) => {
           if (!foundTags.includes(tag)) {
diff --git a/src/plugins/notebook/components/Notebook.vue b/src/plugins/notebook/components/Notebook.vue
index d56228e922..fb65a2aaf0 100644
--- a/src/plugins/notebook/components/Notebook.vue
+++ b/src/plugins/notebook/components/Notebook.vue
@@ -395,8 +395,8 @@ export default {
       );
 
       foundAnnotations.forEach((foundAnnotation) => {
-        const targetId = Object.keys(foundAnnotation.targets)[0];
-        const entryId = foundAnnotation.targets[targetId].entryId;
+        const target = foundAnnotation.targets?.[0];
+        const entryId = target.entryId;
         if (!this.notebookAnnotations[entryId]) {
           this.notebookAnnotations[entryId] = [];
         }
diff --git a/src/plugins/notebook/monkeyPatchObjectAPIForNotebooks.js b/src/plugins/notebook/monkeyPatchObjectAPIForNotebooks.js
index 7c617df38e..c7af753d65 100644
--- a/src/plugins/notebook/monkeyPatchObjectAPIForNotebooks.js
+++ b/src/plugins/notebook/monkeyPatchObjectAPIForNotebooks.js
@@ -51,13 +51,18 @@ async function resolveNotebookTagConflicts(localAnnotation, openmct) {
     throw new Error("Conflict on annotation's tag has different tags than remote");
   }
 
-  Object.keys(localClonedAnnotation.targets).forEach((targetKey) => {
-    if (!remoteMutable.targets[targetKey]) {
+  localClonedAnnotation.targets.forEach((target) => {
+    const targetKey = target.keyString;
+
+    const remoteMutableTarget = remoteMutable.targets.find((remoteTarget) => {
+      return remoteTarget.keyString === targetKey;
+    });
+    if (!remoteMutableTarget) {
       throw new Error(`Conflict on annotation's target is missing ${targetKey}`);
     }
-
-    const remoteMutableTarget = remoteMutable.targets[targetKey];
-    const localMutableTarget = localClonedAnnotation.targets[targetKey];
+    const localMutableTarget = localClonedAnnotation.targets.find((localTarget) => {
+      return localTarget.keyString === targetKey;
+    });
 
     if (remoteMutableTarget.entryId !== localMutableTarget.entryId) {
       throw new Error(
diff --git a/src/plugins/notebook/utils/notebook-entries.js b/src/plugins/notebook/utils/notebook-entries.js
index daf483783e..0d9bdd4fa5 100644
--- a/src/plugins/notebook/utils/notebook-entries.js
+++ b/src/plugins/notebook/utils/notebook-entries.js
@@ -66,13 +66,14 @@ export function selectEntry({
   onAnnotationChange,
   notebookAnnotations
 }) {
-  const targetDetails = {};
   const keyString = openmct.objects.makeKeyString(domainObject.identifier);
-  targetDetails[keyString] = {
-    entryId
-  };
-  const targetDomainObjects = {};
-  targetDomainObjects[keyString] = domainObject;
+  const targetDetails = [
+    {
+      entryId,
+      keyString
+    }
+  ];
+  const targetDomainObjects = [domainObject];
   openmct.selection.select(
     [
       {
diff --git a/src/plugins/persistence/couch/CouchObjectProvider.js b/src/plugins/persistence/couch/CouchObjectProvider.js
index f4ff3fbe5f..786df222f6 100644
--- a/src/plugins/persistence/couch/CouchObjectProvider.js
+++ b/src/plugins/persistence/couch/CouchObjectProvider.js
@@ -38,6 +38,7 @@ class CouchObjectProvider {
     this.openmct = openmct;
     this.indicator = indicator;
     this.url = options.url;
+    this.useDesignDocuments = options.useDesignDocuments;
     this.namespace = namespace;
     this.objectQueue = {};
     this.observers = {};
@@ -187,7 +188,8 @@ class CouchObjectProvider {
   #normalize(options) {
     if (typeof options === 'string') {
       return {
-        url: options
+        url: options,
+        useDesignDocuments: false
       };
     }
 
@@ -436,6 +438,39 @@ class CouchObjectProvider {
     return Promise.resolve([]);
   }
 
+  async getObjectsByView({ designDoc, viewName, keysToSearch }, abortSignal) {
+    const stringifiedKeys = JSON.stringify(keysToSearch);
+    const url = `${this.url}/_design/${designDoc}/_view/${viewName}?keys=${stringifiedKeys}&include_docs=true`;
+    let objectModels = [];
+
+    try {
+      const response = await fetch(url, {
+        method: 'GET',
+        headers: { 'Content-Type': 'application/json' },
+        signal: abortSignal
+      });
+
+      if (!response.ok) {
+        throw new Error(
+          `HTTP request failed with status ${response.status} ${response.statusText}`
+        );
+      }
+
+      const result = await response.json();
+      const couchRows = result.rows;
+      couchRows.forEach((couchRow) => {
+        const couchDoc = couchRow.doc;
+        const objectModel = this.#getModel(couchDoc);
+        if (objectModel) {
+          objectModels.push(objectModel);
+        }
+      });
+    } catch (error) {
+      // do nothing
+    }
+    return objectModels;
+  }
+
   async getObjectsByFilter(filter, abortSignal) {
     let objects = [];
 
diff --git a/src/plugins/persistence/couch/CouchSearchProvider.js b/src/plugins/persistence/couch/CouchSearchProvider.js
index 53cb8b9d68..e8341e182a 100644
--- a/src/plugins/persistence/couch/CouchSearchProvider.js
+++ b/src/plugins/persistence/couch/CouchSearchProvider.js
@@ -37,6 +37,7 @@ class CouchSearchProvider {
   constructor(couchObjectProvider) {
     this.couchObjectProvider = couchObjectProvider;
     this.searchTypes = couchObjectProvider.openmct.objects.SEARCH_TYPES;
+    this.useDesignDocuments = couchObjectProvider.useDesignDocuments;
     this.supportedSearchTypes = [
       this.searchTypes.OBJECTS,
       this.searchTypes.ANNOTATIONS,
@@ -102,6 +103,25 @@ class CouchSearchProvider {
   }
 
   #bulkAnnotationSearch(batchIdsToSearch) {
+    if (!batchIdsToSearch?.length) {
+      // nothing to search
+      return;
+    }
+
+    let lastAbortSignal = batchIdsToSearch[batchIdsToSearch.length - 1].abortSignal;
+
+    if (this.useDesignDocuments) {
+      const keysToSearch = batchIdsToSearch.map(({ keyString }) => keyString);
+      return this.couchObjectProvider.getObjectsByView(
+        {
+          designDoc: 'annotation_keystring_index',
+          viewName: 'by_keystring',
+          keysToSearch
+        },
+        lastAbortSignal
+      );
+    }
+
     const filter = {
       selector: {
         $and: [
@@ -111,25 +131,20 @@ class CouchSearchProvider {
             }
           },
           {
-            $or: []
+            'model.targets': {
+              $elemMatch: {
+                keyString: {
+                  $in: []
+                }
+              }
+            }
           }
         ]
       }
     };
-    let lastAbortSignal = null;
     // TODO: should remove duplicates from batchIds
     batchIdsToSearch.forEach(({ keyString, abortSignal }) => {
-      const modelFilter = {
-        model: {
-          targets: {}
-        }
-      };
-      modelFilter.model.targets[keyString] = {
-        $exists: true
-      };
-
-      filter.selector.$and[1].$or.push(modelFilter);
-      lastAbortSignal = abortSignal;
+      filter.selector.$and[1]['model.targets'].$elemMatch.keyString.$in.push(keyString);
     });
 
     return this.couchObjectProvider.getObjectsByFilter(filter, lastAbortSignal);
@@ -142,11 +157,7 @@ class CouchSearchProvider {
     }
 
     const returnedData = await this.#bulkPromise;
-    // only return data that matches the keystring
-    const filteredByKeyString = returnedData.filter((foundAnnotation) => {
-      return foundAnnotation.targets[keyString];
-    });
-    return filteredByKeyString;
+    return returnedData;
   }
 
   searchForTags(tagsArray, abortSignal) {
@@ -154,28 +165,33 @@ class CouchSearchProvider {
       return [];
     }
 
+    if (this.useDesignDocuments) {
+      return this.couchObjectProvider.getObjectsByView(
+        { designDoc: 'annotation_tags_index', viewName: 'by_tags', keysToSearch: tagsArray },
+        abortSignal
+      );
+    }
+
     const filter = {
       selector: {
         $and: [
           {
-            'model.tags': {
-              $elemMatch: {
-                $or: []
-              }
+            'model.type': {
+              $eq: 'annotation'
             }
           },
           {
-            'model.type': {
-              $eq: 'annotation'
+            'model.tags': {
+              $elemMatch: {
+                $in: []
+              }
             }
           }
         ]
       }
     };
     tagsArray.forEach((tag) => {
-      filter.selector.$and[0]['model.tags'].$elemMatch.$or.push({
-        $eq: `${tag}`
-      });
+      filter.selector.$and[1]['model.tags'].$elemMatch.$in.push(tag);
     });
 
     return this.couchObjectProvider.getObjectsByFilter(filter, abortSignal);
diff --git a/src/plugins/persistence/couch/README.md b/src/plugins/persistence/couch/README.md
index ef19f513b6..be09065a17 100644
--- a/src/plugins/persistence/couch/README.md
+++ b/src/plugins/persistence/couch/README.md
@@ -141,7 +141,7 @@ sh ./src/plugins/persistence/couch/replace-localstorage-with-couchdb-indexhtml.s
   Add a line to install the CouchDB plugin for Open MCT:
 
   ```js
-  openmct.install(openmct.plugins.CouchDB("http://localhost:5984/openmct"));
+  openmct.install(openmct.plugins.CouchDB({url: "http://localhost:5984/openmct", useDesignDocuments: false}));
   ```
 
 # Validating a successful Installation
@@ -151,3 +151,63 @@ sh ./src/plugins/persistence/couch/replace-localstorage-with-couchdb-indexhtml.s
 3. Navigate to: <http://127.0.0.1:5984/_utils/#database/openmct/_all_docs>
 4. Look at the 'JSON' tab and ensure you can see the specific object you created above.
 5. All done! 🏆
+
+# Search Performance
+
+For large Open MCT installations, it may be helpful to add additional CouchDB capabilities to bear to improve performance.
+
+## Indexing
+Indexing the `model.type` field in CouchDB can benefit the performance of queries significantly, particularly if there are a large number of documents in the database. An index can accelerate annotation searches by reducing the number of documents that the database needs to examine.
+
+To create an index for `model.type`, you can use the following payload:
+
+```json
+{
+  "index": {
+    "fields": ["model.type", "model.tags"]
+  },
+  "name": "type_tags_index",
+  "type": "json"
+}
+```
+
+This instructs CouchDB to create an index on the `model.type` field and the `model.tags` field. Once this index is created, queries that include a selector on `model.type` and `model.tags` (like when searching for tags) can use this index to retrieve results faster.
+
+You can find more detailed information about indexing in CouchDB in the [official documentation](https://docs.couchdb.org/en/stable/api/database/find.html#db-index).
+
+## Design Documents
+
+We can also add a design document for retrieving domain objects for specific tags:
+
+```json
+{
+  "_id": "_design/annotation_tags_index",
+  "views": {
+    "by_tags": {
+      "map": "function (doc) { if (doc.model && doc.model.type === 'annotation' && doc.model.tags) { doc.model.tags.forEach(function (tag) { emit(tag, doc._id); }); } }"
+    }
+  }
+}
+```
+and can be retrieved by issuing a `GET` to http://localhost:5984/openmct/_design/annotation_tags_index/_view/by_tags?keys=["TAG_ID_TO_SEARCH_FOR"]&include_docs=true
+where `TAG_ID_TO_SEARCH_FOR` is the tag UUID we're looking for.
+
+and for targets:
+```javascript
+{
+  "_id": "_design/annotation_keystring_index",
+  "views": {
+    "by_keystring": {
+      "map": "function (doc) { if (doc.model && doc.model.type === 'annotation' && doc.model.targets) { doc.model.targets.forEach(function(target) { if(target.keyString) { emit(target.keyString, doc._id); } }); } }"
+    }
+  }
+}
+```
+and can be retrieved by issuing a `GET` to http://localhost:5984/openmct/_design/annotation_keystring_index/_view/by_keystring?keys=["KEY_STRING_TO_SEARCH_FOR"]&include_docs=true
+where `KEY_STRING_TO_SEARCH_FOR` is the UUID we're looking for.
+
+To enable them in Open MCT, we need to configure the plugin `useDesignDocuments` like so:
+
+  ```js
+  openmct.install(openmct.plugins.CouchDB({url: "http://localhost:5984/openmct", useDesignDocuments: true}));
+  ```
diff --git a/src/plugins/plot/MctPlot.vue b/src/plugins/plot/MctPlot.vue
index 8ecebc3f5e..296cbf77b6 100644
--- a/src/plugins/plot/MctPlot.vue
+++ b/src/plugins/plot/MctPlot.vue
@@ -476,7 +476,7 @@ export default {
         // the annotations
         this.freeze();
         // just use first annotation
-        const boundingBoxes = Object.values(selectedAnnotations[0].targets);
+        const boundingBoxes = selectedAnnotations[0].targets;
         let minX = Number.MAX_SAFE_INTEGER;
         let minY = Number.MAX_SAFE_INTEGER;
         let maxX = Number.MIN_SAFE_INTEGER;
@@ -863,8 +863,8 @@ export default {
 
     marqueeAnnotations(annotationsToSelect) {
       annotationsToSelect.forEach((annotationToSelect) => {
-        Object.keys(annotationToSelect.targets).forEach((targetKeyString) => {
-          const target = annotationToSelect.targets[targetKeyString];
+        annotationToSelect.targets.forEach((target) => {
+          const targetKeyString = target.keyString;
           const series = this.seriesModels.find(
             (seriesModel) => seriesModel.keyString === targetKeyString
           );
@@ -912,17 +912,14 @@ export default {
     },
 
     prepareExistingAnnotationSelection(annotations) {
-      const targetDomainObjects = {};
-      this.config.series.models.forEach((series) => {
-        targetDomainObjects[series.keyString] = series.domainObject;
+      const targetDomainObjects = this.config.series.models.map((series) => {
+        return series.domainObject;
       });
 
-      const targetDetails = {};
+      const targetDetails = [];
       const uniqueBoundsAnnotations = [];
       annotations.forEach((annotation) => {
-        Object.entries(annotation.targets).forEach(([key, value]) => {
-          targetDetails[key] = value;
-        });
+        targetDetails.push(annotation.targets);
 
         const boundingBoxAlreadyAdded = uniqueBoundsAnnotations.some((existingAnnotation) => {
           const existingBoundingBox = Object.values(existingAnnotation.targets)[0];
@@ -1332,17 +1329,17 @@ export default {
       document.body.addEventListener('click', this.cancelSelection);
     },
     selectNewPlotAnnotations(boundingBoxPerYAxis, pointsInBoxBySeries, event) {
-      let targetDomainObjects = {};
-      let targetDetails = {};
+      let targetDomainObjects = [];
+      let targetDetails = [];
       let annotations = [];
       Object.keys(pointsInBoxBySeries).forEach((seriesKey) => {
         const seriesModel = this.getSeries(seriesKey);
         const boundingBoxWithId = boundingBoxPerYAxis.find(
           (box) => box.id === seriesModel.get('yAxisId')
         );
-        targetDetails[seriesKey] = boundingBoxWithId?.boundingBox;
+        targetDetails.push({ ...boundingBoxWithId?.boundingBox, keyString: seriesKey });
 
-        targetDomainObjects[seriesKey] = seriesModel.domainObject;
+        targetDomainObjects.push(seriesModel.domainObject);
       });
       this.selectPlotAnnotations({
         targetDetails,
@@ -1354,8 +1351,8 @@ export default {
       const annotationsBySeries = {};
       rawAnnotations.forEach((rawAnnotation) => {
         if (rawAnnotation.targets) {
-          const targetValues = Object.values(rawAnnotation.targets);
-          const targetKeys = Object.keys(rawAnnotation.targets);
+          const targetValues = rawAnnotation.targets;
+          const targetKeys = rawAnnotation.targets.map((target) => target.keyString);
           if (targetValues && targetValues.length) {
             let boundingBoxPerYAxis = [];
             targetValues.forEach((boundingBox, index) => {
diff --git a/src/ui/layout/search/AnnotationSearchResult.vue b/src/ui/layout/search/AnnotationSearchResult.vue
index 64527facbf..b506daf4f5 100644
--- a/src/ui/layout/search/AnnotationSearchResult.vue
+++ b/src/ui/layout/search/AnnotationSearchResult.vue
@@ -158,24 +158,14 @@ export default {
     },
     fireAnnotationSelection() {
       this.openmct.selection.off('change', this.fireAnnotationSelection);
-
-      const targetDetails = {};
-      const targetDomainObjects = {};
-      Object.entries(this.result.targets).forEach(([key, value]) => {
-        targetDetails[key] = value;
-      });
-      this.result.targetModels.forEach((targetModel) => {
-        const keyString = this.openmct.objects.makeKeyString(targetModel.identifier);
-        targetDomainObjects[keyString] = targetModel;
-      });
       const selection = [
         {
           element: this.$el,
           context: {
             item: this.result.targetModels[0],
             type: 'annotation-search-result',
-            targetDetails,
-            targetDomainObjects,
+            targetDetails: this.result.targets,
+            targetDomainObjects: this.result.targetModels,
             annotations: [this.result],
             annotationType: this.result.annotationType,
             onAnnotationChange: () => {}
diff --git a/src/ui/layout/search/GrandSearchSpec.js b/src/ui/layout/search/GrandSearchSpec.js
index 42d583d010..91ed9dd809 100644
--- a/src/ui/layout/search/GrandSearchSpec.js
+++ b/src/ui/layout/search/GrandSearchSpec.js
@@ -126,11 +126,12 @@ describe('GrandSearch', () => {
         key: 'anAnnotationKey',
         namespace: 'fooNameSpace'
       },
-      targets: {
-        'fooNameSpace:some-object': {
+      targets: [
+        {
+          keyString: 'fooNameSpace:some-object',
           entryId: 'fooBarEntry'
         }
-      }
+      ]
     };
     mockNewObject = {
       type: 'folder',