diff --git a/documentation/docs/06-runtime/05-hydratable.md b/documentation/docs/06-runtime/05-hydratable.md
new file mode 100644
index 0000000000..671d2bf93b
--- /dev/null
+++ b/documentation/docs/06-runtime/05-hydratable.md
@@ -0,0 +1,99 @@
+---
+title: "`hydratable`"
+---
+
+In Svelte, when you want to render asynchonous content data on the server, you can simply `await` it. This is great! However, it comes with a major pitall: when hydrating that content on the client, Svelte has to redo the asynchronous work, which blocks hydration for however long it takes:
+
+```svelte
+
+
+
{user.name}
+```
+
+That's silly, though. If we've already done the hard work of getting the data on the server, we don't want to get it again during hydration on the client. `hydratable` is a low-level API build to solve this problem. You probably won't need this very often -- it will probably be used behind the scenes by whatever datafetching library you use. For example, it powers [remote functions in SvelteKit](/docs/kit/remote-functions).
+
+To fix the example above:
+
+```svelte
+
+
+{user.name}
+```
+
+This API can also be used to provide access to random or time-based values that are stable between server rendering and hydration. For example, to get a random number that doesn't update on hydration:
+
+```ts
+import { hydratable } from 'svelte';
+const rand = hydratable('random', () => Math.random());
+```
+
+If you're a library author, be sure to prefix the keys of your `hydratable` values with the name of your library so that your keys don't conflict with other libraries.
+
+## Imperative API
+
+If you're writing a library with separate server and client exports, it may be more convenient to use the imperative API:
+
+```ts
+import { hydratable } from 'svelte';
+
+const value = hydratable.get('foo'); // only works on the client
+const hasValue = hydratable.has('foo');
+hydratable.set('foo', 'whatever value you want'); // only works on the server
+```
+
+## Custom serialization
+
+By default, Svelte uses [`devalue`](https://npmjs.com/package/devalue) to serialize your data on the server so that decoding it on the client requires no dependencies. If you need to serialize additional things not covered by `devalue`, you can provide your own transport mechanisms by writing custom `encode` and `decode` methods.
+
+### `encode`
+
+Encode receives a value and outputs _the JavaScript code necessary to create that value on the client_. For example, Svelte's built-in encoder looks like this:
+
+```ts
+const encode = (value) => devalue.uneval(value);
+encode(['hello', 'world']); // outputs `['hello', 'world']`
+```
+
+### `decode`
+
+`decode` accepts whatever the JavaScript that `encode` outputs resolves to, and returns whatever the final value from `hydratable` should be.
+
+### Usage
+
+When using the isomorphic API, you must provide either `encode` or `decode`, depending on the environment. This enables your bundler to treeshake the unneeded code during your build:
+
+```svelte
+
+```
+
+For the imperative API, you just provide `encode` or `decode` depending on which method you're using:
+
+```ts
+import { hydratable } from 'svelte';
+import { encode, decode } from '$lib/encoders';
+
+const random = hydratable.get('random', { decode });
+hydratable.set('random', Math.random(), { encode });
+```