diff --git a/server/config.go b/server/config.go
index c3f93560..633e1fed 100644
--- a/server/config.go
+++ b/server/config.go
@@ -108,6 +108,7 @@ type Config struct {
 	EnableLogin                          bool
 	EnableEmailConfirm                   bool
 	EnableResetPassword                  bool
+	EnableAccountUpgrades                bool
 	Version                              string // injected by App
 }
 
diff --git a/server/server.go b/server/server.go
index b4a79f0e..d45bd282 100644
--- a/server/server.go
+++ b/server/server.go
@@ -40,12 +40,12 @@ import (
 			message cache duration
 			Keep 10000 messages or keep X days?
 			Attachment expiration based on plan
-		reserve topics
 		purge accounts that were not logged into in X
 		reset daily limits for users
-		Account usage not updated "in real time"
 		max token issue limit
 		user db startup queries -> foreign keys
+		UI
+		- Feature flag for "reserve topic" feature
 		Sync:
 			- "mute" setting
 			- figure out what settings are "web" or "phone"
@@ -447,17 +447,20 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi
 	if !s.config.WebRootIsApp {
 		appRoot = "/app"
 	}
-	disallowedTopicsStr := `"` + strings.Join(disallowedTopics, `", "`) + `"`
+	response := &apiConfigResponse{
+		BaseURL:             "", // Will translate to window.location.origin
+		AppRoot:             appRoot,
+		EnableLogin:         s.config.EnableLogin,
+		EnableSignup:        s.config.EnableSignup,
+		EnableResetPassword: s.config.EnableResetPassword,
+		DisallowedTopics:    disallowedTopics,
+	}
+	b, err := json.Marshal(response)
+	if err != nil {
+		return err
+	}
 	w.Header().Set("Content-Type", "text/javascript")
-	_, err := io.WriteString(w, fmt.Sprintf(`// Generated server configuration
-var config = {
-  baseUrl: window.location.origin,
-  appRoot: "%s",
-  enableLogin: %t,
-  enableSignup: %t,
-  enableResetPassword: %t,
-  disallowedTopics: [%s], 
-};`, appRoot, s.config.EnableLogin, s.config.EnableSignup, s.config.EnableResetPassword, disallowedTopicsStr))
+	_, err = io.WriteString(w, fmt.Sprintf("// Generated server configuration\nvar config = %s;\n", string(b)))
 	return err
 }
 
diff --git a/server/types.go b/server/types.go
index de5c452e..2a2db420 100644
--- a/server/types.go
+++ b/server/types.go
@@ -280,3 +280,12 @@ type apiAccountAccessRequest struct {
 	Topic    string `json:"topic"`
 	Everyone string `json:"everyone"`
 }
+
+type apiConfigResponse struct {
+	BaseURL             string   `json:"base_url"`
+	AppRoot             string   `json:"app_root"`
+	EnableLogin         bool     `json:"enable_login"`
+	EnableSignup        bool     `json:"enable_signup"`
+	EnableResetPassword bool     `json:"enable_reset_password"`
+	DisallowedTopics    []string `json:"disallowed_topics"`
+}
diff --git a/web/public/static/langs/en.json b/web/public/static/langs/en.json
index 5b3e7235..4ab4e018 100644
--- a/web/public/static/langs/en.json
+++ b/web/public/static/langs/en.json
@@ -83,6 +83,7 @@
   "subscription_settings_dialog_title": "Subscription settings",
   "subscription_settings_dialog_description": "Configure settings specifically for this topic subscription. Settings are currently only applied locally.",
   "subscription_settings_dialog_display_name_placeholder": "Display name",
+  "subscription_settings_dialog_reserve_topic_label": "Reserve topic and configure access",
   "subscription_settings_button_cancel": "Cancel",
   "subscription_settings_button_save": "Save",
   "notifications_loading": "Loading notifications …",
@@ -159,6 +160,7 @@
   "subscribe_dialog_login_button_back": "Back",
   "subscribe_dialog_login_button_login": "Login",
   "subscribe_dialog_error_user_not_authorized": "User {{username}} not authorized",
+  "subscribe_dialog_error_topic_already_reserved": "Topic already reserved",
   "subscribe_dialog_error_user_anonymous": "anonymous",
   "account_basics_title": "Account",
   "account_basics_username_title": "Username",
@@ -253,6 +255,7 @@
   "prefs_reservations_table_everyone_read_write": "Everyone can publish and subscribe",
   "prefs_reservations_dialog_title_add": "Reserve topic",
   "prefs_reservations_dialog_title_edit": "Edit reserved topic",
+  "prefs_reservations_dialog_description": "Reserving a topic gives you ownership over the topic, and allows you to define access permissions for other users over the topic.",
   "prefs_reservations_dialog_topic_label": "Topic",
   "prefs_reservations_dialog_access_label": "Access",
   "priority_min": "min",
diff --git a/web/src/app/AccountApi.js b/web/src/app/AccountApi.js
index 94f638d3..338681de 100644
--- a/web/src/app/AccountApi.js
+++ b/web/src/app/AccountApi.js
@@ -231,6 +231,8 @@ class AccountApi {
         });
         if (response.status === 401 || response.status === 403) {
             throw new UnauthorizedError();
+        } else if (response.status === 409) {
+            throw new TopicReservedError();
         } else if (response.status !== 200) {
             throw new Error(`Unexpected server response ${response.status}`);
         }
@@ -312,6 +314,13 @@ export class UsernameTakenError extends Error {
     }
 }
 
+export class TopicReservedError extends Error {
+    constructor(topic) {
+        super("Topic already reserved");
+        this.topic = topic;
+    }
+}
+
 export class AccountCreateLimitReachedError extends Error {
     constructor() {
         super("Account creation limit reached");
diff --git a/web/src/app/config.js b/web/src/app/config.js
index 71a9ece3..0cb0bb1b 100644
--- a/web/src/app/config.js
+++ b/web/src/app/config.js
@@ -1,2 +1,7 @@
 const config = window.config;
+
+if (config.base_url === "") {
+    config.base_url = window.location.origin;
+}
+
 export default config;
diff --git a/web/src/components/Account.js b/web/src/components/Account.js
index 7ef175b1..1357f866 100644
--- a/web/src/components/Account.js
+++ b/web/src/components/Account.js
@@ -177,7 +177,7 @@ const Stats = () => {
             <PrefGroup>
                 <Pref title={t("account_usage_plan_title")}>
                     <div>
-                        {account?.role === "admin"
+                        {account.role === "admin"
                             ? <>{t("account_usage_unlimited")} <Tooltip title={t("account_basics_username_admin_tooltip")}><span style={{cursor: "default"}}>👑</span></Tooltip></>
                             : t(`account_usage_plan_code_${planCode}`)}
                     </div>
@@ -187,28 +187,44 @@ const Stats = () => {
                         <Typography variant="body2" sx={{float: "left"}}>{account.stats.topics}</Typography>
                         <Typography variant="body2" sx={{float: "right"}}>{account.limits.topics > 0 ? t("account_usage_of_limit", { limit: account.limits.topics }) : t("account_usage_unlimited")}</Typography>
                     </div>
-                    <LinearProgress variant="determinate" value={account.limits.topics > 0 ? normalize(account.stats.topics, account.limits.topics) : 100} />
+                    <LinearProgress
+                        variant="determinate"
+                        value={account.limits.topics > 0 ? normalize(account.stats.topics, account.limits.topics) : 100}
+                        color={account?.role !== "admin" && account.stats.topics_remaining === 0 ? 'error' : 'primary'}
+                    />
                 </Pref>
                 <Pref title={t("account_usage_messages_title")}>
                     <div>
                         <Typography variant="body2" sx={{float: "left"}}>{account.stats.messages}</Typography>
                         <Typography variant="body2" sx={{float: "right"}}>{account.limits.messages > 0 ? t("account_usage_of_limit", { limit: account.limits.messages }) : t("account_usage_unlimited")}</Typography>
                     </div>
-                    <LinearProgress variant="determinate" value={account.limits.messages > 0 ? normalize(account.stats.messages, account.limits.messages) : 100} />
+                    <LinearProgress
+                        variant="determinate"
+                        value={account.limits.messages > 0 ? normalize(account.stats.messages, account.limits.messages) : 100}
+                        color={account?.role !== "admin" && account.stats.messages_remaining === 0 ? 'error' : 'primary'}
+                    />
                 </Pref>
                 <Pref title={t("account_usage_emails_title")}>
                     <div>
                         <Typography variant="body2" sx={{float: "left"}}>{account.stats.emails}</Typography>
                         <Typography variant="body2" sx={{float: "right"}}>{account.limits.emails > 0 ? t("account_usage_of_limit", { limit: account.limits.emails }) : t("account_usage_unlimited")}</Typography>
                     </div>
-                    <LinearProgress variant="determinate" value={account.limits.emails > 0 ? normalize(account.stats.emails, account.limits.emails) : 100} />
+                    <LinearProgress
+                        variant="determinate"
+                        value={account.limits.emails > 0 ? normalize(account.stats.emails, account.limits.emails) : 100}
+                        color={account?.role !== "admin" && account.stats.emails_remaining === 0 ? 'error' : 'primary'}
+                    />
                 </Pref>
-                <Pref title={t("account_usage_attachment_storage_title")} subtitle={t("account_usage_attachment_storage_subtitle", { filesize: formatBytes(account.limits.attachment_file_size) })}>
+                <Pref title={t("account_usage_attachment_storage_title")} subtitle={account.role !== "admin" ? t("account_usage_attachment_storage_subtitle", { filesize: formatBytes(account.limits.attachment_file_size) }) : null}>
                     <div>
                         <Typography variant="body2" sx={{float: "left"}}>{formatBytes(account.stats.attachment_total_size)}</Typography>
                         <Typography variant="body2" sx={{float: "right"}}>{account.limits.attachment_total_size > 0 ? t("account_usage_of_limit", { limit: formatBytes(account.limits.attachment_total_size) }) : t("account_usage_unlimited")}</Typography>
                     </div>
-                    <LinearProgress variant="determinate" value={account.limits.attachment_total_size > 0 ? normalize(account.stats.attachment_total_size, account.limits.attachment_total_size) : 100} />
+                    <LinearProgress
+                        variant="determinate"
+                        value={account.limits.attachment_total_size > 0 ? normalize(account.stats.attachment_total_size, account.limits.attachment_total_size) : 100}
+                        color={account.role !== "admin" && account.stats.attachment_total_size_remaining === 0 ? 'error' : 'primary'}
+                    />
                 </Pref>
             </PrefGroup>
             {account.limits.basis === "ip" &&
diff --git a/web/src/components/Preferences.js b/web/src/components/Preferences.js
index 6b7162d1..90e0eb21 100644
--- a/web/src/components/Preferences.js
+++ b/web/src/components/Preferences.js
@@ -1,6 +1,7 @@
 import * as React from 'react';
 import {useEffect, useState} from 'react';
 import {
+    Alert,
     CardActions,
     CardContent,
     FormControl,
@@ -44,6 +45,8 @@ import LockIcon from "@mui/icons-material/Lock";
 import {Public, PublicOff} from "@mui/icons-material";
 import ListItemIcon from "@mui/material/ListItemIcon";
 import ListItemText from "@mui/material/ListItemText";
+import DialogContentText from "@mui/material/DialogContentText";
+import ReserveTopicSelect from "./ReserveTopicSelect";
 
 const Preferences = () => {
     return (
@@ -482,10 +485,11 @@ const Reservations = () => {
     const [dialogKey, setDialogKey] = useState(0);
     const [dialogOpen, setDialogOpen] = useState(false);
 
-    if (!session.exists() || !account) {
+    if (!session.exists() || !account || account.role === "admin") {
         return <></>;
     }
     const reservations = account.reservations || [];
+    const limitReached = account.role === "user" && account.stats.topics_remaining === 0;
 
     const handleAddClick = () => {
         setDialogKey(prev => prev+1);
@@ -505,7 +509,7 @@ const Reservations = () => {
         } catch (e) {
             console.log(`[Preferences] Error topic reservation.`, e);
         }
-        // FIXME handle 401/403
+        // FIXME handle 401/403/409
     };
 
     return (
@@ -518,9 +522,15 @@ const Reservations = () => {
                     {t("prefs_reservations_description")}
                 </Paragraph>
                 {reservations.length > 0 && <ReservationsTable reservations={reservations}/>}
+                {limitReached &&
+                    <Alert severity="info">
+                        You reached your reserved topics limit.
+                    </Alert>
+                }
             </CardContent>
             <CardActions>
-                <Button onClick={handleAddClick}>{t("prefs_reservations_add_button")}</Button>
+                <Button onClick={handleAddClick} disabled={limitReached}>{t("prefs_reservations_add_button")}</Button>
+
                 <ReservationsDialog
                     key={`reservationAddDialog${dialogKey}`}
                     open={dialogOpen}
@@ -559,7 +569,7 @@ const ReservationsTable = (props) => {
         } catch (e) {
             console.log(`[Preferences] Error topic reservation.`, e);
         }
-        // FIXME handle 401/403
+        // FIXME handle 401/403/409
     };
 
     const handleDeleteClick = async (reservation) => {
@@ -670,6 +680,9 @@ const ReservationsDialog = (props) => {
         <Dialog open={props.open} onClose={props.onCancel} maxWidth="sm" fullWidth fullScreen={fullScreen}>
             <DialogTitle>{editMode ? t("prefs_reservations_dialog_title_edit") : t("prefs_reservations_dialog_title_add")}</DialogTitle>
             <DialogContent>
+                <DialogContentText>
+                    {t("prefs_reservations_dialog_description")}
+                </DialogContentText>
                 {!editMode && <TextField
                     autoFocus
                     margin="dense"
@@ -682,37 +695,11 @@ const ReservationsDialog = (props) => {
                     fullWidth
                     variant="standard"
                 />}
-                <FormControl fullWidth variant="standard">
-                    <Select
-                        value={everyone}
-                        onChange={(ev) => setEveryone(ev.target.value)}
-                        aria-label={t("prefs_reservations_dialog_access_label")}
-                        sx={{
-                            marginTop: 1,
-                            "& .MuiSelect-select": {
-                                display: 'flex',
-                                alignItems: 'center'
-                            }
-                        }}
-                    >
-                        <MenuItem value="deny-all">
-                            <ListItemIcon><LockIcon /></ListItemIcon>
-                            <ListItemText primary={t("prefs_reservations_table_everyone_deny_all")} />
-                        </MenuItem>
-                        <MenuItem value="read-only">
-                            <ListItemIcon><PublicOff /></ListItemIcon>
-                            <ListItemText primary={t("prefs_reservations_table_everyone_read_only")} />
-                        </MenuItem>
-                        <MenuItem value="write-only">
-                            <ListItemIcon><PublicOff /></ListItemIcon>
-                            <ListItemText primary={t("prefs_reservations_table_everyone_write_only")} />
-                        </MenuItem>
-                        <MenuItem value="read-write">
-                            <ListItemIcon><Public /></ListItemIcon>
-                            <ListItemText primary={t("prefs_reservations_table_everyone_read_write")} />
-                        </MenuItem>
-                    </Select>
-                </FormControl>
+                <ReserveTopicSelect
+                    value={everyone}
+                    onChange={setEveryone}
+                    sx={{mt: 1}}
+                />
             </DialogContent>
             <DialogActions>
                 <Button onClick={props.onCancel}>{t("prefs_users_dialog_button_cancel")}</Button>
diff --git a/web/src/components/ReserveTopicSelect.js b/web/src/components/ReserveTopicSelect.js
new file mode 100644
index 00000000..e9ca91d0
--- /dev/null
+++ b/web/src/components/ReserveTopicSelect.js
@@ -0,0 +1,62 @@
+import * as React from 'react';
+import {useState} from 'react';
+import Button from '@mui/material/Button';
+import TextField from '@mui/material/TextField';
+import Dialog from '@mui/material/Dialog';
+import DialogContent from '@mui/material/DialogContent';
+import DialogContentText from '@mui/material/DialogContentText';
+import DialogTitle from '@mui/material/DialogTitle';
+import {Checkbox, FormControl, FormControlLabel, Select, useMediaQuery} from "@mui/material";
+import theme from "./theme";
+import subscriptionManager from "../app/SubscriptionManager";
+import DialogFooter from "./DialogFooter";
+import {useTranslation} from "react-i18next";
+import accountApi, {UnauthorizedError} from "../app/AccountApi";
+import session from "../app/Session";
+import routes from "./routes";
+import MenuItem from "@mui/material/MenuItem";
+import ListItemIcon from "@mui/material/ListItemIcon";
+import LockIcon from "@mui/icons-material/Lock";
+import ListItemText from "@mui/material/ListItemText";
+import {Public, PublicOff} from "@mui/icons-material";
+
+const ReserveTopicSelect = (props) => {
+    const { t } = useTranslation();
+    const sx = props.sx || {};
+    return (
+        <FormControl fullWidth variant="standard" sx={sx}>
+            <Select
+                value={props.value}
+                onChange={(ev) => props.onChange(ev.target.value)}
+                aria-label={t("prefs_reservations_dialog_access_label")}
+                sx={{
+                    "& .MuiSelect-select": {
+                        display: 'flex',
+                        alignItems: 'center',
+                        paddingTop: "4px",
+                        paddingBottom: "4px",
+                    }
+                }}
+            >
+                <MenuItem value="deny-all">
+                    <ListItemIcon><LockIcon/></ListItemIcon>
+                    <ListItemText primary={t("prefs_reservations_table_everyone_deny_all")}/>
+                </MenuItem>
+                <MenuItem value="read-only">
+                    <ListItemIcon><PublicOff/></ListItemIcon>
+                    <ListItemText primary={t("prefs_reservations_table_everyone_read_only")}/>
+                </MenuItem>
+                <MenuItem value="write-only">
+                    <ListItemIcon><PublicOff/></ListItemIcon>
+                    <ListItemText primary={t("prefs_reservations_table_everyone_write_only")}/>
+                </MenuItem>
+                <MenuItem value="read-write">
+                    <ListItemIcon><Public/></ListItemIcon>
+                    <ListItemText primary={t("prefs_reservations_table_everyone_read_write")}/>
+                </MenuItem>
+            </Select>
+        </FormControl>
+    );
+};
+
+export default ReserveTopicSelect;
diff --git a/web/src/components/SubscribeDialog.js b/web/src/components/SubscribeDialog.js
index f5add414..b5061ea1 100644
--- a/web/src/components/SubscribeDialog.js
+++ b/web/src/components/SubscribeDialog.js
@@ -6,7 +6,7 @@ import Dialog from '@mui/material/Dialog';
 import DialogContent from '@mui/material/DialogContent';
 import DialogContentText from '@mui/material/DialogContentText';
 import DialogTitle from '@mui/material/DialogTitle';
-import {Autocomplete, Checkbox, FormControlLabel, useMediaQuery} from "@mui/material";
+import {Autocomplete, Checkbox, FormControlLabel, FormGroup, useMediaQuery} from "@mui/material";
 import theme from "./theme";
 import api from "../app/Api";
 import {randomAlphanumericString, topicUrl, validTopic, validUrl} from "../app/utils";
@@ -17,14 +17,14 @@ import DialogFooter from "./DialogFooter";
 import {useTranslation} from "react-i18next";
 import session from "../app/Session";
 import routes from "./routes";
-import accountApi, {UnauthorizedError} from "../app/AccountApi";
-import IconButton from "@mui/material/IconButton";
+import accountApi, {TopicReservedError, UnauthorizedError} from "../app/AccountApi";
 import PublicIcon from '@mui/icons-material/Public';
 import LockIcon from '@mui/icons-material/Lock';
 import PublicOffIcon from '@mui/icons-material/PublicOff';
 import MenuItem from "@mui/material/MenuItem";
 import PopupMenu from "./PopupMenu";
 import ListItemIcon from "@mui/material/ListItemIcon";
+import ReserveTopicSelect from "./ReserveTopicSelect";
 
 const publicBaseUrl = "https://ntfy.sh";
 
@@ -33,6 +33,7 @@ const SubscribeDialog = (props) => {
     const [topic, setTopic] = useState("");
     const [showLoginPage, setShowLoginPage] = useState(false);
     const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
+
     const handleSuccess = async () => {
         console.log(`[SubscribeDialog] Subscribing to topic ${topic}`);
         const actualBaseUrl = (baseUrl) ? baseUrl : config.baseUrl;
@@ -44,6 +45,7 @@ const SubscribeDialog = (props) => {
                     topic: topic
                 });
                 await subscriptionManager.setRemoteId(subscription.id, remoteSubscription.id);
+                await accountApi.sync();
             } catch (e) {
                 console.log(`[SubscribeDialog] Subscribing to topic ${topic} failed`, e);
                 if ((e instanceof UnauthorizedError)) {
@@ -54,6 +56,7 @@ const SubscribeDialog = (props) => {
         poller.pollInBackground(subscription); // Dangle!
         props.onSuccess(subscription);
     }
+
     return (
         <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
             {!showLoginPage && <SubscribePage
@@ -78,10 +81,11 @@ const SubscribeDialog = (props) => {
 
 const SubscribePage = (props) => {
     const { t } = useTranslation();
+    const [reserveTopicVisible, setReserveTopicVisible] = useState(false);
     const [anotherServerVisible, setAnotherServerVisible] = useState(false);
     const [errorText, setErrorText] = useState("");
     const [accessAnchorEl, setAccessAnchorEl] = useState(null);
-    const [access, setAccess] = useState("public");
+    const [everyone, setEveryone] = useState("deny-all");
     const baseUrl = (anotherServerVisible) ? props.baseUrl : config.baseUrl;
     const topic = props.topic;
     const existingTopicUrls = props.subscriptions.map(s => topicUrl(s.baseUrl, s.topic));
@@ -92,6 +96,8 @@ const SubscribePage = (props) => {
     const handleSubscribe = async () => {
         const user = await userManager.get(baseUrl); // May be undefined
         const username = (user) ? user.username : t("subscribe_dialog_error_user_anonymous");
+
+        // Check read access to topic
         const success = await api.topicAuth(baseUrl, topic, user);
         if (!success) {
             console.log(`[SubscribeDialog] Login to ${topicUrl(baseUrl, topic)} failed for user ${username}`);
@@ -103,6 +109,24 @@ const SubscribePage = (props) => {
                 return;
             }
         }
+
+        // Reserve topic (if requested)
+        if (session.exists() && baseUrl === config.baseUrl && reserveTopicVisible) {
+            console.log(`[SubscribeDialog] Reserving topic ${topic} with everyone access ${everyone}`);
+            try {
+                await accountApi.upsertAccess(topic, everyone);
+                // Account sync later after it was added
+            } catch (e) {
+                console.log(`[SubscribeDialog] Error reserving topic`, e);
+                if ((e instanceof UnauthorizedError)) {
+                    session.resetAndRedirect(routes.login);
+                } else if ((e instanceof TopicReservedError)) {
+                    setErrorText(t("subscribe_dialog_error_topic_already_reserved"));
+                    return;
+                }
+            }
+        }
+
         console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`);
         props.onSuccess();
     };
@@ -137,14 +161,7 @@ const SubscribePage = (props) => {
                 <DialogContentText>
                     {t("subscribe_dialog_subscribe_description")}
                 </DialogContentText>
-                <div style={{display: 'flex'}} role="row">
-                    {session.exists() &&
-                        <IconButton onClick={(ev) => setAccessAnchorEl(ev.currentTarget)} color="inherit" size="large" edge="start" sx={{height: "45px", marginTop: "5px", color: "grey"}}>
-                            {access === "public" && <PublicIcon/>}
-                            {access === "public-read" && <PublicOffIcon/>}
-                            {access === "private" && <LockIcon/>}
-                        </IconButton>
-                    }
+                <div style={{display: 'flex', paddingBottom: "8px"}} role="row">
                     <TextField
                         autoFocus
                         margin="dense"
@@ -168,19 +185,19 @@ const SubscribePage = (props) => {
                         open={!!accessAnchorEl}
                         onClose={() => setAccessAnchorEl(null)}
                     >
-                        <MenuItem onClick={() => setAccess("private")} selected={access === "private"}>
+                        <MenuItem onClick={() => setEveryone("private")} selected={everyone === "private"}>
                             <ListItemIcon>
                                 <LockIcon fontSize="small" />
                             </ListItemIcon>
                             Only I can publish and subscribe
                         </MenuItem>
-                        <MenuItem onClick={() => setAccess("public-read")} selected={access === "public-read"}>
+                        <MenuItem onClick={() => setEveryone("public-read")} selected={everyone === "public-read"}>
                             <ListItemIcon>
                                 <PublicOffIcon fontSize="small" />
                             </ListItemIcon>
                             I can publish, everyone can subscribe
                         </MenuItem>
-                        <MenuItem onClick={() => setAccess("public")} selected={access === "public"}>
+                        <MenuItem onClick={() => setEveryone("public")} selected={everyone === "public"}>
                             <ListItemIcon>
                                 <PublicIcon fontSize="small" />
                             </ListItemIcon>
@@ -188,32 +205,58 @@ const SubscribePage = (props) => {
                         </MenuItem>
                     </PopupMenu>
                 </div>
-                <FormControlLabel
-                    sx={{pt: 1}}
-                    control={
-                        <Checkbox
-                            onChange={handleUseAnotherChanged}
-                            inputProps={{
-                                "aria-label": t("subscribe_dialog_subscribe_use_another_label")
-                            }}
-                        />
-                    }
-                    label={t("subscribe_dialog_subscribe_use_another_label")} />
-                {anotherServerVisible && <Autocomplete
-                    freeSolo
-                    options={existingBaseUrls}
-                    sx={{ maxWidth: 400 }}
-                    inputValue={props.baseUrl}
-                    onInputChange={updateBaseUrl}
-                    renderInput={ (params) =>
-                        <TextField
-                            {...params}
-                            placeholder={config.baseUrl}
+                {session.exists() && !anotherServerVisible &&
+                    <FormGroup>
+                        <FormControlLabel
                             variant="standard"
-                            aria-label={t("subscribe_dialog_subscribe_base_url_label")}
+                            control={
+                                <Checkbox
+                                    fullWidth
+                                    checked={reserveTopicVisible}
+                                    onChange={(ev) => setReserveTopicVisible(ev.target.checked)}
+                                    inputProps={{
+                                        "aria-label": t("subscription_settings_dialog_reserve_topic_label")
+                                    }}
+                                />
+                            }
+                            label={t("subscription_settings_dialog_reserve_topic_label")}
                         />
-                    }
-                />}
+                        {reserveTopicVisible &&
+                            <ReserveTopicSelect
+                                value={everyone}
+                                onChange={setEveryone}
+                            />
+                        }
+                    </FormGroup>
+                }
+                {!reserveTopicVisible &&
+                    <FormGroup>
+                        <FormControlLabel
+                            control={
+                                <Checkbox
+                                    onChange={handleUseAnotherChanged}
+                                    inputProps={{
+                                        "aria-label": t("subscribe_dialog_subscribe_use_another_label")
+                                    }}
+                                />
+                            }
+                            label={t("subscribe_dialog_subscribe_use_another_label")}/>
+                        {anotherServerVisible && <Autocomplete
+                            freeSolo
+                            options={existingBaseUrls}
+                            inputValue={props.baseUrl}
+                            onInputChange={updateBaseUrl}
+                            renderInput={(params) =>
+                                <TextField
+                                    {...params}
+                                    placeholder={config.baseUrl}
+                                    variant="standard"
+                                    aria-label={t("subscribe_dialog_subscribe_base_url_label")}
+                                />
+                            }
+                        />}
+                    </FormGroup>
+                }
             </DialogContent>
             <DialogFooter status={errorText}>
                 <Button onClick={props.onCancel}>{t("subscribe_dialog_subscribe_button_cancel")}</Button>
diff --git a/web/src/components/SubscriptionSettingsDialog.js b/web/src/components/SubscriptionSettingsDialog.js
index f6f5125a..7592262f 100644
--- a/web/src/components/SubscriptionSettingsDialog.js
+++ b/web/src/components/SubscriptionSettingsDialog.js
@@ -6,7 +6,7 @@ import Dialog from '@mui/material/Dialog';
 import DialogContent from '@mui/material/DialogContent';
 import DialogContentText from '@mui/material/DialogContentText';
 import DialogTitle from '@mui/material/DialogTitle';
-import {Checkbox, FormControl, FormControlLabel, Select, useMediaQuery} from "@mui/material";
+import {Checkbox, FormControlLabel, useMediaQuery} from "@mui/material";
 import theme from "./theme";
 import subscriptionManager from "../app/SubscriptionManager";
 import DialogFooter from "./DialogFooter";
@@ -14,11 +14,7 @@ import {useTranslation} from "react-i18next";
 import accountApi, {UnauthorizedError} from "../app/AccountApi";
 import session from "../app/Session";
 import routes from "./routes";
-import MenuItem from "@mui/material/MenuItem";
-import ListItemIcon from "@mui/material/ListItemIcon";
-import LockIcon from "@mui/icons-material/Lock";
-import ListItemText from "@mui/material/ListItemText";
-import {Public, PublicOff} from "@mui/icons-material";
+import ReserveTopicSelect from "./ReserveTopicSelect";
 
 const SubscriptionSettingsDialog = (props) => {
     const { t } = useTranslation();
@@ -53,6 +49,8 @@ const SubscriptionSettingsDialog = (props) => {
                 if ((e instanceof UnauthorizedError)) {
                     session.resetAndRedirect(routes.login);
                 }
+
+                // FIXME handle 409
             }
         }
         props.onClose();
@@ -80,7 +78,6 @@ const SubscriptionSettingsDialog = (props) => {
                         "aria-label": t("subscription_settings_dialog_display_name_placeholder")
                     }}
                 />
-
                 <FormControlLabel
                     fullWidth
                     variant="standard"
@@ -90,45 +87,17 @@ const SubscriptionSettingsDialog = (props) => {
                             checked={reserveTopicVisible}
                             onChange={(ev) => setReserveTopicVisible(ev.target.checked)}
                             inputProps={{
-                                "aria-label": t("xxxxxxxxxxxxxxxxxx")
+                                "aria-label": t("subscription_settings_dialog_reserve_topic_label")
                             }}
                         />
                     }
-                    label={t("Reserve topic and configure custom access:")}
+                    label={t("subscription_settings_dialog_reserve_topic_label")}
                 />
                 {reserveTopicVisible &&
-                    <FormControl variant="standard">
-                        <Select
-                            value={everyone}
-                            onChange={(ev) => setEveryone(ev.target.value)}
-                            aria-label={t("prefs_reservations_dialog_access_label")}
-                            sx={{
-                                "& .MuiSelect-select": {
-                                    display: 'flex',
-                                    alignItems: 'center',
-                                    paddingTop: "4px",
-                                    paddingBottom: "4px",
-                                }
-                            }}
-                        >
-                            <MenuItem value="deny-all">
-                                <ListItemIcon><LockIcon/></ListItemIcon>
-                                <ListItemText primary={t("prefs_reservations_table_everyone_deny_all")}/>
-                            </MenuItem>
-                            <MenuItem value="read-only">
-                                <ListItemIcon><PublicOff/></ListItemIcon>
-                                <ListItemText primary={t("prefs_reservations_table_everyone_read_only")}/>
-                            </MenuItem>
-                            <MenuItem value="write-only">
-                                <ListItemIcon><PublicOff/></ListItemIcon>
-                                <ListItemText primary={t("prefs_reservations_table_everyone_write_only")}/>
-                            </MenuItem>
-                            <MenuItem value="read-write">
-                                <ListItemIcon><Public/></ListItemIcon>
-                                <ListItemText primary={t("prefs_reservations_table_everyone_read_write")}/>
-                            </MenuItem>
-                        </Select>
-                    </FormControl>
+                    <ReserveTopicSelect
+                        value={everyone}
+                        onChange={setEveryone}
+                    />
                 }
             </DialogContent>
             <DialogFooter>