diff --git a/content/developer/howtos.rst b/content/developer/howtos.rst
index 8c71e3cc8..6a38860ac 100644
--- a/content/developer/howtos.rst
+++ b/content/developer/howtos.rst
@@ -12,6 +12,7 @@ How-to guides
howtos/javascript_field
howtos/javascript_view
howtos/javascript_client_action
+ howtos/standalone_owl_application
howtos/web_services
howtos/company
howtos/create_reports
@@ -42,6 +43,12 @@ How-to guides
Learn how to create client actions in the Odoo JavaScript web framework.
+ .. card:: Create a standalone Owl application
+ :target: howtos/standalone_owl_application
+
+ Learn how to create a public-facing Owl application outside of the web client using a
+ controller and the Odoo JavaScript framework.
+
.. card:: Web services
:target: howtos/web_services
diff --git a/content/developer/howtos/standalone_owl_application.rst b/content/developer/howtos/standalone_owl_application.rst
new file mode 100644
index 000000000..3673c479a
--- /dev/null
+++ b/content/developer/howtos/standalone_owl_application.rst
@@ -0,0 +1,172 @@
+===================================
+Create a standalone Owl application
+===================================
+
+For any number of reasons, you may want to have a standalone Owl application that isn't a part of
+the web client. One example in Odoo is the self-ordering application, that lets customers order
+food from their phone. In this chapter we will go into what's required to achieve something like this.
+
+Overview
+========
+
+To have a standalone Owl app, a few things are required:
+
+- a root component for the application
+- some setup code to start the services and mount the component
+- an assets bundle that contains the setup code
+- a QWeb view that calls the assets bundle
+- a controller that renders the view
+
+1. Root component
+=================
+
+To keep things simple, let's start with a very straightforward component that just renders
+"Hello, World!". This will let us know at a glance if our setup is working.
+
+First, create the template in :file:`/your_module/static/src/standalone_app/root.xml`.
+
+.. code-block:: xml
+
+
+
+
+ Hello, World!
+
+
+
+Then create the JavaScript file for that component in :file:`/your_module/static/src/standalone_app/root.js`.
+
+.. code-block:: js
+
+ /** @odoo-module */
+ import { Component } from "@odoo/owl";
+
+ export class Root extends Component {
+ static template = "your_module.Root";
+ static props = {};
+ }
+
+.. seealso::
+ :ref:`Owl components reference`.
+
+2. Setting up the environment and mounting the application
+==========================================================
+
+An Owl application needs an environment, and the Odoo JavaScript framework needs that environment to
+contain the :ref:`services`.
+Starting the services is also required to load the translations, which we need to do before mounting
+our Owl application, so that we can give Owl a working translation function.
+
+Create the JavaScript file that will mount the app in :file:`/your_module/static/src/standalone_app/app.js`.
+
+.. code-block:: js
+
+ /** @odoo-module */
+ import { whenReady } from "@odoo/owl";
+ import { mountComponent } from "@web/env";
+ import { Root } from "./root";
+
+ whenReady(() => mountComponent(Root, document.body));
+
+
+3. Creating an assets bundle containing our code
+================================================
+
+In the manifest of your module, create a new :ref:`assets bundle`.
+It should include the `web._assets_core` bundle, which contains the Odoo JavaScript
+framework and the core libraries it needs (e.g. Owl and luxon), after which you can have a
+glob that adds all the files for your application in the bundle.
+
+.. code-block:: py
+ :emphasize-lines: 9-10
+
+ {
+ # ...
+ 'assets': {
+ 'your_module.assets_standalone_app': [
+ ('include', 'web._assets_helpers'),
+ 'web/static/src/scss/pre_variables.scss',
+ 'web/static/lib/bootstrap/scss/_variables.scss',
+ ('include', 'web._assets_bootstrap'),
+ ('include', 'web._assets_core'),
+ 'your_module/static/src/standalone_app/**/*',
+ ],
+ }
+ }
+
+The other lines are bundles and scss files that are required to make Bootstrap work. They are
+mandatory, as the components of the web framework use bootstrap classes for their styling and
+layout.
+
+.. caution::
+ Make sure that the files for your standalone app are only added to this bundle, if you already
+ have a definition for `web.assets_backend` or `web.assets_frontend` and they have globs, make
+ sure these globs don't match the files for your standalone app, otherwise the startup code for
+ your app will conflict with the existing startup code in those bundles.
+
+.. seealso::
+ :ref:`Module manifest reference`.
+
+4. XML view that calls the assets bundle
+========================================
+
+Now that we have created our assets bundle, we need to create a
+:ref:`QWeb view` that uses that assets bundle.
+
+.. code-block:: xml
+
+
+
+ <!DOCTYPE html>
+
+
+
+
+
+
+
+
+
+
+This template only does two things: it initializes the `odoo` global variable, then calls the assets
+bundle we just defined. Initializing the `odoo` global variable is a necessary step. This variable
+should be an object that contains the following:
+
+- The CSRF token, which is required to interact with HTTP controllers in many cases.
+- The debug value, which is used in many places to add additional logging or developer-friendly checks.
+- `__session_info__`, that contains information from the server that is always needed and for which
+ we don't want to perform an additional request. More on this in the next section.
+
+5. Controller that renders the view
+===================================
+
+Now that we have the view, we need to make it accessible to the user. For that purpose, we will create
+an :ref:`HTTP controller` that renders that view and returns it to the user.
+
+.. code-block:: py
+
+ from odoo.http import request, route, Controller
+
+ class YourController(Controller):
+ @route("/your_module/standalone_app", auth="public")
+ def standalone_app(self):
+ return request.render(
+ 'your_module.standalone_app',
+ {
+ 'session_info': request.env['ir.http'].get_frontend_session_info(),
+ }
+ )
+
+Notice how we're giving the template `session_info`. We get it from the `get_frontend_session_info`
+method, and it will end up containing information used by the web framework, such as the current
+user's ID if they are logged in, the server version, the Odoo edition, etc.
+
+At this point, if you open the url `/your_module/standalone_app` in your brower, you should
+see a blank page with the text "Hello, World!". At this point, you can start actually writing the
+code for your app.