diff --git a/docs/api/v4/actions.md b/docs/api/v4/actions.md
index 9a593b5ae03473609d77cfc6d8c19f5766ff5850..84f3532832ce01e5afc7af71872852e7bb08094b 100644
--- a/docs/api/v4/actions.md
+++ b/docs/api/v4/actions.md
@@ -31,6 +31,10 @@ along with some _metadata_ actions to retrieve information about the structure o
     Search for records based on query parameters. For most entities, `get` includes advanced features
     such as [joins](explicit-joins.md). 
 
+* **`export`**
+
+    Available only for [Managed Entity](managed.md) types, outputs code which can be used to package and distribute a record in an extension. 
+
 ## Write Actions
 
 * **`create`**
@@ -39,11 +43,13 @@ along with some _metadata_ actions to retrieve information about the structure o
 
 * **`update`**
 
-    Update one or more existing records with new values.
+    Update one or more existing records with new values. If an `id` is not supplied, then query params are required to search for records to update.
 
 * **`save`**
 
-    Save an array of records. For each one, the presence of an `id` determines whether an existing record will be updated.
+    Given an array of records, either `create` or `update` each one. By default the presence of an `id` determines whether an existing record will be updated.
+    The `match` param gives finer control over this, and lets you specify one or more fields; if a single existing record matches *all* of them, it will be updated instead of a new record created. For example you can `save` an array of contacts and specify
+    `['external_id']` as the `match` param, which will update contacts with matching `external_id`s.
 
 * **`delete`**
 
@@ -51,11 +57,15 @@ along with some _metadata_ actions to retrieve information about the structure o
 
 * **`replace`**
 
-    Replace an existing set of records with a new or modified set of records. For example, replace a group of "Phone" numbers for a given contact.
+    Replace an existing set of records with a new or modified set of records. For example, replace a group of `Phone` numbers for a given `Contact`.
 
 !!! warning
     Replace includes an implicit delete action - use with care & test well before using in production.
 
+* **`revert`**
+
+  Available only for [Managed Entity](managed.md) types, resets a record to its original packaged state. 
+
 
 
 
diff --git a/docs/api/v4/managed.md b/docs/api/v4/managed.md
new file mode 100644
index 0000000000000000000000000000000000000000..e1498688d16374768c365c1c80b68b12bbd66ba3
--- /dev/null
+++ b/docs/api/v4/managed.md
@@ -0,0 +1,63 @@
+# APIv4 Managed Entities
+
+## Introduction
+
+When [writing an extension](../../extensions/index.md), it's often necessary to insert data when the extension is enabled,
+for example *menu items*, or *custom fields* or *option values* all might need to be added for the extension to function.
+Extensions may also want to ship with preconfigured SearchKit Displays.
+
+You'd want these items to be added when the extension is installed, disabled when the extension is disabled, and deleted when the extension is uninstalled.
+In some cases you'd also want to allow the end-user to make changes, e.g. adjusting settings for a search display,
+but allow them to revert it back to the original state defined by your extension. In other cases, e.g. a reserved
+option value, you want the user to be locked out of editing or deleting it, but you do want changes made in your extension code
+to update the record in the database, e.g. if you later add a `description` to the option value record.
+
+All this is easy to do with APIv4 Managed Entities.
+
+## Creating a Managed Record
+
+*The easiest way to package and distribute managed records is using the APIv4 Export action:*
+
+1. Create the record(s) you want in CiviCRM (on a live site, or preferably on a demo or test installation).
+2. Visit the *APIv4 Explorer*, select the entity type and the **Export** action.
+3. Enter the id of your record.
+4. Set `update` and `cleanup` modes as desired (see below).
+5. Click the **Execute** button.
+6. In the **Result** panel, select *View as PHP*, select and click the "Copy" button.
+7. In your extension, create a *managed* directory if one doesn't already exist.
+8. Create a new file in the *managed* directory with the suffix `.mgd.php`
+   (the name of the file should help you remember what's in it, a good practice is to use the `name` output by the API Export).
+9. Begin the file with the line `<?php` and paste the contents from the Export action.
+
+### Update Modes
+
+*This setting determines whether a managed record will be auto-updated after it is intalled.*
+*This is important to consider if the values in your `mgd.php` record might ever change in future updates to your extension.*
+
+- **always:** Forcibly overwrite record with values from the `.mgd.php` definition with every cache-flush.
+- **never:** Once installed, the record will never be updated.
+- **unmodified:** Updates to the `.mgd.php` will be propigated only if the record has not been updated by the user.
+
+In general, `always` is a good choice for records which are not meant to be updated by the user, such as reserved `OptionValues`.
+In most other cases, `unmodified` is the best trade-off between allowing edits to be made by the user, and propidating updates
+when changes are made to the `.mgd.php` in new versions of the extension. Changes made by the user can be undone
+by the [`revert`](actions.md#write-actions) API action.
+
+### Cleanup Modes
+
+*This setting determines whether a managed record will be deleted when the extension is uninstalled.*
+
+- **always:** Unconditionally delete the record when the extension is uninstalled.
+- **never:** Once installed, the record will never be auto-deleted.
+- **unused:** Determines whether any references to the record exist (e.g. if the record is an Acitity Type, do any activities exist of that type?), and only delete it if not.
+
+## Handling possibly existing records
+
+Normally, if the Managed system tries to add a record which already exists, it causes an error.
+However, sometimes an extension author isn't sure if the item they want to distribute already exists.
+For example, the 1.0 release of an extension may have used [`hook_civicrm_install`](../../hooks/hook_civicrm_install.md)
+to add an OptionValue. In the 2.0 release, it's decided to move it into a `mgd.php` file - what to do about existing sites
+which have already installed the OptionValue?
+
+The solution is to add [`match`](actions.md#write-actions) to the `params` array, declaring which fields should be compared to
+see if a record exists before creating a new one.
diff --git a/mkdocs.yml b/mkdocs.yml
index 3d5f3f637e39438ecba8bd19f74e88ec9b42167d..dba8e7c6579512a93826fa75ffaab5cc593c8c5e 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -104,6 +104,7 @@ nav:
       - Option Lists: api/v4/pseudoconstants.md
       - Chaining: api/v4/chaining.md
       - Custom Data: api/v4/custom-data.md
+    - Managed APIv4 Entities: api/v4/managed.md
     - Differences Between Api v3 and v4: api/v4/differences-with-v3.md
     - APIv4 Architecture: api/v4/architecture.md
     - APIv4 Changelog: api/v4/changes.md