diff --git a/docs/Authentication/config.mdx b/docs/Authentication/config.mdx
index 7fb944d302..b0842e2f56 100644
--- a/docs/Authentication/config.mdx
+++ b/docs/Authentication/config.mdx
@@ -34,6 +34,10 @@ Technically, both of these options will work for third-party integrations but th
To enable API keys on a collection, set the `useAPIKey` auth option to `true`. From there, a new interface will appear in the Admin panel for each document within the collection that allows you to generate an API key for each user in the Collection.
+
+ User API keys are encrypted within the database, meaning that if your database is compromised, your API keys will not be.
+
+
##### Authenticating via API Key
To utilize your API key while interacting with the REST or GraphQL API, add the `Authorization` header.
diff --git a/docs/Authentication/overview.mdx b/docs/Authentication/overview.mdx
index b2f41fc807..604ddd9763 100644
--- a/docs/Authentication/overview.mdx
+++ b/docs/Authentication/overview.mdx
@@ -107,7 +107,7 @@ For more about how to automatically include cookies in requests from your app to
### CSRF Protection
-CSRF (cross-site request forgery) attacks are common and dangerous. By using an HTTP-only cookie, Payload removes any XSS vulnerabilities, however, CSRF attacks can still be possible.
+CSRF (cross-site request forgery) attacks are common and dangerous. By using an HTTP-only cookie, Payload removes many XSS vulnerabilities, however, CSRF attacks can still be possible.
For example, let's say you have a very popular app running at coolsite.com. This app allows users to manage finances and send / receive money. As Payload is using HTTP-only cookies, that means that browsers automatically will include cookies when sending requests to your domain - no matter what page created the request.
diff --git a/docs/Configuration/overview.mdx b/docs/Configuration/overview.mdx
index e05a3eaa2e..b82675d4e5 100644
--- a/docs/Configuration/overview.mdx
+++ b/docs/Configuration/overview.mdx
@@ -20,7 +20,7 @@ Payload is a *config-based*, code-first CMS and application framework. The Paylo
| `globals` | An array of all Globals that Payload will manage. For more on Globals and their configs, [click here](/docs/configuration/globals). |
| `admin` | Base Payload admin configuration. Specify custom components, control metadata, set the Admin user collection, and [more](/docs/admin/overview#options). |
| `localization` | Opt-in and control how Payload handles the translation of your content into multiple locales. [More](/docs/configuration/localization) |
-| `graphQL` | Manage GraphQL-specific functionality here. Define your own queries and mutations, manage query complexity limits, and [more](/docs/graphql/config). |
+| `graphQL` | Manage GraphQL-specific functionality here. Define your own queries and mutations, manage query complexity limits, and [more](/docs/graphql/overview#config). |
| `cookiePrefix` | A string that will be prefixed to all cookies that Payload sets. |
| `cors` | Either a whitelist array of URLS to allow CORS requests from, or a wildcard string (`'*'`) to accept incoming requests from any domain. |
| `csrf` | A whitelist array of URLs to allow Payload cookies to be accepted from as a form of CSRF protection. [More](/docs/authentication/overview#csrf-protection) |
diff --git a/docs/GraphQL/config.mdx b/docs/GraphQL/config.mdx
deleted file mode 100644
index c904c8f454..0000000000
--- a/docs/GraphQL/config.mdx
+++ /dev/null
@@ -1,11 +0,0 @@
----
-title: GraphQL Overview
-label: Overview
-order: 10
----
-
-Go over GraphQL configuration options.
-
-- Naming conventions
-- List of all queries and mutations w/ examples
-- Context
diff --git a/docs/GraphQL/extending.mdx b/docs/GraphQL/extending.mdx
index 3932cf6641..9332b684d8 100644
--- a/docs/GraphQL/extending.mdx
+++ b/docs/GraphQL/extending.mdx
@@ -4,4 +4,65 @@ label: Custom Queries and Mutations
order: 20
---
-Talk about how to add your own queries and mutations.
+You can add your own GraphQL queries and mutations to Payload, making use of all the types that Payload has defined for you.
+
+To do so, add your queries and mutations to the main Payload config as follows:
+
+| Config Path | Description |
+| -------------------- | -------------|
+| `graphQL.queries` | Function that returns an object containing keys to custom GraphQL queries |
+| `graphQL.mutations` | Function that returns an object containing keys to custom GraphQL mutations |
+
+The above properties each receive a function that is defined with the following arguments:
+
+**`GraphQL`**
+
+This is Payload's GraphQL dependency. You should not install your own copy of GraphQL as a dependency due to underlying restrictions based on how GraphQL works. Instead, you can use the Payload-provided copy via this argument.
+
+**`payload`**
+
+This is a copy of the currently running Payload instance, which provides you with existing GraphQL types for all of your Collections and Globals - among other things.
+
+##### Return value
+
+Both `graphQL.queries` and `graphQL.mutations` functions should return an object with properties equal to your newly written GraphQL queries and mutations.
+
+### Example
+
+`payload.config.js`:
+
+```js
+import { buildConfig } from 'payload/config';
+import myCustomQueryResolver from './graphQL/resolvers/myCustomQueryResolver';
+
+export default buildConfig({
+ serverURL: 'http://localhost:3000',
+ graphQL: {
+ // highlight-start
+ queries: (GraphQL, payload) => {
+ return {
+ MyCustomQuery: {
+ type: new GraphQL.GraphQLObjectType({
+ name: 'MyCustomQuery',
+ fields: {
+ text: {
+ type: GraphQL.GraphQLString,
+ },
+ someNumberField: {
+ type: GraphQL.GraphQLFloat,
+ },
+ },
+ args: {
+ argNameHere: {
+ type: new GraphQL.GraphQLNonNull(GraphQLString),
+ }
+ },
+ resolve: myCustomQueryResolver,
+ })
+ }
+ }
+ }
+ // highlight-end
+ }
+})
+```
diff --git a/docs/GraphQL/overview.mdx b/docs/GraphQL/overview.mdx
new file mode 100644
index 0000000000..5be288520b
--- /dev/null
+++ b/docs/GraphQL/overview.mdx
@@ -0,0 +1,93 @@
+---
+title: GraphQL Overview
+label: Overview
+order: 10
+---
+
+In addition to its REST and Local APIs, Payload ships with a fully featured and extensible GraphQL API.
+
+By default, the GraphQL API is exposed via `/api/graphql`, but you can customize this URL via specifying your `routes` within the main Payload config.
+
+The labels you provide for your Collections and Globals are used to format the GraphQL types that are created to correspond to your config. Special characters and spaces are removed.
+
+### Collections
+
+Everything that can be done to a Collection via the REST or Local API can be done with GraphQL (outside of uploading files, which is REST-only). If you have a collection as follows:
+
+```js
+const Post = {
+ slug: 'public-users',
+ auth: true, // Auth is enabled
+ labels: {
+ singular: 'Public User',
+ plural: 'Public Users',
+ },
+ fields: [
+ ...
+ ],
+}
+```
+
+**Payload will automatically open up the following queries:**
+
+| Query Name | Operation |
+| ---------------------- | -------------|
+| **`PublicUser`** | `findByID` |
+| **`PublicUsers`** | `find` |
+| **`mePublicUser`** | `me` auth operation |
+
+**And the following mutations:**
+
+| Query Name | Operation |
+| ------------------------------ | -------------|
+| **`createPublicUser`** | `create` |
+| **`updatePublicUser`** | `update` |
+| **`deletePublicUser`** | `delete` |
+| **`forgotPasswordPublicUser`** | `forgotPassword` auth operation |
+| **`resetPasswordPublicUser`** | `resetPassword` auth operation |
+| **`unlockPublicUser`** | `unlock` auth operation |
+| **`verifyPublicUser`** | `verify` auth operation |
+| **`loginPublicUser`** | `login` auth operation |
+| **`logoutPublicUser`** | `logout` auth operation |
+| **`refreshTokenPublicUser`** | `refresh` auth operation |
+
+### Globals
+
+Globals are also fully supported. For example:
+
+```js
+const Header = {
+ slug: 'header',
+ label: 'Header',
+ fields: [
+ ...
+ ],
+}
+```
+
+**Payload will open the following query:**
+
+| Query Name | Operation |
+| ---------------------- | -------------|
+| **`Header`** | `findOne` |
+
+**And the following mutation:**
+
+| Query Name | Operation |
+| ---------------------- | -------------|
+| **`updateHeader`** | `update` |
+
+### GraphQL Playground
+
+GraphQL Playground is enabled by default for development purposes, but disabled in production. You can enable it in production by passing `graphQL.disablePlaygroundInProduction` a `false` setting in the main Payload config.
+
+You can even log in using the `login` mutation to use the Playground as an authenticated user.
+
+
+ Tip:
+ To see more regarding how the above queries and mutations are used, visit your GraphQL playground (by default at (http://localhost:3000/api/graphql-playground) while your server is running. There, you can use the "Schema" and "Docs" buttons on the right to see a ton of detail about how GraphQL operates within Payload.
+
+
+### Query complexity limits
+
+Payload comes with a built-in query complexity limiter to prevent bad people from trying to slow down your server by running massive queries. To learn more, [click here](/docs/production/security#graphql-complexity).
diff --git a/docs/GraphQL/playground.mdx b/docs/GraphQL/playground.mdx
deleted file mode 100644
index b22d03bde4..0000000000
--- a/docs/GraphQL/playground.mdx
+++ /dev/null
@@ -1,11 +0,0 @@
----
-title: Using the GraphQL Playground
-label: Playground
-order: 30
----
-
-Talk about the GraphQL Playground
-
-Talk about how to enable / disable it in production
-
-Talk about how to log in
diff --git a/docs/Guides/blog-api.mdx b/docs/Guides/blog-api.mdx
deleted file mode 100644
index 1155e7e024..0000000000
--- a/docs/Guides/blog-api.mdx
+++ /dev/null
@@ -1,253 +0,0 @@
----
-title: Blog API From Scratch
-label: Blog API
-order: 20
----
-
-In this guide, we will be creating a Blog API from scratch.
-
-Prerequisites:
-
-- Node
-- npm or yarn
-- MongoDB server
-
-## Initial Setup
-
-Let's get started by creating a project in a blank directory
-
-```sh
-yarn init -y
-```
-
-We then will install Payload and Express
-
-```sh
-yarn add payload express
-```
-
-### Server
-
-Create a file named `server.js` in the root folder with the following contents
-
-```js
-const express = require('express');
-const app = express();
-
-app.listen(3000, async () => {
- console.log('Express is now listening for incoming connections on port 3000.');
-});
-```
-
-This is a basic Express application. Now let's pull in Payload and fill in the `init` parameters. `secret` and `mongoURL` can be changed as needed.
-
-```js
-const express = require('express');
-const payload = require('payload'); // highlight-line
-
-const app = express();
-
-// highlight-start
-payload.init({
- secret: 'SECRET_KEY',
- mongoURL: 'mongodb://localhost/payload-blog',
- express: app,
-})
-// highlight-end
-
-app.listen(3000, async () => {
- console.log('Express is now listening for incoming connections on port 3000.');
-});
-```
-
-### Payload Configuration
-
-Next, let's get some basics put into our `payload.config.js` file
-
-```js
-module.exports = {
- serverURL: 'http://localhost:3000',
- collections: [],
-}
-```
-
-#### Collections
-
-We'll need a few different collections for our blog:
-
-- Posts
-- Categories
-- Tags
-
-The Posts will have the ability to have a single category and multiple tags
-
-Let's create a `collections` directory and create a `.js` file for each
-
-```js:title=Posts.js
-module.exports = {
- slug: 'posts',
- labels: {
- singular: 'Post',
- plural: 'Posts',
- },
- admin: {
- useAsTitle: 'title',
- },
- fields: [
- {
- name: 'title',
- label: 'Title',
- type: 'text'
- },
- {
- name: 'content',
- label: 'Content',
- type: 'textarea',
- },
- {
- name: 'category',
- label: 'Category',
- type: 'relationship',
- relationTo: 'categories', // Categories Slug // highlight-line
- },
- {
- name: 'tags',
- label: 'Tags',
- type: 'relationship',
- relationTo: 'tags', // Tags Slug // highlight-line
- hasMany: true, // Allow multiple tags per post
- },
- ]
-};
-```
-
-```js:title=Categories.js
-module.exports = {
- slug: 'categories',
- labels: {
- singular: 'Category',
- plural: 'Categories',
- },
- admin: {
- useAsTitle: 'name',
- },
- fields: [
- {
- name: 'name',
- label: 'Name',
- type: 'text',
- },
- ],
-};
-```
-
-```js:title=Tags.js
-module.exports = {
- slug: 'tags',
- labels: {
- singular: 'Tag',
- plural: 'Tags',
- },
- admin: {
- useAsTitle: 'name',
- },
- fields: [
- {
- name: 'name',
- label: 'Name',
- type: 'text',
- },
- ],
-};
-```
-
-Once these are created, we can pull them into the `collections` array in our `payload.config.js`
-
-```js:title=payload.config.js
-const Admins = require('./collections/Admins');
-// highlight-start
-const Posts = require('./collections/Posts');
-const Categories = require('./collections/Categories');
-const Tags = require('./collections/Tags');
-// highlight-end
-
-module.exports = {
- serverURL: 'http://localhost:3000',
- admin: {
- user: 'admins',
- },
- collections: [
- Admins,
- // highlight-start
- Posts,
- Categories,
- Tags
- // highlight-end
- ],
-}
-```
-
-### Create Content
-
-**TODO: Should a guide like this use the Admin interface or `curl`?**
-
-Navigate to the admin interface at `http://localhost:3000/admin` and create your first user.
-
-Then create some Categories, Tags, and a Post.
-
-Let's use `curl` to quickly create some data. The following commands will create 2 tags and 1 category.
-
-```sh
-curl -H "Content-Type: application/json" -X POST -d '{"name": "JavaScript"}' http://localhost:3000/api/tags
-curl -H "Content-Type: application/json" -X POST -d '{"name": "TypeScript"}' http://localhost:3000/api/tags
-curl -H "Content-Type: application/json" -X POST -d '{"name": "Code"}' http://localhost:3000/api/categories
-```
-
-We'll then make a request to create a Post, this will inclue a relationship to the category and tags created previously.
-
-TODO: Somehow retrieve tag and category IDs then include them in a request to create a Post
-
-Once complete, navigate to `http://localhost:3000/api/posts`, and you should see something similar to the following:
-
-```js
-{
- "docs": [
- {
- "tags": [
- {
- "name": "TypeScript",
- "createdAt": "2020-11-13T11:48:05.993Z",
- "updatedAt": "2020-11-13T11:48:05.993Z",
- "id": "5fae72758315da656fb3a8f0"
- },
- {
- "name": "JavaScript",
- "createdAt": "2020-11-13T11:48:00.064Z",
- "updatedAt": "2020-11-13T11:48:00.064Z",
- "id": "5fae72708315da656fb3a8ef"
- }
- ],
- "title": "My Title",
- "content": "This is some content",
- "category": {
- "name": "Code",
- "createdAt": "2020-11-13T11:48:10.351Z",
- "updatedAt": "2020-11-13T11:48:36.358Z",
- "id": "5fae727a8315da656fb3a8f1"
- },
- "createdAt": "2020-11-13T11:50:14.312Z",
- "updatedAt": "2020-11-13T11:50:14.312Z",
- "id": "5fae72f68e314b67609e05d1"
- }
- ],
- "totalDocs": 1,
- "limit": 10,
- "totalPages": 1,
- "page": 1,
- "pagingCounter": 1,
- "hasPrevPage": false,
- "hasNextPage": false,
- "prevPage": null,
- "nextPage": null
-}
-```
diff --git a/docs/Guides/configure-email.mdx b/docs/Guides/configure-email.mdx
deleted file mode 100644
index 188ccb0cec..0000000000
--- a/docs/Guides/configure-email.mdx
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Configure Email
-label: Configure Email
-order: 30
----
-
-TODO: Configure SendGrid or similar
diff --git a/docs/Production/security.mdx b/docs/Production/security.mdx
index c68063bb10..ecc338926c 100644
--- a/docs/Production/security.mdx
+++ b/docs/Production/security.mdx
@@ -10,3 +10,7 @@ order: 20
- Max Depth
- CSRF
- CORS
+
+### Limiting GraphQL Complexity
+
+^^ Keep this header here