diff --git a/exchanges/context/CHANGELOG.md b/exchanges/context/CHANGELOG.md
new file mode 100644
index 0000000000..8c30f86a21
--- /dev/null
+++ b/exchanges/context/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changelog
+
+## v0.1.0
+
+**Initial Release**
diff --git a/exchanges/context/README.md b/exchanges/context/README.md
new file mode 100644
index 0000000000..ba8a23aa6c
--- /dev/null
+++ b/exchanges/context/README.md
@@ -0,0 +1,41 @@
+
@urql/exchange-context
+
+An exchange for setting operation context in urql
+
+`@urql/exchange-context` is an exchange for the [`urql`](https://github.com/FormidableLabs/urql) GraphQL client which can set the operation context both synchronously as well as asynchronously
+
+## Quick Start Guide
+
+First install `@urql/exchange-context` alongside `urql`:
+
+```sh
+yarn add @urql/exchange-context
+# or
+npm install --save @urql/exchange-context
+```
+
+You'll then need to add the `contextExchange`, that this package exposes, to your `urql` Client, the positioning of this exchange depends on whether you set an async setter or not. If you set an async context-setter it's best placed after all the synchronous exchanges (in front of the fetchExchange).
+
+```js
+import { createClient, dedupExchange, cacheExchange, fetchExchange } from 'urql';
+import { contextExchange } from '@urql/exchange-context';
+
+const client = createClient({
+ url: 'http://localhost:1234/graphql',
+ exchanges: [
+ dedupExchange,
+ cacheExchange,
+ contextExchange({
+ getContext: async (operation) => {
+ const token = await getToken();
+ return { ...operation.context, headers: { authorization: token } }
+ },
+ }),
+ fetchExchange,
+ ],
+});
+```
+
+## Maintenance Status
+
+**Active:** Formidable is actively working on this project, and we expect to continue for work for the foreseeable future. Bug reports, feature requests and pull requests are welcome.
diff --git a/exchanges/context/package.json b/exchanges/context/package.json
new file mode 100644
index 0000000000..31d279a4d3
--- /dev/null
+++ b/exchanges/context/package.json
@@ -0,0 +1,65 @@
+{
+ "name": "@urql/exchange-context",
+ "version": "0.1.0",
+ "description": "An exchange for setting (a)synchronous operation-context in urql",
+ "sideEffects": false,
+ "homepage": "https://formidable.com/open-source/urql/docs/",
+ "bugs": "https://github.com/FormidableLabs/urql/issues",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/FormidableLabs/urql.git",
+ "directory": "exchanges/context"
+ },
+ "keywords": [
+ "urql",
+ "exchange",
+ "context",
+ "formidablelabs",
+ "exchanges"
+ ],
+ "main": "dist/urql-exchange-context",
+ "module": "dist/urql-exchange-context.mjs",
+ "types": "dist/types/index.d.ts",
+ "source": "src/index.ts",
+ "exports": {
+ ".": {
+ "import": "./dist/urql-exchange-context.mjs",
+ "require": "./dist/urql-exchange-context.js",
+ "types": "./dist/types/index.d.ts",
+ "source": "./src/index.ts"
+ },
+ "./package.json": "./package.json"
+ },
+ "files": [
+ "LICENSE",
+ "CHANGELOG.md",
+ "README.md",
+ "dist/"
+ ],
+ "scripts": {
+ "test": "jest",
+ "clean": "rimraf dist extras",
+ "check": "tsc --noEmit",
+ "lint": "eslint --ext=js,jsx,ts,tsx .",
+ "build": "rollup -c ../../scripts/rollup/config.js",
+ "prepare": "node ../../scripts/prepare/index.js",
+ "prepublishOnly": "run-s clean build"
+ },
+ "jest": {
+ "preset": "../../scripts/jest/preset"
+ },
+ "dependencies": {
+ "@urql/core": ">=2.3.6",
+ "wonka": "^6.0.0"
+ },
+ "peerDependencies": {
+ "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
+ },
+ "devDependencies": {
+ "graphql": "^16.0.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/exchanges/context/src/context.test.ts b/exchanges/context/src/context.test.ts
new file mode 100644
index 0000000000..6ab48bc53a
--- /dev/null
+++ b/exchanges/context/src/context.test.ts
@@ -0,0 +1,117 @@
+import { pipe, map, makeSubject, publish, tap } from 'wonka';
+
+import {
+ gql,
+ createClient,
+ Operation,
+ OperationResult,
+ ExchangeIO,
+} from '@urql/core';
+
+import { contextExchange } from './context';
+
+const queryOne = gql`
+ {
+ author {
+ id
+ name
+ }
+ }
+`;
+
+const queryOneData = {
+ __typename: 'Query',
+ author: {
+ __typename: 'Author',
+ id: '123',
+ name: 'Author',
+ },
+};
+
+const dispatchDebug = jest.fn();
+let client, op, ops$, next;
+beforeEach(() => {
+ client = createClient({ url: 'http://0.0.0.0' });
+ op = client.createRequestOperation('query', {
+ key: 1,
+ query: queryOne,
+ });
+
+ ({ source: ops$, next } = makeSubject());
+});
+
+it(`calls getContext`, () => {
+ const response = jest.fn(
+ (forwardOp: Operation): OperationResult => {
+ return {
+ operation: forwardOp,
+ data: queryOneData,
+ };
+ }
+ );
+
+ const result = jest.fn();
+ const forward: ExchangeIO = ops$ => {
+ return pipe(ops$, map(response));
+ };
+
+ const headers = { hello: 'world' };
+ pipe(
+ contextExchange({
+ getContext: op => ({ ...op.context, headers }),
+ })({
+ forward,
+ client,
+ dispatchDebug,
+ })(ops$),
+ tap(result),
+ publish
+ );
+
+ next(op);
+
+ expect(response).toHaveBeenCalledTimes(1);
+ expect(response.mock.calls[0][0].context.headers).toEqual(headers);
+ expect(result).toHaveBeenCalledTimes(1);
+});
+
+it(`calls getContext async`, done => {
+ const response = jest.fn(
+ (forwardOp: Operation): OperationResult => {
+ return {
+ operation: forwardOp,
+ data: queryOneData,
+ };
+ }
+ );
+
+ const result = jest.fn();
+ const forward: ExchangeIO = ops$ => {
+ return pipe(ops$, map(response));
+ };
+
+ const headers = { hello: 'world' };
+ pipe(
+ contextExchange({
+ getContext: async op => {
+ await Promise.resolve();
+ return { ...op.context, headers };
+ },
+ })({
+ forward,
+ client,
+ dispatchDebug,
+ })(ops$),
+ tap(result),
+ publish
+ );
+
+ next(op);
+
+ setTimeout(() => {
+ expect(response).toHaveBeenCalledTimes(1);
+ expect(response.mock.calls[0][0].context.headers).toEqual(headers);
+ expect(result).toHaveBeenCalledTimes(1);
+ done();
+ }, 10);
+});
diff --git a/exchanges/context/src/context.ts b/exchanges/context/src/context.ts
new file mode 100644
index 0000000000..1adde2e2ce
--- /dev/null
+++ b/exchanges/context/src/context.ts
@@ -0,0 +1,39 @@
+import {
+ Exchange,
+ makeOperation,
+ Operation,
+ OperationContext,
+} from '@urql/core';
+import { fromPromise, fromValue, mergeMap, pipe } from 'wonka';
+
+export interface ContextExchangeArgs {
+ getContext: (
+ operation: Operation
+ ) => OperationContext | Promise;
+}
+
+export const contextExchange = ({
+ getContext,
+}: ContextExchangeArgs): Exchange => ({ forward }) => {
+ return ops$ => {
+ return pipe(
+ ops$,
+ mergeMap(operation => {
+ const result = getContext(operation);
+ const isPromise = 'then' in result;
+ if (isPromise) {
+ return fromPromise(
+ result.then((ctx: OperationContext) =>
+ makeOperation(operation.kind, operation, ctx)
+ )
+ );
+ } else {
+ return fromValue(
+ makeOperation(operation.kind, operation, result as OperationContext)
+ );
+ }
+ }),
+ forward
+ );
+ };
+};
diff --git a/exchanges/context/src/index.ts b/exchanges/context/src/index.ts
new file mode 100644
index 0000000000..d9379222b2
--- /dev/null
+++ b/exchanges/context/src/index.ts
@@ -0,0 +1 @@
+export { contextExchange, ContextExchangeArgs } from './context';
diff --git a/exchanges/context/tsconfig.json b/exchanges/context/tsconfig.json
new file mode 100644
index 0000000000..5797ce6168
--- /dev/null
+++ b/exchanges/context/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": ["src"],
+ "compilerOptions": {
+ "baseUrl": "./",
+ "paths": {
+ "urql": ["../../node_modules/urql/src"],
+ "*-urql": ["../../node_modules/*-urql/src"],
+ "@urql/core/*": ["../../node_modules/@urql/core/src/*"],
+ "@urql/*": ["../../node_modules/@urql/*/src"]
+ }
+ }
+}