diff --git a/parser.py b/parser.py
index 9e7285a5a6e7c76bfe4beabc654fb348b1a19e01..12703d11e7233538666236df45a5577105203bb5 100644
--- a/parser.py
+++ b/parser.py
@@ -4,6 +4,7 @@ from collections import OrderedDict
 from enum import Enum
 
 from shared import escape_tex
+from utils import footnote_hash
 
 import config
 
@@ -223,6 +224,8 @@ class Tag:
                         r"\textit{{({})}}".format(self.decision.get_categories_str())
                     )
                 return " ".join(parts)
+            elif self.name == "footnote":
+                return r"\footnote{{{}}}".format(self.values[0])
             return r"\textbf{{{}:}} {}".format(escape_tex(self.name.capitalize()), escape_tex(";".join(self.values)))
         elif render_type == RenderType.plaintext:
             if self.name == "url":
@@ -231,6 +234,8 @@ class Tag:
                 if not show_private:
                     return ""
                 return self.values[0]
+            elif self.name == "footnote":
+                return "[^]({})".format(self.values[0])
             return "{}: {}".format(self.name.capitalize(), ";".join(self.values))
         elif render_type == RenderType.wikitext:
             if self.name == "url":
@@ -239,6 +244,8 @@ class Tag:
                 if not show_private:
                     return ""
                 return self.todo.render_wikitext(current_protocol=protocol)
+            elif self.name == "footnote":
+                return "<ref>{}</ref>".format(self.values[0])
             return "'''{}:''' {}".format(self.name.capitalize(), ";".join(self.values))
         elif render_type == RenderType.html:
             if self.name == "url":
@@ -259,6 +266,9 @@ class Tag:
                     return " ".join(parts)
                 else:
                     return "<b>Beschluss:</b> {}".format(self.values[0])
+            elif self.name == "footnote":
+                return '<sup id="#fnref{0}"><a href="#fn{0}">Fn</a></sup>'.format(
+                    footnote_hash(self.values[0]))
         else:
             raise _not_implemented(self, render_type)
 
@@ -284,7 +294,7 @@ class Tag:
     # v3: also match [] without semicolons inbetween, as there is not other use for that
     PATTERN = r"\[(?<content>[^\]]*)\]"
 
-    KNOWN_TAGS = ["todo", "url", "beschluss"]
+    KNOWN_TAGS = ["todo", "url", "beschluss", "footnote"]
 
 
 class Empty(Element):
@@ -495,6 +505,16 @@ class Fork(Element):
         else:
             return 1
 
+    def get_visible_elements(self, show_private, elements=None):
+        if elements is None:
+            elements = set()
+        if show_private or not self.test_private(self.name):
+            for child in self.children:
+                elements.add(child)
+                if isinstance(child, Fork):
+                    child.get_visible_elements(show_private, elements)
+        return elements
+
     @staticmethod
     def create_root():
         return Fork(None, None, None, 0)
diff --git a/server.py b/server.py
index 0ca5a9ab242028b26c8ef8374206e0e2686f20cc..cfb636c8de09aeb259004e335c28c0cef51fd946 100755
--- a/server.py
+++ b/server.py
@@ -21,7 +21,7 @@ import mimetypes
 
 import config
 from shared import db, date_filter, datetime_filter, date_filter_long, date_filter_short, time_filter, time_filter_short, user_manager, security_manager, current_user, check_login, login_required, group_required, class_filter, needs_date_test, todostate_name_filter, code_filter, indent_tab_filter
-from utils import is_past, mail_manager, url_manager, get_first_unused_int, set_etherpad_text, get_etherpad_text, split_terms, optional_int_arg, fancy_join
+from utils import is_past, mail_manager, url_manager, get_first_unused_int, set_etherpad_text, get_etherpad_text, split_terms, optional_int_arg, fancy_join, footnote_hash
 from decorators import db_lookup, require_public_view_right, require_private_view_right, require_modify_right, require_publish_right, require_admin_right
 from models.database import ProtocolType, Protocol, DefaultTOP, TOP, LocalTOP, Document, Todo, Decision, MeetingReminder, Error, TodoMail, DecisionDocument, TodoState, Meta, DefaultMeta, DecisionCategory, Like
 from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, generate_protocol_form, TopForm, LocalTopForm, SearchForm, DecisionSearchForm, ProtocolSearchForm, TodoSearchForm, NewProtocolFileUploadForm, NewTodoForm, TodoForm, TodoMailForm, DefaultMetaForm, MetaForm, MergeTodosForm, DecisionCategoryForm, DocumentEditForm
@@ -60,6 +60,7 @@ app.jinja_env.filters["todo_get_name"] = todostate_name_filter
 app.jinja_env.filters["code"] = code_filter
 app.jinja_env.filters["indent_tab"] = indent_tab_filter
 app.jinja_env.filters["fancy_join"] = fancy_join
+app.jinja_env.filters["footnote_hash"] = footnote_hash
 app.jinja_env.tests["auth_valid"] = security_manager.check_user
 app.jinja_env.tests["needs_date"] = needs_date_test
 
diff --git a/tasks.py b/tasks.py
index ef4d1ba97acfa54b26137ffe8367669d25f2404b..f39330901d3dbfa0d823efa8803a93d0f3983c91 100644
--- a/tasks.py
+++ b/tasks.py
@@ -12,7 +12,7 @@ from models.database import Document, Protocol, Error, Todo, Decision, TOP, Defa
 from models.errors import DateNotMatchingException
 from server import celery, app
 from shared import db, escape_tex, unhyphen, date_filter, datetime_filter, date_filter_long, date_filter_short, time_filter, class_filter, KNOWN_KEYS
-from utils import mail_manager, url_manager, encode_kwargs, decode_kwargs, add_line_numbers, set_etherpad_text, get_etherpad_text
+from utils import mail_manager, url_manager, encode_kwargs, decode_kwargs, add_line_numbers, set_etherpad_text, get_etherpad_text, footnote_hash
 from parser import parse, ParserException, Element, Content, Text, Tag, Remark, Fork, RenderType
 from wiki import WikiClient, WikiException
 from calendarpush import Client as CalendarClient, CalendarException
@@ -168,6 +168,8 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
         return
     # tags 
     tags = tree.get_tags()
+    elements = tree.get_visible_elements(show_private=True)
+    public_elements = tree.get_visible_elements(show_private=False)
     for tag in tags:
         if tag.name not in Tag.KNOWN_TAGS:
             error = protocol.create_error("Parsing", "Invalid tag",
@@ -304,11 +306,20 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
         db.session.commit()
         todo_tag.todo = todo
     # Decisions
+    decision_tags = [tag for tag in tags if tag.name == "beschluss"]
+    for decision_tag in decision_tags:
+        if decision_tag not in public_elements:
+            error = protocol.create_error("Parsing", "Decision in private context.",
+                "The decision in line {} is in a private context, but decisions are "
+                "and have to be public. Please move it to a public spot.".format(
+                decision_tag.linenumber))
+            db.session.add(error)
+            db.session.commit()
+            return
     old_decisions = list(protocol.decisions)
     for decision in old_decisions:
         protocol.decisions.remove(decision)
     db.session.commit()
-    decision_tags = [tag for tag in tags if tag.name == "beschluss"]
     decisions_to_render = []
     for decision_tag in decision_tags:
         if len(decision_tag.values) == 0:
@@ -352,9 +363,15 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
         decision_top = decision_tag.fork.get_top()
         decision_content = texenv.get_template("decision.tex").render(
             render_type=RenderType.latex, decision=decision,
-            protocol=protocol, top=decision_top, show_private=False)
+            protocol=protocol, top=decision_top, show_private=True)
         maxdepth = decision_top.get_maxdepth()
         compile_decision(decision_content, decision, maxdepth=maxdepth)
+
+    # Footnotes
+    footnote_tags = [tag for tag in tags if tag.name == "footnote"]
+    public_footnote_tags = [tag for tag in footnote_tags if tag in public_elements]
+
+    # TOPs
     old_tops = list(protocol.tops)
     for top in old_tops:
         protocol.tops.remove(top)
@@ -365,31 +382,47 @@ def parse_protocol_async_inner(protocol, encoded_kwargs):
         db.session.add(top)
     db.session.commit()
 
-    render_kwargs = {
+    # render
+    private_render_kwargs = {
         "protocol": protocol,
-        "tree": tree
+        "tree": tree,
+        "footnotes": footnote_tags,
     }
+    public_render_kwargs = copy(private_render_kwargs)
+    public_render_kwargs["footnotes"] = public_footnote_tags
+    render_kwargs = {True: private_render_kwargs, False: public_render_kwargs}
+    
     maxdepth = tree.get_maxdepth()
     privacy_states = [False]
-    content_private = render_template("protocol.txt", render_type=RenderType.plaintext, show_private=True, **render_kwargs)
-    content_public = render_template("protocol.txt", render_type=RenderType.plaintext, show_private=False, **render_kwargs)
+    content_private = render_template("protocol.txt", render_type=RenderType.plaintext, show_private=True, **private_render_kwargs)
+    content_public = render_template("protocol.txt", render_type=RenderType.plaintext, show_private=False, **public_render_kwargs)
     if content_private != content_public:
         privacy_states.append(True)
     protocol.content_private = content_private
     protocol.content_public = content_public
     protocol.content_html_private = render_template("protocol.html",
-        render_type=RenderType.html, show_private=True, **render_kwargs)
+        render_type=RenderType.html, show_private=True, **private_render_kwargs)
     protocol.content_html_public = render_template("protocol.html",
-        render_type=RenderType.html, show_private=False, **render_kwargs)
+        render_type=RenderType.html, show_private=False, **public_render_kwargs)
 
     for show_private in privacy_states:
-        latex_source = texenv.get_template("protocol.tex").render(render_type=RenderType.latex, show_private=show_private, **render_kwargs)
+        latex_source = texenv.get_template("protocol.tex").render(
+            render_type=RenderType.latex,
+            show_private=show_private,
+            **render_kwargs[show_private])
         compile(latex_source, protocol, show_private=show_private, maxdepth=maxdepth)
 
     if protocol.protocoltype.use_wiki:
-        wiki_source = wikienv.get_template("protocol.wiki").render(render_type=RenderType.wikitext, show_private=not protocol.protocoltype.wiki_only_public, **render_kwargs).replace("\n\n\n", "\n\n")
-        wiki_infobox_source = wikienv.get_template("infobox.wiki").render(protocoltype=protocol.protocoltype)
-        push_to_wiki(protocol, wiki_source, wiki_infobox_source, "Automatisch generiert vom Protokollsystem 3.0")
+        show_private = not protocol.protocoltype.wiki_only_public
+        wiki_source = wikienv.get_template("protocol.wiki").render(
+            render_type=RenderType.wikitext,
+            show_private=show_private,
+            **render_kwargs[show_private]
+        ).replace("\n\n\n", "\n\n")
+        wiki_infobox_source = wikienv.get_template("infobox.wiki").render(
+            protocoltype=protocol.protocoltype)
+        push_to_wiki(protocol, wiki_source, wiki_infobox_source,
+            "Automatisch generiert vom Protokollsystem 3.0")
     protocol.done = True
     db.session.commit()
 
diff --git a/templates/protocol.html b/templates/protocol.html
index 08a54d2465edd79707d0a47aa5ab8c07dd4ab8a6..391bebadffe97ac1cd36d2150d77611a6f7fb9a7 100644
--- a/templates/protocol.html
+++ b/templates/protocol.html
@@ -4,3 +4,10 @@
 
 {% endif %}
 {% endfor %}
+
+{% if footnotes|length > 0 %}
+    <hr />
+    {% for footnote in footnotes %}
+        <p><sup id="fn{{footnote.values[0]|footnote_hash}}">{{loop.index}}. {{footnote.values[0]}} <a href="#fnref{{footnote.values[0]|footnote_hash}}">↩</a></sup></p>
+    {% endfor %}
+{% endif %}
diff --git a/templates/protocol.wiki b/templates/protocol.wiki
index bc5e061b940be6bc59ea8b703ab627a3d69a5d47..2ab67c9daded0ab5c60706d9ba6aff08d2f37d3f 100644
--- a/templates/protocol.wiki
+++ b/templates/protocol.wiki
@@ -28,4 +28,8 @@
     <env> endif </env>
 <env> endfor </env>
 
+<env> if footnotes|length > 0 </env>
+    <references />
+<env> endif </env>
+
 [[Kategorie:<var>protocol.protocoltype.wiki_category</var>]]
diff --git a/utils.py b/utils.py
index 1ac0c4d4309c7011440689719eded63832e72cbc..dfc3fa16cddd7bc6f815caf5701c9b04e63439df 100644
--- a/utils.py
+++ b/utils.py
@@ -200,3 +200,6 @@ def fancy_join(values, sep1=" und ", sep2=", "):
     last = values[-1]
     start = values[:-1]
     return "{}{}{}".format(sep2.join(start), sep1, last)
+
+def footnote_hash(text, length=5):
+    return str(sum(ord(c) * i for i, c in enumerate(text)) % 10**length)