diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java b/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java
index d1a7cb38c4654bd2021cd2eab567f577ba762c54..89b8e4cd79453c2081308ccd3776f8aaf02844b1 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java
@@ -57,6 +57,7 @@ public class Listing extends NodeBase {
 	private long updateDate;
 	@JsonbProperty(DatabaseFieldNames.LICENSE_TYPE)
 	private String license;
+	private List<String> marketIds;
 	private List<String> categoryIds;
 	private List<Category> categories;
 	private Organization organization;
@@ -71,6 +72,7 @@ public class Listing extends NodeBase {
 		this.authors = new ArrayList<>();
 		this.tags = new ArrayList<>();
 		this.versions = new ArrayList<>();
+		this.marketIds = new ArrayList<>();
 		this.categoryIds = new ArrayList<>();
 		this.categories = new ArrayList<>();
 	}
@@ -273,6 +275,21 @@ public class Listing extends NodeBase {
 		this.categoryIds = new ArrayList<>(categoryIds);
 	}
 
+
+	/**
+	 * @return the categoryIds
+	 */
+	public List<String> getMarketIds() {
+		return new ArrayList<>(marketIds);
+	}
+
+	/**
+	 * @param marketIds the categoryIds to set
+	 */
+	public void setMarketIds(List<String> marketIds) {
+		this.marketIds = new ArrayList<>(marketIds);
+	}
+
 	/**
 	 * @return the categories
 	 */
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/Market.java b/src/main/java/org/eclipsefoundation/marketplace/dto/Market.java
index b7ce51aead624664474c3831732e0c45e6579f93..911c0506ea9726bb8d94340624b5a73ef39f7438 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/Market.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/Market.java
@@ -26,7 +26,6 @@ import io.quarkus.runtime.annotations.RegisterForReflection;
  */
 @RegisterForReflection
 public class Market extends NodeBase {
-	private List<String> categoryIds;
 	private List<Category> categories;
 
 
@@ -36,7 +35,6 @@ public class Market extends NodeBase {
 	 */
 	public Market() {
 		this.categories = new LinkedList<>();
-		this.categoryIds = new LinkedList<>();
 	}
 
 	/**
@@ -54,26 +52,11 @@ public class Market extends NodeBase {
 		this.categories = new ArrayList<>(categories);
 	}
 
-	/**
-	 * @return the categoryIds
-	 */
-	@JsonbTransient
-	public List<String> getCategoryIds() {
-		return new ArrayList<>(categoryIds);
-	}
-
-	/**
-	 * @param categoryIds the categoryIds to set
-	 */
-	public void setCategoryIds(List<String> categoryIds) {
-		this.categoryIds = new ArrayList<>(categoryIds);
-	}
-
 	@Override
 	public int hashCode() {
 		final int prime = 31;
 		int result = super.hashCode();
-		result = prime * result + Objects.hash(categories, categoryIds);
+		result = prime * result + Objects.hash(categories);
 		return result;
 	}
 
@@ -89,6 +72,6 @@ public class Market extends NodeBase {
 			return false;
 		}
 		Market other = (Market) obj;
-		return Objects.equals(categories, other.categories) && Objects.equals(categoryIds, other.categoryIds);
+		return Objects.equals(categories, other.categories);
 	}
 }
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java
index 19daaffe552d45d451e47aae56f38d7ed54a98a8..944bf96d207ec1543a75b03f886a43c99a2acf7c 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java
@@ -80,6 +80,7 @@ public class ListingCodec implements CollectibleCodec<Listing> {
 		doc.put(DatabaseFieldNames.CREATION_DATE, new Date(value.getCreationDate()));
 		doc.put(DatabaseFieldNames.FOUNDATION_MEMBER_FLAG, value.isFoundationMember());
 		doc.put(DatabaseFieldNames.CATEGORY_IDS, value.getCategoryIds());
+		doc.put(DatabaseFieldNames.MARKET_IDS, value.getMarketIds());
 
 		// for nested document types, use the converters to safely transform into BSON
 		// documents
@@ -118,6 +119,7 @@ public class ListingCodec implements CollectibleCodec<Listing> {
 		out.setFavoriteCount(document.getLong(DatabaseFieldNames.MARKETPLACE_FAVORITES));
 		out.setFoundationMember(document.getBoolean(DatabaseFieldNames.FOUNDATION_MEMBER_FLAG));
 		out.setCategoryIds(document.getList(DatabaseFieldNames.CATEGORY_IDS, String.class));
+		out.setMarketIds(document.getList(DatabaseFieldNames.MARKET_IDS, String.class));
 
 		// for nested document types, use the converters to safely transform into POJO
 		out.setAuthors(document.getList(DatabaseFieldNames.LISTING_AUTHORS, Document.class).stream()
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/MarketCodec.java b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/MarketCodec.java
index 4a2631d713fdbfd81bf0d08a1d35967809fa81eb..3a108d0c0f66685beb49a2984d922cd0c8255b0a 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/MarketCodec.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/MarketCodec.java
@@ -7,6 +7,7 @@
 package org.eclipsefoundation.marketplace.dto.codecs;
 
 import java.util.UUID;
+import java.util.stream.Collectors;
 
 import org.apache.commons.lang3.StringUtils;
 import org.bson.BsonReader;
@@ -19,6 +20,7 @@ import org.bson.codecs.CollectibleCodec;
 import org.bson.codecs.DecoderContext;
 import org.bson.codecs.EncoderContext;
 import org.eclipsefoundation.marketplace.dto.Market;
+import org.eclipsefoundation.marketplace.dto.converters.CategoryConverter;
 import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames;
 
 import com.mongodb.MongoClient;
@@ -32,6 +34,7 @@ import com.mongodb.MongoClient;
  */
 public class MarketCodec implements CollectibleCodec<Market> {
 	private final Codec<Document> documentCodec;
+	private final CategoryConverter categoryConverter;
 
 	/**
 	 * Creates the codec and initializes the codecs and converters needed to create
@@ -39,6 +42,7 @@ public class MarketCodec implements CollectibleCodec<Market> {
 	 */
 	public MarketCodec() {
 		this.documentCodec = MongoClient.getDefaultCodecRegistry().get(Document.class);
+		this.categoryConverter = new CategoryConverter();
 	}
 	
 	@Override
@@ -48,7 +52,6 @@ public class MarketCodec implements CollectibleCodec<Market> {
 		doc.put(DatabaseFieldNames.DOCID, value.getId());
 		doc.put(DatabaseFieldNames.URL, value.getUrl());
 		doc.put(DatabaseFieldNames.TITLE, value.getTitle());
-		doc.put(DatabaseFieldNames.CATEGORY_IDS, value.getCategoryIds());
 
 		documentCodec.encode(writer, doc, encoderContext);
 	}
@@ -61,10 +64,13 @@ public class MarketCodec implements CollectibleCodec<Market> {
 	@Override
 	public Market decode(BsonReader reader, DecoderContext decoderContext) {
 		Document document = documentCodec.decode(reader, decoderContext);
+
 		Market out = new Market();
 		out.setId(document.getString(DatabaseFieldNames.DOCID));
 		out.setUrl(document.getString(DatabaseFieldNames.URL));
 		out.setTitle(document.getString(DatabaseFieldNames.TITLE));
+		out.setCategories(document.getList(DatabaseFieldNames.LISTING_CATEGORIES, Document.class).stream()
+				.map(categoryConverter::convert).collect(Collectors.toList()));
 		
 		return out;
 	}
diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/MarketFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/MarketFilter.java
index 7472d7549fe8c474547a536dfc236c4450b5195c..997a882210d5bbea1959b7e0ee264e1eb8f61011 100644
--- a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/MarketFilter.java
+++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/MarketFilter.java
@@ -6,19 +6,28 @@
  */
 package org.eclipsefoundation.marketplace.dto.filter;
 
+import static com.mongodb.client.model.Filters.and;
+import static com.mongodb.client.model.Filters.eq;
+import static com.mongodb.client.model.Filters.expr;
+
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
 import javax.enterprise.context.ApplicationScoped;
 
+import org.bson.BsonArray;
 import org.bson.conversions.Bson;
 import org.eclipsefoundation.marketplace.dto.Market;
 import org.eclipsefoundation.marketplace.model.RequestWrapper;
 import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames;
 import org.eclipsefoundation.marketplace.namespace.DtoTableNames;
 
+import com.mongodb.client.model.Accumulators;
 import com.mongodb.client.model.Aggregates;
+import com.mongodb.client.model.Projections;
+import com.mongodb.client.model.Variable;
 
 /**
  * Filter implementation for the {@linkplain Market} class.
@@ -36,10 +45,48 @@ public class MarketFilter implements DtoFilter<Market> {
 	@Override
 	public List<Bson> getAggregates(RequestWrapper wrap) {
 		List<Bson> aggs = new ArrayList<>();
+
+		String tempFieldName = "tmp";
+		List<Bson> pipeline = new ArrayList<>();
+		// match the listings on the given market_id
+		pipeline.add(
+				Aggregates.match(expr(eq("$in", Arrays.asList("$$market_id", "$" + DatabaseFieldNames.MARKET_IDS)))));
+		// suppress all fields except category_ids
+		pipeline.add(Aggregates.project(
+				Projections.fields(Projections.excludeId(), Projections.include(DatabaseFieldNames.CATEGORY_IDS))));
+
+		// set up a var reference for the _id
+		Variable<String> id = new Variable<String>("market_id", "$" + DatabaseFieldNames.DOCID);
+		// lookup all category IDS from listings with the given market ID
+		aggs.add(Aggregates.lookup(DtoTableNames.LISTING.getTableName(), Arrays.asList(id), pipeline, tempFieldName));
+		// explode all category IDS for collection
+		aggs.add(Aggregates.unwind("$" + tempFieldName));
+
+		// flatten categories using projection, and retain original data through data
+		// field
+		aggs.add(Aggregates.group("$_id", Accumulators.first("data", "$$ROOT"),
+				Accumulators.push(tempFieldName, "$" + tempFieldName + "." + DatabaseFieldNames.CATEGORY_IDS)));
+
+		// no reduction shortcuts in driver, build documents from scratch
+		// in operation merges multiple lists using sets to deduplicate
+		Bson inOperation = eq("$setUnion", Arrays.asList("$$value", "$$this"));
+		Bson reductionOptions = eq(tempFieldName, eq("$reduce",
+				and(eq("input", "$" + tempFieldName), eq("initialValue", new BsonArray()), eq("in", inOperation))));
+
+		// using projections, retain data-root + tmp category IDS and reduce them
+		aggs.add(Aggregates.project(Projections.fields(Projections.include("data"), reductionOptions)));
+
+		// create custom array as mergeObjects uses non-standard syntax
+		BsonArray ba = BsonArray.parse("[ '$data', {'" + tempFieldName + "': '$" + tempFieldName + "'}]");
+		// replaceRoot to restore original root data + set data for category IDs
+		aggs.add(Aggregates.replaceRoot(eq("$mergeObjects", ba)));
+
 		// adds a $lookup aggregate, joining categories on categoryIDS as "categories"
-		aggs.add(Aggregates.lookup(DtoTableNames.CATEGORY.getTableName(), DatabaseFieldNames.CATEGORY_IDS, DatabaseFieldNames.DOCID,
+		aggs.add(Aggregates.lookup(DtoTableNames.CATEGORY.getTableName(), tempFieldName, DatabaseFieldNames.DOCID,
 				"categories"));
-		
+
+		// remove the unneeded temporary field
+		aggs.add(Aggregates.project(Projections.exclude(tempFieldName)));
 		return aggs;
 	}
 
diff --git a/src/main/node/index.js b/src/main/node/index.js
index 3f7cc76cbafb26dde0650b1ce7433214d69402d5..e0899796e7fce1255aac9fc2eb3dae077981821e 100644
--- a/src/main/node/index.js
+++ b/src/main/node/index.js
@@ -27,11 +27,11 @@ const platforms = ["windows","macos","linux"];
 const eclipseVs = ["4.6","4.7","4.8","4.9","4.10","4.11","4.12"];
 const javaVs = ["1.5", "1.6", "1.7", "1.8", "1.9", "1.10"];
 const categoryIds = [];
-for (var i=0;i<20;i++) {
+for (var i=0;i<200;i++) {
   categoryIds.push(uuid.v4());
 }
 const marketIds = [];
-for (i=0;i<5;i++) {
+for (var i=0;i<5;i++) {
   marketIds.push(uuid.v4());
 }
 
@@ -147,7 +147,8 @@ function generateJSON(id) {
   		}
   	],
   	"versions": solutions,
-  	"category_ids": splice(categoryIds).splice(0,Math.ceil(Math.random()*5)+1)
+	"market_ids": splice(marketIds).splice(0,Math.ceil(Math.random()*2)),
+  	"category_ids": splice(categoryIds).splice(0,Math.ceil(Math.random()*5))
   };
 }
 
@@ -163,8 +164,7 @@ function generateMarketJSON(id) {
   return {
     "id": id,
     "name": randomWords({exactly:1, wordsPerString:Math.ceil(Math.random()*4)})[0],
-    "url": "https://www.eclipse.org",
-    "category_ids": splice(categoryIds).splice(0,Math.ceil(Math.random()*5)+1)
+    "url": "https://www.eclipse.org"
   };
 }