diff --git a/convert-all-policies.sh b/convert-all-policies.sh
new file mode 100755
index 000000000..5e7fec545
--- /dev/null
+++ b/convert-all-policies.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+./convert-policy.py ../sg/SG\ Policy.md policy-template.html ../sg/policy-link-mapping.txt > whatwg.org/sg-policy
+./convert-policy.py ../sg/Workstream\ Policy.md policy-template.html ../sg/policy-link-mapping.txt > whatwg.org/workstream-policy
+./convert-policy.py ../sg/SG\ Agreement.md policy-template.html ../sg/policy-link-mapping.txt > whatwg.org/sg-agreement
+./convert-policy.py ../sg/Principles.md policy-template.html ../sg/policy-link-mapping.txt > whatwg.org/principles
+./convert-policy.py ../sg/IPR\ Policy.md policy-template.html ../sg/policy-link-mapping.txt > whatwg.org/ipr-policy
+
diff --git a/convert-policy.py b/convert-policy.py
new file mode 100755
index 000000000..023a22dc3
--- /dev/null
+++ b/convert-policy.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+import argparse
+import markdown
+from mdx_partial_gfm import PartialGithubFlavoredMarkdownExtension
+import re
+import string
+
+
+def parse_arguments():
+ parser = argparse.ArgumentParser(description="Convert a Markdown-formatted WHATWG policy to HTML suitable for publishing on whatwg.org")
+ parser.add_argument('policy', metavar='POLICY', type=argparse.FileType(), help="The markdown policy document to convert")
+ parser.add_argument('template', metavar='TEMPLATE', type=argparse.FileType(), help="A template to add appropriate boilerplate to the policy")
+ parser.add_argument('link_mapping', metavar='MAPPING', type=argparse.FileType(), help="File defining link mappings")
+
+ args = parser.parse_args()
+
+ return (args.policy, args.template, args.link_mapping)
+
+
+def lower_headers(policy_markdown):
+ return re.sub(r'^#', '##', policy_markdown, flags=re.MULTILINE)
+
+
+def apply_link_mapping(policy_markdown, link_mapping):
+ mapping_pairs = [line.split('=',1) for line in link_mapping.split("\n") if len(line) > 0]
+ for mapping in mapping_pairs:
+ policy_markdown = policy_markdown.replace(mapping[0], mapping[1])
+
+ return policy_markdown
+
+
+def ascii_lower(str):
+ return "".join([char.lower() if char >= 'A' and char <= 'Z' else char for char in str])
+
+
+def header_text_to_id(header_text):
+ punctuation_regexp = r'[^\w\- ]'
+ header_id = ascii_lower(header_text)
+ header_id = re.sub(punctuation_regexp, '', header_id)
+ header_id = re.sub(' ', '-', header_id)
+
+ return header_id
+
+
+def add_one_header_anchor(line):
+ header_text = line.lstrip('#')
+ header_level = len(line) - len(header_text)
+
+ if header_level <= 2:
+ return line
+
+ header_text = header_text.lstrip(' ')
+ header_id = header_text_to_id(header_text)
+
+ return '