diff --git a/docs/framework/cache.md b/docs/framework/cache.md
index c356dcaa58df8eec52abc39a55a7455f7242c746..5841d500629145414804debee8eca0db5a4b835a 100644
--- a/docs/framework/cache.md
+++ b/docs/framework/cache.md
@@ -1,11 +1,13 @@
 # Cache Reference
 
-## Using the cache
+## Using the default cache {:#default}
 
-`Civi::cache()` is the simplest way to access the cache, automatically using the default cache type (described below). The `CRM_Utils_Cache_Interface` class lays out the methods for saving and retrieving cached items.
+`Civi::cache()` is the simplest way to access the cache, automatically using the default cache type (described in [Configuration](#configuration)).
 
 ### Methods
 
+The `CRM_Utils_Cache_Interface` class lays out the methods for saving and retrieving cached items.
+
 * Set a cache value
 
     ```php
@@ -30,6 +32,13 @@
     Civi::cache()->flush();
     ```
 
+!!! tip "PSR-16 Compliance (v5.4+)"
+
+    In CiviCRM v5.4+, the cache complies with the PHP-FIG standard [PSR-16](https://www.php-fig.org/psr/psr-16/). `CRM_Utils_Cache_Interface` extends the simple `CacheInterface`, although the implementations differ in a couple small ways:
+
+    * The flush function has two names -- `clear()` (per PSR-16) and `flush()` (which predates PSR-16). These are synonyms.
+    * Cache keys in PSR-16 are prohibited from using certain characters.  However, some of these characters were supported in previous versions of CiviCRM's cache interface.  To enable a transition, these restrictions are *not* enforced in a default runtime. However, they *are* enforced during testing, and they can be enabled in `civicrm.settings.php` by toggling `CIVICRM_PSR16_STRICT`.
+
 ### Example
 
 ```php
@@ -49,16 +58,108 @@ function findMagicNumber() {
 }
 ```
 
-## Cache types
+### Configuration
+
+In a stock configuration, the `Civi::cache()` object stores data in a local PHP variable (`ArrayCache`).  This allows frequent, high-speed I/O, but
+it only retains data for the scope of one page-request -- which reduces the potential performance gains.
+
+System administrators may configure the default cache to use a more long-term backend, such as `Memcached` or `Redis`.  For more information about
+configuring the default cache driver, see [System Administrator Guide => Setup => Caches](https://docs.civicrm.org/sysadmin/en/latest/setup/cache/).
+
+### Aliases
+
+In reading code, you may find these three notations -- which all refer to the same thing:
+
+* `Civi::cache()`
+* `Civi::cache('default')`
+* `CRM_Utils_Cache::singleton()`
+
+## Using a custom cache {:#custom}
 
-This is selected in `civicrm.settings.php`, where `CIVICRM_DB_CACHE_CLASS` is defined.
+Generally, it's best to store caches in a memory-backed service like Redis or Memcached. But what happens if the system-configuration doesn't support that?
+Perhaps you store the cache in a MySQL table? Or a data-file? Or a PHP array?
 
-* "ArrayCache" - This is the default, using an in-memory array.
+The answers should not be the same for all data. For example:
 
-* "Memcache" - This is for the PHP Memcache extension.
+* If the cache is tracking metadata derived from `civicrm_option_value`, then you can get the original data pretty quickly (by querying MySQL).
+  Writing the cache to another MySQL table or data-file would serve little benefit.
+* If the cache is tracking a remote feed (fetched from another continent via HTTPS), then it's much more expensive to get the original data. In absence of
+  Redis/Memcached, you might put the cache in a MySQL table or a data-file.
 
-* "Memcached" - This is for the PHP Memcached extension.
+With a *custom cache object*, a developer gets the same interface (`CRM_Utils_Cache_Interface` / PSR-16), but they can define different preferences
+for how to store the cache-data. In particular, you can define a fallback list. Compare these examples:
 
-* "APCcache" - This is for the PHP APC extension.
+* `['SqlGroup', 'ArrayCache']` means "If MySQL is available, use that. Otherwise, use a local PHP array."
+* `['*memory*', 'SqlGroup', 'ArrayCache']` means "If any memory service is available, then use that. Otherwise, if MySQL is available, use that. As a last resort, use a local PHP array."
 
-* "NoCache" - This caches nothing
+You can manually instantiate a custom cache object using the factory-function, `CRM_Utils_Cache::create()`. This is good for demonstrating the concept and for some edge-cases. However,
+in typical usage, it's more common to register a named service.
+
+### Example: Manual
+
+```php
+// Create a cache object
+$cache = CRM_Utils_Cache::create([
+  'type' => ['SqlGroup', 'ArrayCache'],
+  'name' => 'HelloWorld',
+]);
+```
+
+A few things to notice here:
+
+* The `type` parameter is an array of preferred storage systems.
+* The `name` will be passed down to the storage system -- ensuring that different caches are stored separately.
+    * Ex: In `Memcached`/`Redis`, the `name` becomes part of the cache-key.
+    * Ex: In `SqlGroup`, the `name` corresponds to the field `civicrm_cache.group_name`.
+
+Once you have the `$cache` object, it supports all the methods of `CRM_Utils_Cache_Interface` and PSR-16.
+
+```php
+// Use the cache object
+$value = $cache->get('name');
+if ($value === NULL) {
+  $cache->set('name', 'Alice');
+}
+
+// Flush the contents of the cache
+$cache->flush();
+```
+
+### Example: Named service
+
+First, we define the service in `Civi\Core\Container` or `hook_civicrm_container`:
+
+```php
+$container->setDefinition("cache.hello", new Definition(
+  'CRM_Utils_Cache_Interface',
+  [[
+    'type' => ['*memory*', 'SqlGroup', 'ArrayCache'],
+    'name' => 'HelloWorld',
+  ]]
+))->setFactory('CRM_Utils_Cache::create');
+```
+
+As before, notice that:
+
+* The `type` parameter is an array of preferred storage systems. It will choose the first valid driver.
+* The `name` will be passed down to the storage system.
+* The service is an instance of `CRM_Utils_Cache_Interface` (PSR-16).
+
+Once the service is declared, we can get a reference to the cache in several ways:
+
+* Lookup the service with `Civi::cache('hello')`
+* Lookup the service with `Civi::service('cache.hello')`
+* Inject the service using the container's dependency-injection
+
+For example, we could use `Civi::cache('hello')` as follows:
+
+```php
+// Use the cache object
+$value = Civi::cache('hello')->get('name');
+if ($value === NULL) {
+  Civi::cache('hello')->set('name', 'Alice');
+}
+
+// Flush the contents of the cache
+Civi::cache('hello')->flush();
+```