diff --git a/docs/index.rst b/docs/index.rst
index 20a55b2c..6f3c9574 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -51,6 +51,7 @@ Contents
introspection
custom_templates
plugins
+ writing_plugins
plugin_hooks
internals
contributing
diff --git a/docs/plugin_hooks.rst b/docs/plugin_hooks.rst
index 44797d92..2de0cccc 100644
--- a/docs/plugin_hooks.rst
+++ b/docs/plugin_hooks.rst
@@ -9,7 +9,7 @@ Each plugin can implement one or more hooks using the ``@hookimpl`` decorator ag
When you implement a plugin hook you can accept any or all of the parameters that are documented as being passed to that hook.
-For example, you can implement the ``render_cell`` plugin hook like thiseven though the full documented hook signature is ``render_cell(value, column, table, database, datasette)``:
+For example, you can implement the ``render_cell`` plugin hook like this even though the full documented hook signature is ``render_cell(value, column, table, database, datasette)``:
.. code-block:: python
diff --git a/docs/plugins.rst b/docs/plugins.rst
index 09a6e4f5..03972c7a 100644
--- a/docs/plugins.rst
+++ b/docs/plugins.rst
@@ -33,53 +33,21 @@ Things you can do with plugins include:
Installing plugins
------------------
-If a plugin has been packaged for distribution using setuptools you can use
-the plugin by installing it alongside Datasette in the same virtual
-environment or Docker container.
+If a plugin has been packaged for distribution using setuptools you can use the plugin by installing it alongside Datasette in the same virtual environment or Docker container.
-You can also define one-off per-project plugins by saving them as
-``plugin_name.py`` functions in a ``plugins/`` folder and then passing that
-folder to ``datasette serve``.
+You can also define one-off per-project plugins by saving them as ``plugin_name.py`` functions in a ``plugins/`` folder and then passing that folder to ``datasette`` using the ``--plugins-dir`` option::
-The ``datasette publish`` and ``datasette package`` commands both take an
-optional ``--install`` argument. You can use this one or more times to tell
-Datasette to ``pip install`` specific plugins as part of the process. You can
-use the name of a package on PyPI or any of the other valid arguments to ``pip
-install`` such as a URL to a ``.zip`` file::
+ datasette mydb.db --plugins-dir=plugins/
+
+The ``datasette publish`` and ``datasette package`` commands both take an optional ``--install`` argument. You can use this one or more times to tell Datasette to ``pip install`` specific plugins as part of the process::
+
+ datasette publish cloudrun mydb.db --install=datasette-vega
+
+You can use the name of a package on PyPI or any of the other valid arguments to ``pip install`` such as a URL to a ``.zip`` file::
datasette publish cloudrun mydb.db \
- --install=datasette-plugin-demos \
--install=https://url-to-my-package.zip
-.. _plugins_writing_one_off:
-
-Writing one-off plugins
------------------------
-
-The easiest way to write a plugin is to create a ``my_plugin.py`` file and
-drop it into your ``plugins/`` directory. Here is an example plugin, which
-adds a new custom SQL function called ``hello_world()`` which takes no
-arguments and returns the string ``Hello world!``.
-
-.. code-block:: python
-
- from datasette import hookimpl
-
- @hookimpl
- def prepare_connection(conn):
- conn.create_function('hello_world', 0, lambda: 'Hello world!')
-
-If you save this in ``plugins/my_plugin.py`` you can then start Datasette like
-this::
-
- datasette serve mydb.db --plugins-dir=plugins/
-
-Now you can navigate to http://localhost:8001/mydb and run this SQL::
-
- select hello_world();
-
-To see the output of your plugin.
-
.. _plugins_installed:
Seeing what plugins are installed
@@ -131,96 +99,6 @@ If you run ``datasette plugins --all`` it will include default plugins that ship
You can add the ``--plugins-dir=`` option to include any plugins found in that directory.
-Packaging a plugin
-------------------
-
-Plugins can be packaged using Python setuptools. You can see an example of a
-packaged plugin at https://github.com/simonw/datasette-plugin-demos
-
-The example consists of two files: a ``setup.py`` file that defines the plugin:
-
-.. code-block:: python
-
- from setuptools import setup
-
- VERSION = '0.1'
-
- setup(
- name='datasette-plugin-demos',
- description='Examples of plugins for Datasette',
- author='Simon Willison',
- url='https://github.com/simonw/datasette-plugin-demos',
- license='Apache License, Version 2.0',
- version=VERSION,
- py_modules=['datasette_plugin_demos'],
- entry_points={
- 'datasette': [
- 'plugin_demos = datasette_plugin_demos'
- ]
- },
- install_requires=['datasette']
- )
-
-And a Python module file, ``datasette_plugin_demos.py``, that implements the
-plugin:
-
-.. code-block:: python
-
- from datasette import hookimpl
- import random
-
-
- @hookimpl
- def prepare_jinja2_environment(env):
- env.filters['uppercase'] = lambda u: u.upper()
-
-
- @hookimpl
- def prepare_connection(conn):
- conn.create_function('random_integer', 2, random.randint)
-
-
-Having built a plugin in this way you can turn it into an installable package
-using the following command::
-
- python3 setup.py sdist
-
-This will create a ``.tar.gz`` file in the ``dist/`` directory.
-
-You can then install your new plugin into a Datasette virtual environment or
-Docker container using ``pip``::
-
- pip install datasette-plugin-demos-0.1.tar.gz
-
-To learn how to upload your plugin to `PyPI `_ for use by
-other people, read the PyPA guide to `Packaging and distributing projects
-`_.
-
-Static assets
--------------
-
-If your plugin has a ``static/`` directory, Datasette will automatically
-configure itself to serve those static assets from the following path::
-
- /-/static-plugins/NAME_OF_PLUGIN_PACKAGE/yourfile.js
-
-See `the datasette-plugin-demos repository `_
-for an example of how to create a package that includes a static folder.
-
-Custom templates
-----------------
-
-If your plugin has a ``templates/`` directory, Datasette will attempt to load
-templates from that directory before it uses its own default templates.
-
-The priority order for template loading is:
-
-* templates from the ``--template-dir`` argument, if specified
-* templates from the ``templates/`` directory in any installed plugins
-* default templates that ship with Datasette
-
-See :ref:`customization` for more details on how to write custom templates,
-including which filenames to use to customize which parts of the Datasette UI.
.. _plugins_configuration:
@@ -288,47 +166,3 @@ If you are publishing your data using the :ref:`datasette publish `
--install=datasette-auth-github \
--plugin-secret datasette-auth-github client_id your_client_id \
--plugin-secret datasette-auth-github client_secret your_client_secret
-
-.. _plugins_plugin_config:
-
-Writing plugins that accept configuration
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-When you are writing plugins, you can access plugin configuration like this using the ``datasette.plugin_config()`` method. If you know you need plugin configuration for a specific table, you can access it like this::
-
- plugin_config = datasette.plugin_config(
- "datasette-cluster-map", database="sf-trees", table="Street_Tree_List"
- )
-
-This will return the ``{"latitude_column": "lat", "longitude_column": "lng"}`` in the above example.
-
-If it cannot find the requested configuration at the table layer, it will fall back to the database layer and then the root layer. For example, a user may have set the plugin configuration option like so::
-
- {
- "databases: {
- "sf-trees": {
- "plugins": {
- "datasette-cluster-map": {
- "latitude_column": "xlat",
- "longitude_column": "xlng"
- }
- }
- }
- }
- }
-
-In this case, the above code would return that configuration for ANY table within the ``sf-trees`` database.
-
-The plugin configuration could also be set at the top level of ``metadata.json``::
-
- {
- "title": "This is the top-level title in metadata.json",
- "plugins": {
- "datasette-cluster-map": {
- "latitude_column": "xlat",
- "longitude_column": "xlng"
- }
- }
- }
-
-Now that ``datasette-cluster-map`` plugin configuration will apply to every table in every database.
diff --git a/docs/writing_plugins.rst b/docs/writing_plugins.rst
new file mode 100644
index 00000000..d8ede5f3
--- /dev/null
+++ b/docs/writing_plugins.rst
@@ -0,0 +1,156 @@
+.. _writing_plugins:
+
+Writing plugins
+===============
+
+You can write one-off plugins that apply to just one Datasette instance, or you can write plugins which can be installed using ``pip`` and can be shipped to the Python Package Index (`PyPI `__) for other people to install.
+
+.. _plugins_writing_one_off:
+
+Writing one-off plugins
+-----------------------
+
+The easiest way to write a plugin is to create a ``my_plugin.py`` file and drop it into your ``plugins/`` directory. Here is an example plugin, which adds a new custom SQL function called ``hello_world()`` which takes no arguments and returns the string ``Hello world!``.
+
+.. code-block:: python
+
+ from datasette import hookimpl
+
+ @hookimpl
+ def prepare_connection(conn):
+ conn.create_function('hello_world', 0, lambda: 'Hello world!')
+
+If you save this in ``plugins/my_plugin.py`` you can then start Datasette like this::
+
+ datasette serve mydb.db --plugins-dir=plugins/
+
+Now you can navigate to http://localhost:8001/mydb and run this SQL::
+
+ select hello_world();
+
+To see the output of your plugin.
+
+Packaging a plugin
+------------------
+
+Plugins can be packaged using Python setuptools. You can see an example of a packaged plugin at https://github.com/simonw/datasette-plugin-demos
+
+The example consists of two files: a ``setup.py`` file that defines the plugin:
+
+.. code-block:: python
+
+ from setuptools import setup
+
+ VERSION = '0.1'
+
+ setup(
+ name='datasette-plugin-demos',
+ description='Examples of plugins for Datasette',
+ author='Simon Willison',
+ url='https://github.com/simonw/datasette-plugin-demos',
+ license='Apache License, Version 2.0',
+ version=VERSION,
+ py_modules=['datasette_plugin_demos'],
+ entry_points={
+ 'datasette': [
+ 'plugin_demos = datasette_plugin_demos'
+ ]
+ },
+ install_requires=['datasette']
+ )
+
+And a Python module file, ``datasette_plugin_demos.py``, that implements the plugin:
+
+.. code-block:: python
+
+ from datasette import hookimpl
+ import random
+
+
+ @hookimpl
+ def prepare_jinja2_environment(env):
+ env.filters['uppercase'] = lambda u: u.upper()
+
+
+ @hookimpl
+ def prepare_connection(conn):
+ conn.create_function('random_integer', 2, random.randint)
+
+
+Having built a plugin in this way you can turn it into an installable package using the following command::
+
+ python3 setup.py sdist
+
+This will create a ``.tar.gz`` file in the ``dist/`` directory.
+
+You can then install your new plugin into a Datasette virtual environment or Docker container using ``pip``::
+
+ pip install datasette-plugin-demos-0.1.tar.gz
+
+To learn how to upload your plugin to `PyPI `_ for use by other people, read the PyPA guide to `Packaging and distributing projects `_.
+
+Static assets
+-------------
+
+If your plugin has a ``static/`` directory, Datasette will automatically configure itself to serve those static assets from the following path::
+
+ /-/static-plugins/NAME_OF_PLUGIN_PACKAGE/yourfile.js
+
+See `the datasette-plugin-demos repository `_ for an example of how to create a package that includes a static folder.
+
+Custom templates
+----------------
+
+If your plugin has a ``templates/`` directory, Datasette will attempt to load templates from that directory before it uses its own default templates.
+
+The priority order for template loading is:
+
+* templates from the ``--template-dir`` argument, if specified
+* templates from the ``templates/`` directory in any installed plugins
+* default templates that ship with Datasette
+
+See :ref:`customization` for more details on how to write custom templates, including which filenames to use to customize which parts of the Datasette UI.
+
+.. _plugins_plugin_config:
+
+Writing plugins that accept configuration
+-----------------------------------------
+
+When you are writing plugins, you can access plugin configuration like this using the ``datasette plugin_config()`` method. If you know you need plugin configuration for a specific table, you can access it like this::
+
+ plugin_config = datasette.plugin_config(
+ "datasette-cluster-map", database="sf-trees", table="Street_Tree_List"
+ )
+
+This will return the ``{"latitude_column": "lat", "longitude_column": "lng"}`` in the above example.
+
+If it cannot find the requested configuration at the table layer, it will fall back to the database layer and then the root layer. For example, a user may have set the plugin configuration option like so::
+
+ {
+ "databases: {
+ "sf-trees": {
+ "plugins": {
+ "datasette-cluster-map": {
+ "latitude_column": "xlat",
+ "longitude_column": "xlng"
+ }
+ }
+ }
+ }
+ }
+
+In this case, the above code would return that configuration for ANY table within the ``sf-trees`` database.
+
+The plugin configuration could also be set at the top level of ``metadata.json``::
+
+ {
+ "title": "This is the top-level title in metadata.json",
+ "plugins": {
+ "datasette-cluster-map": {
+ "latitude_column": "xlat",
+ "longitude_column": "xlng"
+ }
+ }
+ }
+
+Now that ``datasette-cluster-map`` plugin configuration will apply to every table in every database.