diff --git a/activities/models/post.py b/activities/models/post.py
index 642842c..dd82bcf 100644
--- a/activities/models/post.py
+++ b/activities/models/post.py
@@ -499,7 +499,7 @@ class Post(StatorModel):
     @classmethod
     def handle_delete_ap(cls, data):
         """
-        Handles an incoming create request
+        Handles an incoming delete request
         """
         with transaction.atomic():
             # Find our post by ID if we have one
diff --git a/activities/views/posts.py b/activities/views/posts.py
index 268a3fe..8cd91a1 100644
--- a/activities/views/posts.py
+++ b/activities/views/posts.py
@@ -202,6 +202,9 @@ class Compose(FormView):
 
     def get_initial(self):
         initial = super().get_initial()
+        initial[
+            "visibility"
+        ] = self.request.identity.config_identity.default_post_visibility
         if self.reply_to:
             initial["reply_to"] = self.reply_to.pk
             initial["visibility"] = Post.Visibilities.unlisted
diff --git a/activities/views/timelines.py b/activities/views/timelines.py
index b680c8a..4f2a515 100644
--- a/activities/views/timelines.py
+++ b/activities/views/timelines.py
@@ -56,6 +56,7 @@ class Home(FormView):
             author=self.request.identity,
             content=linebreaks_filter(form.cleaned_data["text"]),
             summary=form.cleaned_data.get("content_warning"),
+            visibility=self.request.identity.config_identity.default_post_visibility,
         )
         return redirect(".")
 
diff --git a/core/context.py b/core/context.py
index 1db3436..876c643 100644
--- a/core/context.py
+++ b/core/context.py
@@ -5,7 +5,7 @@ def config_context(request):
     return {
         "config": Config.system,
         "config_identity": (
-            Config.load_identity(request.identity) if request.identity else None
+            request.identity.config_identity if request.identity else None
         ),
         "top_section": request.path.strip("/").split("/")[0],
     }
diff --git a/core/models/config.py b/core/models/config.py
index 0a8d624..f22389b 100644
--- a/core/models/config.py
+++ b/core/models/config.py
@@ -223,3 +223,4 @@ class Config(models.Model):
     class IdentityOptions(pydantic.BaseModel):
 
         toot_mode: bool = False
+        default_post_visibility: int = 1  # Post.Visibilities.unlisted
diff --git a/templates/activities/_menu.html b/templates/activities/_menu.html
index 4b167af..1ebe940 100644
--- a/templates/activities/_menu.html
+++ b/templates/activities/_menu.html
@@ -41,7 +41,7 @@
         {% csrf_token %}
         {{ form.text }}
         {{ form.content_warning }}
-        
+        
         
             CW
             
diff --git a/users/models/identity.py b/users/models/identity.py
index 5d7fd0b..8583487 100644
--- a/users/models/identity.py
+++ b/users/models/identity.py
@@ -1,4 +1,4 @@
-from functools import partial
+from functools import cached_property, partial
 from typing import Dict, Literal, Optional, Tuple
 from urllib.parse import urlparse
 
@@ -9,10 +9,12 @@ from django.db import IntegrityError, models
 from django.template.defaultfilters import linebreaks_filter
 from django.templatetags.static import static
 from django.utils import timezone
+from django.utils.functional import lazy
 
 from core.exceptions import ActorMismatchError
 from core.html import sanitize_post
 from core.ld import canonicalise, media_type_from_filename
+from core.models import Config
 from core.signatures import HttpSignature, RsaKeys
 from core.uploads import upload_namer
 from stator.models import State, StateField, StateGraph, StatorModel
@@ -433,3 +435,17 @@ class Identity(StatorModel):
         self.private_key, self.public_key = RsaKeys.generate_keypair()
         self.public_key_id = self.actor_uri + "#main-key"
         self.save()
+
+    ### Config ###
+
+    @cached_property
+    def config_identity(self) -> Config.IdentityOptions:
+        return Config.load_identity(self)
+
+    def lazy_config_value(self, key: str):
+        """
+        Lazily load a config value for this Identity
+        """
+        if key not in Config.IdentityOptions.__fields__:
+            raise KeyError(f"Undefined IdentityOption for {key}")
+        return lazy(lambda: getattr(self.config_identity, key))
diff --git a/users/views/settings/__init__.py b/users/views/settings/__init__.py
index 65be1c5..f7f4018 100644
--- a/users/views/settings/__init__.py
+++ b/users/views/settings/__init__.py
@@ -1,140 +1,12 @@
-from functools import partial
-from typing import ClassVar, Dict, List
-
-from django import forms
-from django.core.files import File
-from django.shortcuts import redirect
 from django.utils.decorators import method_decorator
-from django.views.generic import FormView, RedirectView
+from django.views.generic import RedirectView
 
-from core.models.config import Config, UploadedImage
 from users.decorators import identity_required
+from users.views.settings.interface import InterfacePage, SettingsPage  # noqa
 from users.views.settings.profile import ProfilePage  # noqa
+from users.views.settings.security import SecurityPage  # noqa
 
 
 @method_decorator(identity_required, name="dispatch")
 class SettingsRoot(RedirectView):
     pattern_name = "settings_profile"
-
-
-@method_decorator(identity_required, name="dispatch")
-class SettingsPage(FormView):
-    """
-    Shows a settings page dynamically created from our settings layout
-    at the bottom of the page. Don't add this to a URL directly - subclass!
-    """
-
-    options_class = Config.IdentityOptions
-    template_name = "settings/settings.html"
-    section: ClassVar[str]
-    options: Dict[str, Dict[str, str]]
-    layout: Dict[str, List[str]]
-
-    def get_form_class(self):
-        # Create the fields dict from the config object
-        fields = {}
-        for key, details in self.options.items():
-            config_field = self.options_class.__fields__[key]
-            if config_field.type_ is bool:
-                form_field = partial(
-                    forms.BooleanField,
-                    widget=forms.Select(
-                        choices=[(True, "Enabled"), (False, "Disabled")]
-                    ),
-                )
-            elif config_field.type_ is UploadedImage:
-                form_field = forms.ImageField
-            elif config_field.type_ is str:
-                if details.get("display") == "textarea":
-                    form_field = partial(
-                        forms.CharField,
-                        widget=forms.Textarea,
-                    )
-                else:
-                    form_field = forms.CharField
-            elif config_field.type_ is int:
-                form_field = forms.IntegerField
-            else:
-                raise ValueError(f"Cannot render settings type {config_field.type_}")
-            fields[key] = form_field(
-                label=details["title"],
-                help_text=details.get("help_text", ""),
-                required=details.get("required", False),
-            )
-        # Create a form class dynamically (yeah, right?) and return that
-        return type("SettingsForm", (forms.Form,), fields)
-
-    def load_config(self):
-        return Config.load_identity(self.request.identity)
-
-    def save_config(self, key, value):
-        Config.set_identity(self.request.identity, key, value)
-
-    def get_initial(self):
-        config = self.load_config()
-        initial = {}
-        for key in self.options.keys():
-            initial[key] = getattr(config, key)
-        return initial
-
-    def get_context_data(self):
-        context = super().get_context_data()
-        context["section"] = self.section
-        # Gather fields into fieldsets
-        context["fieldsets"] = {}
-        for title, fields in self.layout.items():
-            context["fieldsets"][title] = [context["form"][field] for field in fields]
-        return context
-
-    def form_valid(self, form):
-        # Save each key
-        for field in form:
-            if field.field.__class__.__name__ == "ImageField":
-                # These can be cleared with an extra checkbox
-                if self.request.POST.get(f"{field.name}__clear"):
-                    self.save_config(field.name, None)
-                    continue
-                # We shove the preview values in initial_data, so only save file
-                # fields if they have a File object.
-                if not isinstance(form.cleaned_data[field.name], File):
-                    continue
-            self.save_config(
-                field.name,
-                form.cleaned_data[field.name],
-            )
-        return redirect(".")
-
-
-class InterfacePage(SettingsPage):
-
-    section = "interface"
-
-    options = {
-        "toot_mode": {
-            "title": "I Will Toot As I Please",
-            "help_text": "Changes all 'Post' buttons to 'Toot!'",
-        }
-    }
-
-    layout = {"Posting": ["toot_mode"]}
-
-
-@method_decorator(identity_required, name="dispatch")
-class SecurityPage(FormView):
-    """
-    Lets the identity's profile be edited
-    """
-
-    template_name = "settings/login_security.html"
-    extra_context = {"section": "security"}
-
-    class form_class(forms.Form):
-        email = forms.EmailField(
-            disabled=True,
-            help_text="Your email address cannot be changed yet.",
-        )
-
-    def get_initial(self):
-        return {"email": self.request.user.email}
-
-    template_name = "settings/login_security.html"
diff --git a/users/views/settings/interface.py b/users/views/settings/interface.py
new file mode 100644
index 0000000..fe8e1e9
--- /dev/null
+++ b/users/views/settings/interface.py
@@ -0,0 +1,126 @@
+from functools import partial
+from typing import ClassVar, Dict, List
+
+from django import forms
+from django.core.files import File
+from django.shortcuts import redirect
+from django.utils.decorators import method_decorator
+from django.views.generic import FormView
+
+from core.models.config import Config, UploadedImage
+from users.decorators import identity_required
+
+
+@method_decorator(identity_required, name="dispatch")
+class SettingsPage(FormView):
+    """
+    Shows a settings page dynamically created from our settings layout
+    at the bottom of the page. Don't add this to a URL directly - subclass!
+    """
+
+    options_class = Config.IdentityOptions
+    template_name = "settings/settings.html"
+    section: ClassVar[str]
+    options: Dict[str, Dict[str, str]]
+    layout: Dict[str, List[str]]
+
+    def get_form_class(self):
+        # Create the fields dict from the config object
+        fields = {}
+        for key, details in self.options.items():
+            field_kwargs = {}
+            config_field = self.options_class.__fields__[key]
+            if config_field.type_ is bool:
+                form_field = partial(
+                    forms.BooleanField,
+                    widget=forms.Select(
+                        choices=[(True, "Enabled"), (False, "Disabled")]
+                    ),
+                )
+            elif config_field.type_ is UploadedImage:
+                form_field = forms.ImageField
+            elif config_field.type_ is str:
+                if details.get("display") == "textarea":
+                    form_field = partial(
+                        forms.CharField,
+                        widget=forms.Textarea,
+                    )
+                else:
+                    form_field = forms.CharField
+            elif config_field.type_ is int:
+                choices = details.get("choices")
+                if choices:
+                    field_kwargs["widget"] = forms.Select(choices=choices)
+                form_field = forms.IntegerField
+            else:
+                raise ValueError(f"Cannot render settings type {config_field.type_}")
+            fields[key] = form_field(
+                label=details["title"],
+                help_text=details.get("help_text", ""),
+                required=details.get("required", False),
+                **field_kwargs,
+            )
+        # Create a form class dynamically (yeah, right?) and return that
+        return type("SettingsForm", (forms.Form,), fields)
+
+    def load_config(self):
+        return Config.load_identity(self.request.identity)
+
+    def save_config(self, key, value):
+        Config.set_identity(self.request.identity, key, value)
+
+    def get_initial(self):
+        config = self.load_config()
+        initial = {}
+        for key in self.options.keys():
+            initial[key] = getattr(config, key)
+        return initial
+
+    def get_context_data(self):
+        context = super().get_context_data()
+        context["section"] = self.section
+        # Gather fields into fieldsets
+        context["fieldsets"] = {}
+        for title, fields in self.layout.items():
+            context["fieldsets"][title] = [context["form"][field] for field in fields]
+        return context
+
+    def form_valid(self, form):
+        # Save each key
+        for field in form:
+            if field.field.__class__.__name__ == "ImageField":
+                # These can be cleared with an extra checkbox
+                if self.request.POST.get(f"{field.name}__clear"):
+                    self.save_config(field.name, None)
+                    continue
+                # We shove the preview values in initial_data, so only save file
+                # fields if they have a File object.
+                if not isinstance(form.cleaned_data[field.name], File):
+                    continue
+            self.save_config(
+                field.name,
+                form.cleaned_data[field.name],
+            )
+        return redirect(".")
+
+
+from activities.models.post import Post
+
+
+class InterfacePage(SettingsPage):
+
+    section = "interface"
+
+    options = {
+        "toot_mode": {
+            "title": "I Will Toot As I Please",
+            "help_text": "Changes all 'Post' buttons to 'Toot!'",
+        },
+        "default_post_visibility": {
+            "title": "Default Post Visibility",
+            "help_text": "Visibility to use as default for new posts.",
+            "choices": Post.Visibilities.choices,
+        },
+    }
+
+    layout = {"Posting": ["toot_mode", "default_post_visibility"]}
diff --git a/users/views/settings/security.py b/users/views/settings/security.py
new file mode 100644
index 0000000..31c2b22
--- /dev/null
+++ b/users/views/settings/security.py
@@ -0,0 +1,26 @@
+from django import forms
+from django.utils.decorators import method_decorator
+from django.views.generic import FormView
+
+from users.decorators import identity_required
+
+
+@method_decorator(identity_required, name="dispatch")
+class SecurityPage(FormView):
+    """
+    Lets the identity's profile be edited
+    """
+
+    template_name = "settings/login_security.html"
+    extra_context = {"section": "security"}
+
+    class form_class(forms.Form):
+        email = forms.EmailField(
+            disabled=True,
+            help_text="Your email address cannot be changed yet.",
+        )
+
+    def get_initial(self):
+        return {"email": self.request.user.email}
+
+    template_name = "settings/login_security.html"