diff --git a/_config.yml b/_config.yml
index 3e3493d..77b6928 100644
--- a/_config.yml
+++ b/_config.yml
@@ -37,6 +37,7 @@ sidebar_categories:
     - filtering
     - mutations
     - fragments
+    - aggregation
     - server-queries
     - connecting-remotely
   Features:
diff --git a/package.json b/package.json
index b7f2470..ac253d1 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
   "version": "0.0.0",
   "private": true,
   "hexo": {
-    "version": "3.8.0"
+    "version": "3.9.0"
   },
   "dependencies": {
     "global": "^4.3.2",
@@ -30,4 +30,4 @@
     "deploy": "hexo deploy --generate",
     "develop-theme": "nodemon -x 'rm db.json; hexo serve' -w assets/ -w code/ -w source/ -w themes/ -w scripts/"
   }
-}
+}
\ No newline at end of file
diff --git a/source/aggregation.md b/source/aggregation.md
new file mode 100644
index 0000000..bfddee9
--- /dev/null
+++ b/source/aggregation.md
@@ -0,0 +1,183 @@
+---
+title: Custom Queries & Aggregations
+---
+
+#### ⭐️ New in Vulcan 1.17.0
+
+Vulcan makes it really easy to query and mutate collections using GraphQL. But sometimes you want to query data on the server with a schema that does not match a database collection - for example an aggregation or an array of objects. `registerCustomQuery()` allows you to add custom GraphQL types from a schema object and resolvers to query the data.
+
+## Registering a Custom Query
+
+You can register a custom query using `registerCustomQuery()` on the server:
+
+```js
+// on the server
+registerCustomQuery({
+  typeName: 'Agent',
+  description: 'Query resolved from an in-memory array of Agents on the server',
+  resolver,
+  schema: agentSchema,
+  filterable,
+});
+```
+
+This function generates and configures the following:
+
+ * The GraphQL type
+ * The filter input object
+ * The sort input object
+ * The multi input object
+ * The multi output object
+ * The query
+ * The resolver
+ * The default fragment (see )
+
+
+The function takes the following arguments:
+
+ * `typeName`: the name of the GraphQL type that will be generated - the same as when [creating a collection](/schemas.html#Creating-Collections)
+ * `description`: a description to document the schema
+ * `resolver`: the query resolver function
+ * `schema`: (optional) a JavaScript object containing a list of fields defining the [schema](/schema-properties.html) of the new type
+ * `graphQLType`: (optional) instead of a `schema`, you can provide a `graphQLType` as a template literal
+ * `filterable`: an array of filterable fields, each one an object with a `name` and GraphQL `type`
+
+## Defining the Resolver
+
+The resolver is an async function with the usual [resolver arguments](https://www.apollographql.com/docs/apollo-server/data/resolvers/#resolver-arguments). It should return an object with the `results` and `totalCount`.
+
+In this example `agents` exists as an array of agent objects defined on the server. We will later query it from the client using `withMulti`.
+
+```js
+// on the server
+import { registerCustomQuery } from 'meteor/vulcan:core';
+import { agents } from './agents';
+import agentSchema from './schema';
+import _map from 'lodash/map';
+import _filter from 'lodash/filter';
+
+const resolver = async function (root, { input }, context, info) {
+  const { search } = input;
+
+  let agents = _map(agents, (agent, key) => {
+    return {
+      _id: agent.id,
+      title: agent.title,
+      description: agent.description,
+    };
+  });
+
+  agents = _filter(agents, agent => agent.title.includes(search) || agent.description.includes(search));
+
+  return { results: agents, totalCount: agents.length };
+};
+```
+
+The resolver should enforce permissions using `context.currentUser`.
+
+## Providing a Schema
+
+You specify the schema the same way as you would for a [collection](/schemas.html#Creating-Collections). To make the process even simpler, we have added support for [SimpleSchema Shorthand Definitions](https://github.com/aldeed/simpl-schema#shorthand-definitions) to `createSchema()`.
+The new `defaultCanRead` parameter assigns the given permissions to all fields where `canRead` is `undefined`.
+
+```js
+// on the client & server
+import { createSchema } from 'meteor/vulcan:core';
+
+const agentSchema = createSchema({
+  _id: String,
+  title: String,
+  description: String,
+}, undefined, undefined, ['members']);
+
+export default agentSchema;
+```
+
+As an alternative, you can pass a `graphQLType` as a template literal instead of a `schema`.
+
+## Specifying Filterable Fields
+
+```js
+// on the server
+const filterable = [{
+  name: 'view',
+  type: 'String',
+}];
+```
+
+## Registering the Default Fragment
+
+You can reuse the schema to register the default fragment (listing all readable fields) with just a couple of lines of code:
+
+```js
+// on the client & server
+registerCustomDefaultFragment({
+  typeName: 'Agent',
+  schema: agentSchema,
+  fragmentName: 'AgentsDefaultFragment',
+});
+```
+
+## Running the Query
+
+On the client you can get the query results as usual with `useMulti2` or `withMulti2`. The only difference is that you pass `typeName` instead of `collection`.
+```js
+const { results, loading, error, networkStatus, refetch } = useMulti2({ 
+  typeName: 'Agent', 
+  fragmentName: 'AgentsDefaultFragment',
+  input, 
+  pollInterval, 
+  queryOptions,
+});
+```
+
+## Aggregation
+
+You can use this same pattern to register an aggregate query. Inside the resolver use `aggregate()` on the collection that you are aggregating.
+
+```js
+const resolver = async function (root, { input }, context) {
+  const { search } = input;
+  const { currentUser } = context;
+  
+  const pipeline = [
+    { // Stage 1
+      $match: {
+        activatedAt: { $exists: true },
+      },
+    },
+    { // Stage 2
+      $lookup: {
+        from: 'supplierstubs',
+        localField: 'supplierCode',
+        foreignField: 'supplierId',
+        as: 'supplierstub',
+      },
+    },
+      
+    // Ommitted stages 3 to 6 for brevity
+    
+    { // Stage 7
+      $sort: {
+        supplierCode: 1,
+      },
+    },
+  
+  ];
+  
+  if (search) {
+    pipeline.splice(1, 0, {
+      $match: {
+        $or: [
+          { name: { $regex: search, $options: 'i' } },
+          { supplierCode: { $regex: search, $options: 'i' } },
+        ],
+      },
+    });
+  }
+  
+  const docs = await Suppliers.aggregate(pipeline);
+  
+  return { results: restrictedDocs, totalCount: restrictedDocs.length };
+};
+```
diff --git a/source/reactive-state.md b/source/reactive-state.md
index 944575f..ca1e664 100644
--- a/source/reactive-state.md
+++ b/source/reactive-state.md
@@ -2,6 +2,8 @@
 title: Reactive State
 ---
 
+#### ⭐️ New in Vulcan 1.17.0
+
 ## Introduction
 
 Vulcan's Reactive State feature offers simple global state management on the client side based on [Apollo Client reactive variables](https://www.apollographql.com/docs/react/local-state/reactive-variables/). You can think of Reactive State as a wrapper around Apollo reactive variables with some added features: