diff --git a/doc/index.rst b/doc/index.rst
index 5d0e71d45..b375f67bd 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -16,6 +16,7 @@ manpages and API documentation. For primary user documentation, see
qubes
qubes-vm/index
qubes-events
+ qubes-features
qubes-storage
qubes-exc
qubes-ext
diff --git a/doc/qubes-features.rst b/doc/qubes-features.rst
new file mode 100644
index 000000000..0b717ac10
--- /dev/null
+++ b/doc/qubes-features.rst
@@ -0,0 +1,186 @@
+:py:class:`qubes.vm.Features` - Qubes VM features, services
+============================================================
+
+Features are generic mechanism for storing key-value pairs attached to a
+VM. The primary use case for them is data storage for extensions (you can think
+of them as more flexible properties, defined by extensions), but some are also
+used in the qubes core itself. There is no definite list of supported features,
+each extension can set their own and there is no requirement of registration,
+but :program:`qvm-features` man page contains well known ones.
+In addition, there is a mechanism for VM request setting a feature. This is
+useful for extensions to discover if its VM part is present.
+
+Features can have three distinct values: no value (not present in mapping,
+which is closest thing to :py:obj:`None`), empty string (which is
+interpreted as :py:obj:`False`) and non-empty string, which is
+:py:obj:`True`. Anything assigned to the mapping is coerced to strings,
+however if you assign instances of :py:class:`bool`, they are converted as
+described above. Be aware that assigning the number `0` (which is considered
+false in Python) will result in string `'0'`, which is considered true.
+
+:py:class:`qubes.vm.Features` inherits from :py:class:`dict`, so provide all the
+standard functions to get, list and set values. Additionally provide helper
+functions to check if given feature is set on the VM and default to the value
+on the VM's template or netvm. This is useful for features which nature is
+inherited from other VMs, like "is package X is installed" or "is VM behind a
+VPN".
+
+Example usage of features in extension:
+
+.. code-block:: python
+
+ import qubes.exc
+ import qubes.ext
+
+ class ExampleExtension(qubes.ext.Extension):
+ @qubes.ext.handler('domain-pre-start')
+ def on_domain_start(self, vm, event, **kwargs):
+ if vm.features.get('do-not-start', False):
+ raise qubes.exc.QubesVMError(vm,
+ 'Start prohibited because of do-not-start feature')
+
+ if vm.features.check_with_template('something-installed', False):
+ # do something
+
+The above extension does two things:
+ - prevent starting a qube with ``do-not-start`` feature set
+ - do something when ``something-installed`` feature is set on the qube, or its template
+
+
+qvm-features-request, qubes.PostInstall service
+------------------------------------------------
+
+When some package in the VM want to request feature to be set (aka advertise
+support for it), it should place a shell script in ``/etc/qubes/post-install.d``.
+This script should call :program:`qvm-features-request` with ``FEATURE=VALUE`` pair(s) as
+arguments to request those features. It is recommended to use very simple
+values here (for example ``1``). The script should be named in form
+``XX-package-name.sh`` where ``XX`` is two-digits number below 90 and
+``package-name`` is unique name specific to this package (preferably actual
+package name). The script needs executable bit set.
+
+``qubes.PostInstall`` service will call all those scripts after any package
+installation and also after initial template installation or template startup.
+This way package have a chance to report to dom0 if any feature is
+added/removed.
+
+The features flow to dom0 according to the diagram below. Important part is
+that qubes core :py:class:`qubes.ext.Extension` is responsible for handling such request in
+``features-request`` event handler. If no extension handles given feature request,
+it will be ignored. The extension should carefuly validate requested
+features (ignoring those not recognized - may be for another extension) and
+only then set appropriate value on VM object
+(:py:attr:`qubes.vm.BaseVM.features`). It is recommended to make the
+verification code as bulletproof as possible (for example allow only specific
+simple values, instead of complex structures), because feature requests
+come from untrusted sources. The features actually set on the VM in some cases
+may not be necessary those requested. Similar for values.
+
+.. graphviz::
+
+ digraph {
+
+ "qubes.PostInstall";
+ "/etc/qubes/post-install.d/ scripts";
+ "qvm-features-request";
+ "qubes.FeaturesRequest";
+ "qubes core extensions";
+ "VM features";
+
+ "qubes.PostInstall" -> "/etc/qubes/post-install.d/ scripts";
+ "/etc/qubes/post-install.d/ scripts" -> "qvm-features-request"
+ [xlabel="each script calls"];
+ "qvm-features-request" -> "qubes.FeaturesRequest"
+ [xlabel="last script call the service to dom0"];
+ "qubes.FeaturesRequest" -> "qubes core extensions"
+ [xlabel="features-request event"];
+ "qubes core extensions" -> "VM features"
+ [xlabel="verification"];
+
+ }
+
+Example ``/etc/qubes/post-install.d/20-example.sh`` file:
+
+.. code-block:: shell
+
+ #!/bin/sh
+
+ qvm-features-request example-feature=1
+
+Example extension handling the above:
+
+.. code-block:: python
+
+ import qubes.ext
+
+ class ExampleExtension(qubes.ext.Extension):
+ # the last argument must be named untrusted_features
+ @qubes.ext.handler('features-request')
+ def on_features_request(self, vm, event, untrusted_features):
+ # don't allow TemplateBasedVMs to request the feature - should be
+ # requested by the template instead
+ if hasattr(vm, 'template'):
+ return
+
+ untrusted_value = untrusted_features.get('example-feature', None)
+ # check if feature is advertised and verify its value
+ if untrusted_value != '1':
+ return
+ value = untrusted_value
+
+ # and finally set the value
+ vm.features['example-feature'] = value
+
+Services
+---------
+
+`Qubes services `_ are implemented
+as features with ``service.`` prefix. The
+:py:class:`qubes.ext.services.ServicesExtension` enumerate all the features
+in form of ``service.`` prefix and write them to QubesDB as
+``/qubes-service/`` and value either ``0`` or ``1``.
+VM startup scripts list those entries for for each with value of ``1``, create
+``/var/run/qubes-service/`` file. Then, it can be conveniently
+used by other scripts to check whether dom0 wishes service to be enabled or
+disabled.
+
+VM package can advertise what services are supported. For that, it needs to
+request ``supported-service.`` feature with value ``1`` according
+to description above. The :py:class:`qubes.ext.services.ServicesExtension` will
+handle such request and set this feature on VM object. ``supported-service.``
+features that stop being advertised with ``qvm-features-request`` call are
+removed. This way, it's enough to remove the file from
+``/etc/qubes/post-install.d`` (for example by uninstalling package providing
+the service) to tell dom0 the service is no longer supported.
+
+Various tools will use this information to discover if given service is
+supported. The API does not enforce service being first advertised before being
+enabled (means: there can be service which is enabled, but without matching
+``supported-service.`` feature). The list of well known services is in
+:program:`qvm-service` man page.
+
+Example ``/etc/qubes/post-install.d/20-my-service.sh``:
+
+.. code-block:: shell
+
+ #!/bin/sh
+
+ qvm-features-request supported-service.my-service=1
+
+Services and features can be then inspected from dom0 using
+:program:`qvm-features` tool, for example:
+
+.. code-block:: shell
+
+ $ qvm-features my-qube
+ supported-service.my-service 1
+
+Module contents
+---------------
+
+.. autoclass:: qubes.vm.Features
+ :members:
+ :show-inheritance:
+
+.. vim: ts=3 sw=3 et
+