diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst
index ff0aa8af611db..967d1fe3369f0 100644
--- a/doc/source/contributing.rst
+++ b/doc/source/contributing.rst
@@ -1088,5 +1088,4 @@ The branch will still exist on GitHub, so to delete it there do::
 
     git push origin --delete shiny-new-feature
 
-
 .. _Gitter: https://gitter.im/pydata/pandas
diff --git a/doc/source/contributing_docstring.rst b/doc/source/contributing_docstring.rst
index c210bb7050fb8..f80bfd9253764 100644
--- a/doc/source/contributing_docstring.rst
+++ b/doc/source/contributing_docstring.rst
@@ -82,6 +82,9 @@ about reStructuredText can be found in:
 - `Quick reStructuredText reference <http://docutils.sourceforge.net/docs/user/rst/quickref.html>`_
 - `Full reStructuredText specification <http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html>`_
 
+Pandas has some helpers for sharing docstrings between related classes, see
+:ref:`docstring.sharing`.
+
 The rest of this document will summarize all the above guides, and will
 provide additional convention specific to the pandas project.
 
@@ -916,3 +919,79 @@ plot will be generated automatically when building the documentation.
                 >>> s.plot()
             """
             pass
+
+.. _docstring.sharing:
+
+Sharing Docstrings
+------------------
+
+Pandas has a system for sharing docstrings, with slight variations, between
+classes. This helps us keep docstrings consistent, while keeping things clear
+for the user reading. It comes at the cost of some complexity when writing.
+
+Each shared docstring will have a base template with variables, like
+``%(klass)s``. The variables filled in later on using the ``Substitution``
+decorator. Finally, docstrings can be appended to with the ``Appender``
+decorator.
+
+In this example, we'll create a parent docstring normally (this is like
+``pandas.core.generic.NDFrame``. Then we'll have two children (like
+``pandas.core.series.Series`` and ``pandas.core.frame.DataFrame``). We'll
+substitute the children's class names in this docstring.
+
+.. code-block:: python
+
+   class Parent:
+       def my_function(self):
+           """Apply my function to %(klass)s."""
+           ...
+
+   class ChildA(Parent):
+       @Substitution(klass="ChildA")
+       @Appender(Parent.my_function.__doc__)
+       def my_function(self):
+           ...
+
+   class ChildB(Parent):
+       @Substitution(klass="ChildB")
+       @Appender(Parent.my_function.__doc__)
+       def my_function(self):
+           ...
+
+The resulting docstrings are
+
+.. code-block:: python
+
+   >>> print(Parent.my_function.__doc__)
+   Apply my function to %(klass)s.
+   >>> print(ChildA.my_function.__doc__)
+   Apply my function to ChildA.
+   >>> print(ChildB.my_function.__doc__)
+   Apply my function to ChildB.
+
+Notice two things:
+
+1. We "append" the parent docstring to the children docstrings, which are
+   initially empty.
+2. Python decorators are applied inside out. So the order is Append then
+   Substitution, even though Substitution comes first in the file.
+
+Our files will often contain a module-level ``_shared_doc_kwargs`` with some
+common substitution values (things like ``klass``, ``axes``, etc).
+
+You can substitute and append in one shot with something like
+
+.. code-block:: python
+
+   @Appender(template % _shared_doc_kwargs)
+   def my_function(self):
+       ...
+
+where ``template`` may come from a module-level ``_shared_docs`` dictionary
+mapping function names to docstrings. Wherever possible, we prefer using
+``Appender`` and ``Substitution``, since the docstring-writing processes is
+slightly closer to normal.
+
+See ``pandas.core.generic.NDFrame.fillna`` for an example template, and
+``pandas.core.series.Series.fillna`` and ``pandas.core.generic.frame.fillna``
+for the filled versions.
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
index d2617305d220a..ace975385ce32 100644
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -3696,7 +3696,8 @@ def rename(self, *args, **kwargs):
         kwargs.pop('mapper', None)
         return super(DataFrame, self).rename(**kwargs)
 
-    @Appender(_shared_docs['fillna'] % _shared_doc_kwargs)
+    @Substitution(**_shared_doc_kwargs)
+    @Appender(NDFrame.fillna.__doc__)
     def fillna(self, value=None, method=None, axis=None, inplace=False,
                limit=None, downcast=None, **kwargs):
         return super(DataFrame,
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
index 80885fd9ef139..f1fa43818ce64 100644
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -5252,7 +5252,9 @@ def infer_objects(self):
     # ----------------------------------------------------------------------
     # Filling NA's
 
-    _shared_docs['fillna'] = ("""
+    def fillna(self, value=None, method=None, axis=None, inplace=False,
+               limit=None, downcast=None):
+        """
         Fill NA/NaN values using the specified method
 
         Parameters
@@ -5343,11 +5345,7 @@ def infer_objects(self):
         1   3.0 4.0 NaN 1
         2   NaN 1.0 NaN 5
         3   NaN 3.0 NaN 4
-        """)
-
-    @Appender(_shared_docs['fillna'] % _shared_doc_kwargs)
-    def fillna(self, value=None, method=None, axis=None, inplace=False,
-               limit=None, downcast=None):
+        """
         inplace = validate_bool_kwarg(inplace, 'inplace')
         value, method = validate_fillna_kwargs(value, method)
 
diff --git a/pandas/core/panel.py b/pandas/core/panel.py
index 5bb4b72a0562d..7c087ac7deafc 100644
--- a/pandas/core/panel.py
+++ b/pandas/core/panel.py
@@ -31,7 +31,7 @@
                                    create_block_manager_from_blocks)
 from pandas.core.series import Series
 from pandas.core.reshape.util import cartesian_product
-from pandas.util._decorators import Appender
+from pandas.util._decorators import Appender, Substitution
 from pandas.util._validators import validate_axis_style_args
 
 _shared_doc_kwargs = dict(
@@ -1254,7 +1254,8 @@ def transpose(self, *args, **kwargs):
 
         return super(Panel, self).transpose(*axes, **kwargs)
 
-    @Appender(_shared_docs['fillna'] % _shared_doc_kwargs)
+    @Substitution(**_shared_doc_kwargs)
+    @Appender(NDFrame.fillna.__doc__)
     def fillna(self, value=None, method=None, axis=None, inplace=False,
                limit=None, downcast=None, **kwargs):
         return super(Panel, self).fillna(value=value, method=method, axis=axis,
diff --git a/pandas/core/series.py b/pandas/core/series.py
index da598259d272d..98ba53bdf5176 100644
--- a/pandas/core/series.py
+++ b/pandas/core/series.py
@@ -3341,7 +3341,8 @@ def drop(self, labels=None, axis=0, index=None, columns=None,
                                         columns=columns, level=level,
                                         inplace=inplace, errors=errors)
 
-    @Appender(generic._shared_docs['fillna'] % _shared_doc_kwargs)
+    @Substitution(**_shared_doc_kwargs)
+    @Appender(generic.NDFrame.fillna.__doc__)
     def fillna(self, value=None, method=None, axis=None, inplace=False,
                limit=None, downcast=None, **kwargs):
         return super(Series, self).fillna(value=value, method=method,