diff --git a/gracedb/events/migrations/0022_restrict_tag_name_chars.py b/gracedb/events/migrations/0022_restrict_tag_name_chars.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6073f009474b26cc1290df3ac4e1a53479b0844
--- /dev/null
+++ b/gracedb/events/migrations/0022_restrict_tag_name_chars.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2018-06-07 18:57
+from __future__ import unicode_literals
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('events', '0021_emobservation_N_not_editable'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='tag',
+            name='name',
+            field=models.CharField(max_length=100, unique=True, validators=[django.core.validators.RegexValidator(code=b'invalid_tag_name', message=b'Tag names can only include [0-9a-zA-z_-]', regex=b'^[0-9a-zA-Z_\\-]*$')]),
+        ),
+    ]
diff --git a/gracedb/events/models.py b/gracedb/events/models.py
index c116df492d4e48b03efede537501876ebd759dcf..25ee5c16ac3ab529beab2ff7d56368033dfc00d8 100644
--- a/gracedb/events/models.py
+++ b/gracedb/events/models.py
@@ -808,7 +808,14 @@ class Tag(CleanSaveModel):
     track those things?  Doesn't seem like it.
     """
     name = models.CharField(max_length=100, null=False, blank=False,
-        unique=True)
+        unique=True,
+        validators=[
+            models.fields.validators.RegexValidator(
+                regex=r'^[0-9a-zA-Z_\-]*$',
+                message="Tag names can only include [0-9a-zA-z_-]",
+                code="invalid_tag_name",
+            )
+        ])
     displayName = models.CharField(max_length=200, null=True, blank=True)
 
     def __unicode__(self):