diff --git a/docs/scripting/db/cursor/array.mdx b/docs/scripting/db/cursor/array.mdx
new file mode 100644
index 00000000..af31bb8b
--- /dev/null
+++ b/docs/scripting/db/cursor/array.mdx
@@ -0,0 +1,32 @@
+---
+title: array(_and_close)
+alias: array_and_close
+---
+
+Retrieve all documents of the cursor, and close it.
+
+## Syntax
+
+```
+let cursor = #db.f(query, projection)
+cursor.array()
+```
+
+or
+
+```
+let cursor = #db.f(query, projection)
+cursor.array_and_close()
+```
+
+### Parameters
+
+No parameters.
+
+### Return
+
+Returns all documents from the cursor, as an array of objects.
+
+## Cursor State
+
+This function closes the cursor. If this is undesirable, try [array_and_keep_open](./array_and_keep_open).
diff --git a/docs/scripting/db/cursor/array_and_keep_open.mdx b/docs/scripting/db/cursor/array_and_keep_open.mdx
new file mode 100644
index 00000000..f2f79282
--- /dev/null
+++ b/docs/scripting/db/cursor/array_and_keep_open.mdx
@@ -0,0 +1,22 @@
+Retrieve all documents of the cursor, keeping it open.
+
+## Syntax
+
+```
+let cursor = #db.f(query, projection)
+cursor.array_and_keep_open()
+```
+
+### Parameters
+
+No parameters.
+
+### Return
+
+Returns all documents from the cursor, as an array of objects.
+
+## Cursor State
+
+This function keeps the cursor open.
+
+Try [array](./array) for a variant of this function that automatically closes the cursor.
diff --git a/docs/scripting/db/cursor/close.mdx b/docs/scripting/db/cursor/close.mdx
new file mode 100644
index 00000000..88db6326
--- /dev/null
+++ b/docs/scripting/db/cursor/close.mdx
@@ -0,0 +1,34 @@
+Close the cursor.
+
+This method is ((%Fidempotent%)):
+calling it multiple times results in identical behavior to calling it once.
+
+## Syntax
+
+```
+let cursor = #db.f(query, projection)
+cursor.close()
+```
+
+### Parameters
+
+No parameters.
+
+### Return
+
+Always returns ((%Vnull%)).
+
+## Cursor State
+
+This function closes the cursor.
+
+## Example
+
+```js
+function(context, args) {
+ let cursor = #db.f({ type: "my_data" });
+
+ // removing the following line causes an error:
+ cursor.close();
+}
+```
diff --git a/docs/scripting/db/cursor/count.mdx b/docs/scripting/db/cursor/count.mdx
new file mode 100644
index 00000000..b8b7d02b
--- /dev/null
+++ b/docs/scripting/db/cursor/count.mdx
@@ -0,0 +1,34 @@
+---
+title: count(_and_close)
+alias: count_and_close
+---
+
+Retrieve the total number of documents of the cursor, and close it.
+
+This method ignores `skip` and `limit` settings.
+
+## Syntax
+
+```
+let cursor = #db.f(query, projection)
+cursor.count()
+```
+
+or
+
+```
+let cursor = #db.f(query, projection)
+cursor.count_and_close()
+```
+
+### Parameters
+
+No parameters.
+
+### Return
+
+The total number of documents of this cursor, as a number.
+
+## Cursor State
+
+This function closes the cursor. If this is undesirable, try [count_and_keep_open](./count_and_keep_open).
diff --git a/docs/scripting/db/cursor/count_and_keep_open.mdx b/docs/scripting/db/cursor/count_and_keep_open.mdx
new file mode 100644
index 00000000..ece5abe2
--- /dev/null
+++ b/docs/scripting/db/cursor/count_and_keep_open.mdx
@@ -0,0 +1,24 @@
+Retrieve the total number of documents of the cursor, keeping it open.
+
+This method ignores `skip` and `limit` settings.
+
+## Syntax
+
+```
+let cursor = #db.f(query, projection)
+cursor.count_and_keep_open()
+```
+
+### Parameters
+
+No parameters.
+
+### Return
+
+The total number of documents of this cursor, as a number.
+
+## Cursor State
+
+This function keeps the cursor open.
+
+Try [count](./count) for a variant of this function that automatically closes the cursor.
diff --git a/docs/scripting/db/cursor/distinct.mdx b/docs/scripting/db/cursor/distinct.mdx
new file mode 100644
index 00000000..3d9acf60
--- /dev/null
+++ b/docs/scripting/db/cursor/distinct.mdx
@@ -0,0 +1,36 @@
+---
+title: distinct(_and_close)
+alias: distinct_and_close
+---
+
+Retrieve the unique values of a given field for all documents of the cursor, and close it.
+
+This method ignores `skip` and `limit` settings.
+
+## Syntax
+
+```
+let cursor = #db.f(query, projection)
+cursor.distinct(field)
+```
+
+or
+
+```
+let cursor = #db.f(query, projection)
+cursor.distinct_and_close(field)
+```
+
+### Parameters
+
+#### field
+
+A string naming the field to pull unique values from.
+
+### Return
+
+The unique values of the given field from all documents of the cursor, as an array.
+
+## Cursor State
+
+This function closes the cursor. If this is undesirable, try [distinct_and_keep_open](./distinct_and_keep_open).
diff --git a/docs/scripting/db/cursor/distinct_and_keep_open.mdx b/docs/scripting/db/cursor/distinct_and_keep_open.mdx
new file mode 100644
index 00000000..613a5ffc
--- /dev/null
+++ b/docs/scripting/db/cursor/distinct_and_keep_open.mdx
@@ -0,0 +1,26 @@
+Retrieve the unique values of a given field for all documents of the cursor, keeping it open.
+
+This method ignores `skip` and `limit` settings.
+
+## Syntax
+
+```
+let cursor = #db.f(query, projection)
+cursor.distinct_and_keep_open(field)
+```
+
+### Parameters
+
+#### field
+
+A string naming the field to pull unique values from.
+
+### Return
+
+The unique values of the given field from all documents of the cursor, as an array.
+
+## Cursor State
+
+This function keeps the cursor open.
+
+Try [distinct](./distinct) for a variant of this function that automatically closes the cursor.
diff --git a/docs/scripting/db/cursor/each.mdx b/docs/scripting/db/cursor/each.mdx
new file mode 100644
index 00000000..0f04960a
--- /dev/null
+++ b/docs/scripting/db/cursor/each.mdx
@@ -0,0 +1,34 @@
+---
+title: each(_and_close)
+alias: each_and_close
+---
+
+Call a function on all documents of the cursor, and close it.
+
+## Syntax
+
+```
+let cursor = #db.f(query, projection)
+cursor.each(fn)
+```
+
+or
+
+```
+let cursor = #db.f(query, projection)
+cursor.each_and_close(fn)
+```
+
+### Parameters
+
+#### fn
+
+The function to call. This function is called once per document, with the document passed as the only argument.
+
+### Return
+
+Always returns ((%Vundefined%)).
+
+## Cursor state
+
+This function closes the cursor. If this is undesirable, try [each_and_keep_open](./each_and_keep_open).
diff --git a/docs/scripting/db/cursor/each_and_keep_open.mdx b/docs/scripting/db/cursor/each_and_keep_open.mdx
new file mode 100644
index 00000000..5783726e
--- /dev/null
+++ b/docs/scripting/db/cursor/each_and_keep_open.mdx
@@ -0,0 +1,24 @@
+Call a function on all documents of the cursor, keeping it open.
+
+## Syntax
+
+```
+let cursor = #db.f(query, projection)
+cursor.each_and_keep_open(fn)
+```
+
+### Parameters
+
+#### fn
+
+The function to call. This function is called once per document, with the document passed as the only argument.
+
+### Return
+
+Always returns ((%Vundefined%)).
+
+## Cursor state
+
+This function keeps the cursor open.
+
+Try [each](./each) for a variant of this function that automatically closes the cursor.
diff --git a/docs/scripting/db/cursor/first.mdx b/docs/scripting/db/cursor/first.mdx
new file mode 100644
index 00000000..9f4f3f0a
--- /dev/null
+++ b/docs/scripting/db/cursor/first.mdx
@@ -0,0 +1,56 @@
+---
+title: first(_and_close)
+alias: first_and_close
+---
+
+Retrieve the first document of the cursor, and close it.
+
+## Syntax
+
+```
+let cursor = #db.f(query, projection)
+cursor.first()
+```
+
+or
+
+```
+let cursor = #db.f(query, projection)
+cursor.first_and_close()
+```
+
+### Parameters
+
+No parameters.
+
+### Return
+
+Returns the first document from the cursor as an object.
+If the cursor is empty, returns ((%Vnull%)).
+
+## Cursor State
+
+This function closes the cursor. If this is undesirable, try [first_and_keep_open](./first_and_keep_open).
+
+## Example
+
+For the following examples, assume the database contains the following documents, inserted in that order:
+
+```
+{ type: "my_data", my_key: "foo", order: 2 }
+{ type: "my_data", my_key: "bar", order: 1 }
+```
+
+```js
+function(context, args) {
+ let data = #db.f({ type: "my_data" }).first();
+ return data; // { type: "my_data", my_key: "foo", order: 2 }
+}
+```
+
+```js
+function(context, args) {
+ let data = #db.f({ type: "my_data" }).sort({ order: 1 }).first();
+ return data; // { type: "my_data", my_key: "bar", order: 1 }
+}
+```
diff --git a/docs/scripting/db/cursor/first_and_keep_open.mdx b/docs/scripting/db/cursor/first_and_keep_open.mdx
new file mode 100644
index 00000000..21ea3af1
--- /dev/null
+++ b/docs/scripting/db/cursor/first_and_keep_open.mdx
@@ -0,0 +1,42 @@
+Retrieve the first document of the cursor, keeping it open.
+
+## Syntax
+
+```
+let cursor = #db.f(query, projection)
+cursor.first_and_keep_open()
+// continue using `cursor`...
+```
+
+### Parameters
+
+No parameters.
+
+### Return
+
+Returns the first document from the cursor as an object.
+If the cursor is empty, returns ((%Vnull%)).
+
+## Cursor State
+
+This function keeps the cursor open.
+
+Try [first](./first) for a variant of this function that automatically closes the cursor.
+
+## Example
+
+For the following example, assume the database contains the following documents, inserted in that order:
+
+```
+{ type: "my_data", my_key: "foo", order: 2 }
+{ type: "my_data", my_key: "bar", order: 1 }
+```
+
+```js
+function(context, args) {
+ let cursor = #db.f({ type: "my_data" });
+ let a = cursor.skip(1).first_and_keep_open();
+ let b = cursor.skip(0).first_and_close();
+ return [ a.my_key, b.my_key ]; // [ "bar", "foo" ]
+}
+```
diff --git a/docs/scripting/db/cursor/index.mdx b/docs/scripting/db/cursor/index.mdx
new file mode 100644
index 00000000..fe6acecc
--- /dev/null
+++ b/docs/scripting/db/cursor/index.mdx
@@ -0,0 +1,55 @@
+---
+title: Cursors
+sidebar_collapsible: true
+sidebar_collapsed: true
+---
+
+Database cursors are special objects returned from [#db.f(..)](../db.f).
+
+## Usage
+
+Cursors are exclusively returned from #db.f(..):
+
+```
+let cursor = #db.f(query, projection)
+```
+
+A cursor _MUST_ be closed before the script run finishes, otherwise the following error occurs:
+
+> `:::TRUST COMMUNICATION::: 1 db cursors were left open at end of script run`
+
+Cursors can be closed via the [close](./close) method, or by using a terminating method such as [`first`](./first).
+
+After a cursor is closed, it can no longer be used. Subsequent attempts to use the cursor result in the following error:
+
+> `:::TRUST COMMUNICATION::: cursor was invalid. this can happen if it was used after being closed`
+
+### Cursor Handles
+
+((%FTODO: Write something that people who have never used a file descriptor in their life can understand.%))
+
+
+Technical details
+
+A cursor is a wrapper object around a _"cursor handle"_.
+`#db.f` always returns a cursor with a new, unique "cursor handle".
+
+Multiple cursor objects can refer to the same "cursor handle".
+See [skip](./skip) for a method that causes this to happen.
+
+When _any_ cursor with a given "cursor handle" is closed, _all_ cursors for that "cursor handle" become invalid.
+(You only need to close one cursor for each "cursor handle" to avoid the 'cursors were left open' error.)
+
+Cursor objects that use the same "cursor handle" are nevertheless separate objects, and do not compare equal.
+
+Note that your code will never interact with a "cursor handle" directly, only with cursor objects.
+
+
+
+## Methods
+
+The following is an auto-generated list of cursor methods:
+
+import DocCardList from "@theme/DocCardList";
+
+
diff --git a/docs/scripting/db/cursor/limit.mdx b/docs/scripting/db/cursor/limit.mdx
new file mode 100644
index 00000000..d52c36fa
--- /dev/null
+++ b/docs/scripting/db/cursor/limit.mdx
@@ -0,0 +1,25 @@
+Set the limit of the cursor.
+
+## Syntax
+
+```
+let cursor = #db.f(query, projection)
+cursor.limit(count)
+```
+
+### Parameters
+
+#### count
+
+The maximum number of results to return.
+If this parameter is `0`, the limit is removed.
+
+### Return
+
+Returns a new cursor, with the same handle as the input cursor.
+
+This can be used to chain into other methods.
+
+## Cursor State
+
+This function keeps the cursor open.
diff --git a/docs/scripting/db/cursor/skip.mdx b/docs/scripting/db/cursor/skip.mdx
new file mode 100644
index 00000000..940cb716
--- /dev/null
+++ b/docs/scripting/db/cursor/skip.mdx
@@ -0,0 +1,25 @@
+Set the skip count of the cursor.
+
+## Syntax
+
+```
+let cursor = #db.f(query, projection)
+cursor.skip(skip)
+```
+
+### Parameters
+
+#### skip
+
+The number of results to skip.
+Note that this is not cumulative; `cursor.skip(5).skip(1)` will skip 1 document, not 6.
+
+### Return
+
+Returns a new cursor, with the same handle as the input cursor.
+
+This can be used to chain into other methods.
+
+## Cursor State
+
+This function keeps the cursor open.
diff --git a/docs/scripting/db/cursor/sort.mdx b/docs/scripting/db/cursor/sort.mdx
new file mode 100644
index 00000000..ed3f65e1
--- /dev/null
+++ b/docs/scripting/db/cursor/sort.mdx
@@ -0,0 +1,27 @@
+Set the sort order of the cursor.
+
+## Syntax
+
+```
+let cursor = #db.f(query, projection)
+cursor.sort(sort)
+```
+
+### Parameters
+
+#### sort
+
+An object specifying the sort order, e.g. (( \{ n_hits: 1, n_fails: -1 \} )).
+Each key is the path of a sort key, each value specifies direction (((%V1%)) for ascending, ((%V-1%)) for descending).
+
+Note that the order of keys in the object matters.
+
+### Return
+
+Returns a new cursor, with the same handle as the input cursor.
+
+This can be used to chain into other methods.
+
+## Cursor State
+
+This function keeps the cursor open.