diff --git a/docs/djblets/coderef/index.rst b/docs/djblets/coderef/index.rst
index ed150323e828d8b046cfd36cffe320569beb6439..53a446ef88dbf10485368881c030ead8d1a36335 100644
--- a/docs/djblets/coderef/index.rst
+++ b/docs/djblets/coderef/index.rst
@@ -65,6 +65,7 @@ Caching
 .. autosummary::
    :toctree: python
 
+   djblets.cache
    djblets.cache.backend
    djblets.cache.backend_compat
    djblets.cache.context_processors
@@ -113,6 +114,7 @@ Datagrids
 .. autosummary::
    :toctree: python
 
+   djblets.datagrid
    djblets.datagrid.grids
 
 
diff --git a/docs/releasenotes/5.3.rst b/docs/releasenotes/5.3.rst
new file mode 100644
index 0000000000000000000000000000000000000000..163f96014e38a5970f2c7d3ffc921f82c8c97f50
--- /dev/null
+++ b/docs/releasenotes/5.3.rst
@@ -0,0 +1,506 @@
+.. default-intersphinx:: django4.2 djblets5.x python3
+
+
+=========================
+Djblets 5.3 Release Notes
+=========================
+
+**Release date**: TBD
+
+
+Installation
+============
+
+To install Djblets 5.3, run:
+
+.. code-block:: console
+
+   $ pip3 install Djblets==5.3
+
+
+To learn more, see:
+
+* `Documentation <https://www.reviewboard.org/docs/djblets/5.x/>`_
+* `Djblets on PyPI <https://pypi.org/project/Djblets/>`_
+* `Djblets on GitHub <https://github.com/djblets/djblets/>`_
+
+
+.. _Django: https://www.djangoproject.com/
+
+
+Highlights
+==========
+
+* :py:mod:`djblets.cache`: Safer cache key building to prevent cache
+  poisoning.
+
+* :py:mod:`djblets.datagrid`: New controls and better defaults around search
+  indexing.
+
+* :py:mod:`djblets.pagestate`: New module for extensible content injections
+  into templates.
+
+* :py:mod:`djblets.protect`: New module for creating and managing distributed
+  locks and handling rate limiting.
+
+* :py:mod:`djblets.siteconfig`: Layered lookups of configuration state,
+  making it easier to override certain settings at user, team, and other
+  levels.
+
+* :py:mod:`djblets.util.views`: Subnet support for health check IPs.
+
+* Lots of new features, bug fixes, enhanced typing, and deprecations
+  throughout Djblets.
+
+
+Packaging
+=========
+
+* Added a dependency on :pypi:`typelets` 1.1.x.
+
+* Added a dependency on :pypi:`django-assert-queries` 2.0.x.
+
+  This offers compatibility for the now-deprecated
+  :py:mod:`djblets.db.query_catcher` and
+  :py:mod:`djblets.db.query_comparator` modules. This dependency may be
+  removed in a future version.
+
+
+New: djblets.pagestate
+======================
+
+This is a new feature in Djblets allowing for advanced injection of
+dynamic state into a page.
+
+Extension authors know that :py:class:`~djblets.extensions.hooks.TemplateHook`
+are a powerful way of letting third-party code modify a page, rendering new
+HTML in pre-defined places (such as a list of scripts, a list of buttons,
+etc.).
+
+This feature is now available more generally.
+
+Templates can now set up page hook points that content can be injected into,
+and callers can provide content to inject into those points. All with a simple
+API.
+
+This plays nice with HTTP caching. When injecting, a custom ETag for that
+content can be set, which will be merged with the ETag sent in the HTTP
+response.
+
+See our :ref:`guide on Dynamic Page Injections <pagestate-guide>`.
+
+
+New: djblets.protect
+====================
+
+* Added a basic distributed lock implementation that can be used to help
+  applications avoid stampede issues.
+
+  :py:class:`djblets.protect.locks.CacheLock` provides the lock, utilizing the
+  cache backend to maintain it. It can be used as a context manager or passed
+  to existing cache functions (such as
+  :py:func:`djblets.cache.backend.cache_memoize`).
+
+  .. important::
+
+     These locks should only be used in cases where the loss of a lock will
+     not cause corruption or other bad behavior. As cache backends may expire
+     keys prematurely, and may lack atomic operations, a lock cannot be
+     guaranteed.
+
+     These can be thought of as a soft optimistic lock.
+
+* Added generic rate limit support.
+
+  :py:mod:`djblets.protect.ratelimit` allows callers to rate limit any
+  operation, with rate limits based on users, IPs, or any other criteria
+  required.
+
+  This can be used to limit requests to third-party services or WebHook
+  endpoints, limit access to files, limit changes to profiles or edits on
+  objects, or anything else the application requires.
+
+  See our :ref:`guide on Rate Limiting <ratelimit-guide>`.
+
+
+djblets.cache
+=============
+
+* Added protection for cache key poisoning.
+
+  When constructing a cache key, callers can provide a list of strings
+  (hard-coded or variables) used to construct the cache key. Each will be
+  escaped and joined together safely.
+
+  This can help avoid cache key poisoning, making cache keys safe when they
+  involve dynamic input, like identifiers or domains.
+
+  The following support this:
+
+  * :py:func:`djblets.cache.backend.cache_memoize`
+  * :py:func:`djblets.cache.backend.cache_memoize_iter`
+  * :py:func:`djblets.cache.backend.make_cache_key`
+  * :py:class:`djblets.cache.synchronizer.GenerationSynchronizer`
+
+  For example:
+
+  .. code-block:: python
+
+     from djblets.cache.backends import cache_memoize
+
+     result = cache_memoize(
+         ['user-stats', user.username, 'full'],
+         lambda: build_user_stats(),
+     )
+
+  This is opt-in. If your cache key is composed of variables instead of
+  static strings, you should update your code to be a list of strings.
+
+* Added support for locked cache operations.
+
+  To avoid stampede issues, where multiple concurrent requests may cause data
+  to be generated multiple times for the same cache key, data generation can
+  now be guarded with a distributed lock.
+
+  Both :py:func:`~djblets.cache.backend.cache_memoize` and
+  :py:func:`~djblets.cache.backend.cache_memoize_iter` now take an optional
+  ``lock=`` argument, which accepts a
+  :py:class:`~djblets.protect.locks.CacheLock` instance. This will make a
+  best attempt to prevent the cache data generation function from being
+  called more than once, blocking other requests until completed.
+
+
+djblets.conditions
+==================
+
+* Added Python type hints throughout the module.
+
+
+djblets.configforms
+===================
+
+* Added dark mode support for configform field and form errors.
+
+
+djblets.datagrid
+================
+
+* Added control over search indexing.
+
+  Search index bots have a tendency to explore too many combinations of
+  column fields and sort options.
+
+  Several updates have been made to reduce what a bot can follow:
+
+  * Sort and column choices are no longer navigable links.
+
+  * The last page of a datagrid now communicates to search bots that they
+    shouldn't navigate there.
+
+  * Search bots are now informed which page is the first, previous, next,
+    or last pages in the datagrid results.
+
+  * Canonical URLs are now provided that excludes extra columns or sort
+    options, reducing how many pages need to be indexed.
+
+  * URLs all use sorted arguments, keeping URLs stable for search engines
+    that don't normalize URLs themselves.
+
+  * Search indexing for a datagrid can be turned off by setting
+    ``allow_search_indexing = False`` when constructing the datagrid.
+
+  It's still up to the search bot to follow any hints set by the datagrid.
+  Many AI bots will ignore these, but most search engines will respect them.
+
+* Added performance improvements for calculating datagrid pagination.
+
+
+djblets.db
+==========
+
+* Added :py:func:`~djblets.db.query.get_object_cached_field`.
+
+  This returns the value populated for a model's field when using
+  :py:meth:`~django.db.models.query.QuerySet.prefetch_related` or
+  :py:meth:`~django.db.models.query.QuerySet.select_related`.
+
+  If the value is not found in the cache, the value will *not* be fetched
+  from the database. Instead, :py:func:`typelets.symbols.UNSET` will be
+  returned. This makes it a useful way of safely checking for a cached
+  value quickly without any side effects.
+
+* Added type hints for :py:func:`~djblets.db.query.get_object_or_none`.
+
+  The returned type will always be an instance of the model type that's
+  passed in.
+
+* Fixed validation of values in
+  :py:class:`~djblets.db.fields.comma_separated_values_field.
+  CommaSeparatedValuesField`.
+
+* Deprecated :py:mod:`djblets.db.query_catcher` and
+  :py:mod:`djblets.db.query_comparator`.
+
+  This has been replaced by our new :pypi:`django-assert-queries` package.
+  Djblets will include this as a dependency for now, but the dependency
+  will be dropped in a future release. Projects using this functionality
+  should explicitly depend on this package.
+
+
+djblets.extensions
+==================
+
+* :py:class:`~djblets.extensions.hooks.TemplateHook` subclasses can now
+  be customized on the class level.
+
+  The following can be set on the class directly, instead of having to pass
+  during construction:
+
+  * :py:attr:`~djblets.extensions.hooks.TemplateHook.apply_to`
+  * :py:attr:`~djblets.extensions.hooks.TemplateHook.extra_context`
+  * :py:attr:`~djblets.extensions.hooks.TemplateHook.name`
+  * :py:attr:`~djblets.extensions.hooks.TemplateHook.template_name`
+
+* :py:class:`~djblets.extensions.hooks.TemplateHook` cam opt out of rendering
+  by overriding :py:meth:`~djblets.extensions.hooks.TemplateHook
+  .should_render`.
+
+* Added custom template context for the
+  :py:func:`~djblets.extensions.views.configure_extension` and
+  :py:func:`~djblets.extensions.views.extension_list` views.
+
+  This allows applications to specialize these views, adding additional
+  data to include in the templates.
+
+* Full extension bundle IDs can now be passed to the
+  :py:func:`{% ext_css_bundle %}
+  <djblets.extensions.templatetags.djblets_extensions.ext_css_bundle>` and
+  :py:func:`{% ext_js_bundle %}
+  <djblets.extensions.templatetags.djblets_extensions.ext_js_bundle>`
+  template tags.
+
+  Full bundle IDs include the extension ID. These can be used instead of the
+  short extension-local bundle ID, in places where that's more convenient
+  or where rendering a bundle outside of an extension-managed template.
+
+* Fixed various type hint issues in both the Python and JavaScript extension
+  APIs.
+
+
+djblets.features
+================
+
+* :py:class:`~djblets.features.testing.override_feature_checks` can now be
+  used as a decorator.
+
+  This function is used for unit tests that need to simulate enabling or
+  disabling a feature check. It can now be used as a decorator on a unit
+  test function or class. For example:
+
+  .. code-block:: python
+
+     from djblets.features.testing import override_feature_checks
+
+
+     @override_feature_checks(my_feature_id, enabled=True)
+     def test_my_feature() -> None:
+         ...
+
+
+djblets.forms
+=============
+
+* :py:class:`~djblets.forms.fields.ConditionsField` now supports
+  dynamically-registered condition choices.
+
+  When passing an instance of a
+  :py:class:`~djblets.conditions.choices.ConditionChoices` to the field,
+  any choices dynamically registered (such as through an extension) will
+  be included in the field.
+
+* Fixed display issues with :py:class:`~djblets.forms.widgets.ListEditWidget`.
+
+  The widget now better fits into the available space where it's shown, and
+  shows a standard button appearance.
+
+
+djblets.http
+============
+
+* Added :py:func:`djblets.http.requests.get_http_request_ip`.
+
+  This method returns the requester's IP, paying attention to the
+  :mailheader:`X-Real-IP` and :mailheader:`X-Forwarded-For` headers if
+  available.
+
+  Callers using this must be careful to ensure HTTP requests cannot send
+  falsified headers.
+
+
+djblets.log
+===========
+
+* Added new options,  context manager support, and deprecations for
+  :py:func:`~djblets.log.log_timed`.
+
+  * The new ``logger`` argument can specify which logger should be used
+    for any log messages.
+
+  * The new ``extra`` argument can be used to specify additional state to
+    pass along with the long message.
+
+  * The function can now be used as a context manager, which is a convenience
+    over explicitly calling ``done()`` on the log timer.
+
+  * All arguments except ``message`` must now be passed as keyword-only.
+
+    This will be required starting in Djblets 7.
+
+
+djblets.mail
+============
+
+* Added enhanced logging for DMARC record fetching.
+
+  When fetching a DMARC record, DEBUG-level messages will be logged to
+  indicate when the fetch begins and ends. If it takes too long, WARNING,
+  ERROR, or CRITICAL log messages will be logged based on the time spent.
+
+* Added enhanced logging for e-mail sending.
+
+  When sending an e-mail using a mail backend, DEBUG-level messages will be
+  logged to indicate when the e-mail is being sent and when delivery has
+  finished. If it takes too long, WARNING, ERROR, or CRITICAL log messages
+  will be logged based on the time spent.
+
+
+djblets.siteconfig
+==================
+
+* Added layered lookups for site configuration.
+
+  :py:meth:`SiteConfiguration.get()
+  <djblets.siteconfig.models.SiteConfiguration.get>` now accepts a
+  ``layers=`` argument, which is a list of dictionaries to search for the key.
+
+  If the key is present in any of the dictionaries, the value will be
+  returned without checking the stored site configuration or defaults.
+
+  This can be used to offer overrides for settings specific to users,
+  teams, feature flag state, etc., all with the convenience of one call.
+
+
+djblets.testing
+===============
+
+* All mismatched warnings are now shown in the
+  :py:meth:`TestCase.assertWarnings()
+  <djblets.testing.testcases.TestCase.assertWarnings>` output.
+
+
+djblets.util.decorators
+=======================
+
+* Added type casting to :py:func:`{% definevar %}
+  <djblets.util.templatetags.djblets_utils.definevar>`.
+
+  Callers can now specify that the defined data should be stored as a
+  specific type. This supports:
+
+  * ``as_type=str``: String (default)
+  * ``as_type=bool``: Boolean (``true`` or ``1``, case-insensitive)
+  * ``as_type=int``: Integer (invalid values will raise a
+    :py:exc:`~django.template.TemplateSyntaxError`).
+
+* Template tags implemented using
+  :py:func:`@blocktag <djblets.util.decorators.blocktag>` now accept keyword
+  arguments.
+
+  For example:
+
+  .. code-block:: html+django
+
+     {% my_block_tag param1=True param2="hi" %}
+
+* Calls to :py:func:`@blocktag <djblets.util.decorators.blocktag>` can now
+  specify an explicit tag name.
+
+  For example:
+
+  .. code-block:: python
+
+     @blocktag(name='my-block-tag')
+     def _my_tag(context, nodelist):
+         ...
+
+
+djblets.util.views
+===================
+
+* ``settings.DJBLETS_HEALTHCHECK_IPS`` can now list subnets.
+
+  When using :py:class:`~djblets.util.views.HealthCheckView`, the configured
+  list of IPs allowed to perform a health check can now include subnets.
+  For example:
+
+  .. code-block:: python
+
+     DJBLETS_HEALTHCHECK_IPS = [
+         '127.0.0.1',
+         '10.0.0.0/8',
+     ]
+
+
+djblets.util.http
+=================
+
+* :py:func:`~djblets.util.http.get_url_params_except` now returns arguments
+  in sorted order instead of dictionary order.
+
+  This helps ensure URLs are consistent, which can help with caching.
+
+* Updated :py:func:`~djblets.util.http.encode_etag` to return SHA256
+  results instead of SHA1.
+
+
+djblets.util.templatetags.djblets_utils
+=======================================
+
+* Added a :py:func:`{% unique_id %}
+  <djblets.util.templatetags.djblets_utils.unique_id>` template tag
+  for generating unique IDs.
+
+  These IDs can be used to dynamically create an ID for an element, making
+  it easy for other attributes to reference that ID. For example:
+
+  .. code-block: html+django
+
+     {% unique_id "my_component" as my_unique_id %}
+
+     <div aria-labelledby="{{my_unique_id}}">
+      <h2 id="{{my_unique_id}}">Header</h2>
+     </div>
+
+
+djblets.webapi
+==============
+
+* Added :py:meth:`WebAPIResource.serialize_object_list
+  <djblets.webapi.resources.base.WebAPIResource.serialize_object_list>`.
+
+  Subclasses can override this to offer custom serialization of items for
+  a payload, or to offer pre-processing/post-processing of the results.
+
+* Added the ``serialize_object_list_func`` argument to
+  :py:class:`~djblets.webapi.responses.WebAPIResponsePaginated`.
+
+  Callers can provide a function this to offer custom serialization of items
+  for a payload, or to offer pre-processing/post-processing of the results.
+
+
+Contributors
+============
+
+* Christian Hammond
+* David Trowbridge
+* Michelle Aubin
diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst
index 3d60a17af844742374b56bb46ae9d6f4cb5de224..03bf4ab063ca8addb263a0bbcca59f84c0c28af2 100644
--- a/docs/releasenotes/index.rst
+++ b/docs/releasenotes/index.rst
@@ -11,6 +11,7 @@ Djblets Release Notes
 .. toctree::
    :maxdepth: 1
 
+   5.3
    5.2.1
    5.2
    5.1.1
