diff --git a/hiboo/models.py b/hiboo/models.py
index 10e90f84281a90eac858af7c5b8495afcbe209b7..eb40676c0574af2fb55828a60a1c935a2501dddd 100644
--- a/hiboo/models.py
+++ b/hiboo/models.py
@@ -158,7 +158,7 @@ class Service(db.Model):
     description = db.Column(db.String())
     policy = db.Column(db.String(255))
     max_profiles = db.Column(db.Integer(), nullable=False, default=1)
-    profile_regex = db.Column(db.String(255))
+    profile_format = db.Column(db.String(255))
     same_username = db.Column(db.Boolean(), nullable=False, default=False)
     config = db.Column(mutable.MutableDict.as_mutable(JSONEncoded))
 
diff --git a/hiboo/profile/__init__.py b/hiboo/profile/__init__.py
index de935134e9b3105b89e0d41f4e46d09828c387b3..61168d9068dbb469caac289c6005858acd2ffd22 100644
--- a/hiboo/profile/__init__.py
+++ b/hiboo/profile/__init__.py
@@ -5,6 +5,12 @@ blueprint = flask.Blueprint("profile", __name__, template_folder="templates")
 
 import flask_login
 from hiboo import models, utils
+from hiboo.profile import format
+
+
+formats = format.ProfileFormat.registry
+
+
 from hiboo.profile import login, admin, forms, cli
 
 
diff --git a/hiboo/profile/format.py b/hiboo/profile/format.py
new file mode 100644
index 0000000000000000000000000000000000000000..c53903169e4528630eb48061e5868014af93d090
--- /dev/null
+++ b/hiboo/profile/format.py
@@ -0,0 +1,79 @@
+from wtforms import validators
+from flask_babel import lazy_gettext as _
+
+import string
+
+
+class ProfileFormat(object):
+
+    registry = {}
+    regex = ".*"
+    allowed = string.printable
+    transform = str
+    message = ""
+    min_length = 3
+    max_length = 30
+
+    @classmethod
+    def register(cls, *format_ids):
+        """ Class decorator 
+        """
+        def register_function(format):
+            for format_id in format_ids:
+                cls.registry[format_id] = format
+            return format
+        return register_function
+
+    @classmethod
+    def validators(cls):
+        """ Return a username validator for wtforms
+        """
+        return [
+            validators.DataRequired(),
+            validators.Length(
+                min=cls.min_length, max=cls.max_length,
+                message=_("must be at least {} and at most {} characters long".format(cls.min_length, cls.max_length))
+            ),
+            validators.Regexp(
+                "^{}$".format(cls.regex),
+                message=_("must comprise only of ") + cls.message
+            )
+        ]
+
+    @classmethod
+    def coalesce(cls, username):
+        """ Transform a username into its valid form
+        """
+        return filter(cls.allowed.__contains__, cls.transform(username))
+
+    @classmethod
+    def alternatives(cls, username):
+        """ Generate alternate usernames for a given username
+        """
+        index = 1
+        while True:
+            yield username + "_" + str(index)
+
+
+register = ProfileFormat.register
+
+
+@register("lowercase", "", None)
+class LowercaseAlphanumPunct(ProfileFormat):
+    """ Lowercase username, including digits and very basic punctuation
+    """
+
+    regex = "[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?"
+    allowed = string.digits + string.ascii_lowercase + ".-_"
+    transform = str.lower
+    message = _("lowercase letters, digits, dots, dashes, and underscores")
+
+
+@register("alnum")
+class AlphanumPunct(ProfileFormat):
+    """ Alphanum username, including some very basic punctuation
+    """
+
+    regex = "[a-zA-Z0-9_]+([a-zA-Z0-9_\.-]+[a-zA-Z0-9_]+)?"
+    allowed = string.digits + string.ascii_letters + ".-_"
+    message = _("letters, digits, dots, dashes and underscores")
diff --git a/hiboo/profile/login.py b/hiboo/profile/login.py
index 0859fe942babdb9da2fa8b7c75578d3e511caaab..077417319b7f961a4c5025b5a9f3fd0ac1e2f5f9 100644
--- a/hiboo/profile/login.py
+++ b/hiboo/profile/login.py
@@ -1,4 +1,4 @@
-from hiboo.profile import blueprint, forms
+from hiboo.profile import blueprint, forms, formats
 from hiboo import models, utils, security
 from hiboo import user as hiboo_user
 from passlib import context, hash
@@ -50,10 +50,7 @@ def create(service_uuid, create_for=False):
             status = models.Profile.REQUEST
     # Actually display the form
     form = forms.ProfileForm()
-    if service.profile_regex:
-        form.username.validators.append(
-            validators.Regexp("^{}$".format(service.profile_regex)),
-        )
+    form.username.validators = formats[service.profile_format].validators()
     if service.same_username:
         form.username.data = user.username
         form.username.render_kw = {"readonly": True}
diff --git a/hiboo/service/forms.py b/hiboo/service/forms.py
index ee17f4a076eacc46e7da8ef458bda7aae91c77e4..ed087baac46740ef054fe46a22a6d945e6a4943c 100644
--- a/hiboo/service/forms.py
+++ b/hiboo/service/forms.py
@@ -1,7 +1,7 @@
 from wtforms import validators, fields, widgets
 from flask_babel import lazy_gettext as _
 
-from hiboo import models, application
+from hiboo import models, application, profile
 
 import flask_wtf
 
@@ -14,6 +14,11 @@ class ServiceForm(flask_wtf.FlaskForm):
         choices=list(models.Service.POLICIES.items()))
     max_profiles = fields.IntegerField(_('Maximum profile count'),
         [validators.NumberRange(1, 1000)])
-    profile_regex = fields.StringField(_('Profile username regex'))
+    profile_format = fields.SelectField(_('Profile username format'),
+        choices=(
+            [("", _("Default ({})".format(profile.formats[None].message)))] +
+            [(name, format.message.capitalize()) for name, format in profile.formats.items() if name]
+        )
+    )
     same_username = fields.BooleanField(_('Disable per-profile username'))
     submit = fields.SubmitField(_('Submit'))
diff --git a/migrations/versions/059b2c50d7e1_regex_to_profile_format.py b/migrations/versions/059b2c50d7e1_regex_to_profile_format.py
new file mode 100644
index 0000000000000000000000000000000000000000..57c5f26724cb0a1db834c7cafacddaa445929a8c
--- /dev/null
+++ b/migrations/versions/059b2c50d7e1_regex_to_profile_format.py
@@ -0,0 +1,28 @@
+""" Replace profile regex by format
+
+Revision ID: 059b2c50d7e1
+Revises: c5109b93fc0f
+Create Date: 2020-05-11 17:22:47.462998
+"""
+
+from alembic import op
+import sqlalchemy as sa
+import hiboo
+
+
+revision = '059b2c50d7e1'
+down_revision = 'c5109b93fc0f'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    with op.batch_alter_table('service') as batch_op:
+        batch_op.add_column(sa.Column('profile_format', sa.String(length=255), nullable=True))
+        batch_op.drop_column('profile_regex')
+
+
+def downgrade():
+    with op.batch_alter_table('service') as batch_op:
+        batch_op.add_column(sa.Column('profile_regex', sa.VARCHAR(length=255), nullable=True))
+        batch_op.drop_column('profile_format')