diff --git a/syng/client.py b/syng/client.py
index 9090010..e421ae5 100644
--- a/syng/client.py
+++ b/syng/client.py
@@ -62,6 +62,14 @@ sources: dict[str, Source] = {}
 currentLock: asyncio.Semaphore = asyncio.Semaphore(0)
 
 
+def default_config():
+    return {
+        "preview_duration": 3,
+        "last_song": None,
+        "waiting_room_policy": None,
+    }
+
+
 @dataclass
 class State:
     """This captures the current state of the playback client.
@@ -87,12 +95,19 @@ class State:
     :type secret: str
     :param key: An optional key, if registration on the server is limited.
     :type key: Optional[str]
-    :param preview_duration: Amount of seconds the preview before a song be
-        displayed.
-    :type preview_duration: int
-    :param last_song: At what time should the server not accept any more songs.
-        `None` if no such limit should exist.
-    :type last_song: Optional[datetime.datetime]
+    :param config: Various configuration options for the client:
+        * `preview_duration` (`Optional[int]`): The duration in seconds the
+            playback client shows a preview for the next song. This is accounted for
+            in the calculation of the ETA for songs later in the queue.
+        * `last_song` (`Optional[datetime.datetime]`): A timestamp, defining the end of
+            the queue.
+        * `waiting_room_policy` (Optional[str]): One of:
+            - `force`, if a performer is already in the queue, they are put in the
+                       waiting room.
+            - `optional`, if a performer is already in the queue, they have the option
+                          to be put in the waiting room.
+            - `None`, performers are always added to the queue.
+    :type config: dict[str, Any]:
     """
 
     # pylint: disable=too-many-instance-attributes
@@ -105,31 +120,17 @@ class State:
     server: str = ""
     secret: str = ""
     key: Optional[str] = None
-    preview_duration: int = 3
-    last_song: Optional[datetime.datetime] = None
-
-    def get_config(self) -> dict[str, Any]:
-        """
-        Return a subset of values to be send to the server.
-
-        Currently this is:
-            - :py:attr:`State.preview_duration`
-            - :py:attr:`State.last_song` (As a timestamp)
-
-        :return: A dict resulting from the above values
-        :rtype: dict[str, Any]
-        """
-        return {
-            "preview_duration": self.preview_duration,
-            "last_song": self.last_song.timestamp()
-            if self.last_song
-            else None,
-        }
+    config: dict[str, Any] = field(default_factory=default_config)
 
 
 state: State = State()
 
 
+@sio.on("update_config")
+async def handle_update_config(data: dict[str, Any]) -> None:
+    state.config = default_config() | data
+
+
 @sio.on("skip-current")
 async def handle_skip_current(data: dict[str, Any]) -> None:
     """
@@ -201,7 +202,7 @@ async def handle_connect() -> None:
         "recent": state.recent,
         "room": state.room,
         "secret": state.secret,
-        "config": state.get_config(),
+        "config": state.config,
     }
     if state.key:
         data["registration-key"] = state.key
@@ -252,7 +253,7 @@ async def preview(entry: Entry) -> None:
         process = await asyncio.create_subprocess_exec(
             "mpv",
             tmpfile.name,
-            f"--image-display-duration={state.preview_duration}",
+            f"--image-display-duration={state.config['preview_duration']}",
             "--sub-pos=50",
             "--sub-file=-",
             "--fullscreen",
@@ -290,7 +291,7 @@ async def handle_play(data: dict[str, Any]) -> None:
     )
     try:
         state.current_source = sources[entry.source]
-        if state.preview_duration > 0:
+        if state.config["preview_duration"] > 0:
             await preview(entry)
         await sources[entry.source].play(entry)
     except Exception:  # pylint: disable=broad-except
@@ -409,12 +410,12 @@ async def aiomain() -> None:
     sources.update(configure_sources(config["sources"]))
 
     if "config" in config:
-        if "last_song" in config["config"]:
-            state.last_song = datetime.datetime.fromisoformat(
-                config["config"]["last_song"]
-            )
-        if "preview_duration" in config["config"]:
-            state.preview_duration = config["config"]["preview_duration"]
+        last_song = (
+            datetime.datetime.fromisoformat(config["config"]["last_song"])
+            if "last_song" in config["config"]
+            else None
+        )
+        state.config |= config["config"] | {"last_song": last_song}
 
     state.key = args.key if args.key else None
 
diff --git a/syng/server.py b/syng/server.py
index 8286af2..f1f37f9 100644
--- a/syng/server.py
+++ b/syng/server.py
@@ -21,6 +21,7 @@ import logging
 import os
 import random
 import string
+from json.decoder import JSONDecodeError
 from argparse import ArgumentParser
 from dataclasses import dataclass
 from dataclasses import field
@@ -43,6 +44,12 @@ sio = socketio.AsyncServer(
 app = web.Application()
 sio.attach(app)
 
+DEFAULT_CONFIG = {
+    "preview_duration": 3,
+    "waiting_room_policy": None,
+    "last_song": None,
+}
+
 
 async def root_handler(request: Any) -> Any:
     """
@@ -68,7 +75,7 @@ logger = logging.getLogger(__name__)
 
 
 @dataclass
-class Config:
+class Client:
     """This stores the configuration of a specific playback client.
 
     In case a new playback client connects to a room, these values can be
@@ -79,18 +86,23 @@ class Config:
     :type sources: Source
     :param sources_prio: A list defining the order of the search results.
     :type sources_prio: list[str]
-    :param preview_duration: The duration in seconds the playbackclients shows
-        a preview for the next song. This is accounted for in the calculation
-        of the ETA for songs later in the queue.
-    :type preview_duration: int
-    :param last_song: A timestamp, defining the end of the queue.
-    :type last_song: Optional[float]
+    :param config: Various configuration options for the client:
+        * `preview_duration` (`Optional[int]`): The duration in seconds the
+            playback client shows a preview for the next song. This is accounted for
+            in the calculation of the ETA for songs later in the queue.
+        * `last_song` (`Optional[float]`): A timestamp, defining the end of the queue.
+        * `waiting_room_policy` (Optional[str]): One of:
+            - `force`, if a performer is already in the queue, they are put in the
+                       waiting room.
+            - `optional`, if a performer is already in the queue, they have the option
+                          to be put in the waiting room.
+            - `None`, performers are always added to the queue.
+    :type config: dict[str, Any]:
     """
 
     sources: dict[str, Source]
     sources_prio: list[str]
-    preview_duration: int
-    last_song: Optional[float]
+    config: dict[str, Any]
 
 
 @dataclass
@@ -113,8 +125,8 @@ class State:
         a new playback client connects to a room (with the correct secret),
         this will be swapped with the new sid.
     :type sid: str
-    :param config: The config for the client
-    :type config: Config
+    :param client: The config for the playback client
+    :type client: Client
     """
 
     secret: str
@@ -122,7 +134,7 @@ class State:
     waiting_room: list[Entry]
     recent: list[Entry]
     sid: str
-    config: Config
+    client: Client
     last_seen: datetime.datetime = field(
         init=False, default_factory=datetime.datetime.now
     )
@@ -153,6 +165,7 @@ async def send_state(state: State, sid: str) -> None:
             "queue": state.queue,
             "recent": state.recent,
             "waiting_room": state.waiting_room,
+            "config": state.client.config,
         },
         room=sid,
     )
@@ -188,7 +201,7 @@ async def handle_waiting_room_append(sid: str, data: dict[str, Any]) -> None:
         room = session["room"]
     state = clients[room]
 
-    source_obj = state.config.sources[data["source"]]
+    source_obj = state.client.sources[data["source"]]
 
     entry = await source_obj.get_entry(data["performer"], data["ident"])
 
@@ -254,18 +267,23 @@ async def append_to_queue(
     start_time = state.queue.fold(
         lambda item, time: time
         + item.duration
-        + state.config.preview_duration
+        + state.client.config["preview_duration"]
         + 1,
         start_time,
     )
 
-    if state.config.last_song:
-        if state.config.last_song < start_time:
-            end_time = datetime.datetime.fromtimestamp(state.config.last_song)
+    if state.client.config["last_song"]:
+        if state.client.config["last_song"] < start_time:
+            # end_time = datetime.datetime.fromtimestamp(
+            #     state.client.config["last_song"]
+            # )
             if report_to is not None:
                 await sio.emit(
                     "err",
-                    {"type": "QUEUE_FULL", "end_time": state.config.last_song},
+                    {
+                        "type": "QUEUE_FULL",
+                        "end_time": state.client.config["last_song"],
+                    },
                     room=report_to,
                 )
             return
@@ -280,6 +298,57 @@ async def append_to_queue(
     )
 
 
+@sio.on("show_config")
+async def handle_show_config(sid: str) -> None:
+    """
+    Sends public config to webclient.
+
+    This will only be send if the client is on an admin connection.
+
+    :param sid: The session id of the client sending this request
+    :type sid: str
+    :rtype: None
+    """
+
+    async with sio.session(sid) as session:
+        room = session["room"]
+        is_admin = session["admin"]
+    state = clients[room]
+
+    if is_admin:
+        await sio.emit(
+            "config",
+            state.client.config,
+            sid,
+        )
+    else:
+        await sio.emit("err", {"type": "NO_ADMIN"}, sid)
+
+
+@sio.on("update_config")
+async def handle_update_config(sid: str, data: dict[str, Any]) -> None:
+    async with sio.session(sid) as session:
+        room = session["room"]
+        is_admin = session["admin"]
+    state = clients[room]
+
+    if is_admin:
+        try:
+            config = json.loads(data["config"])
+            await sio.emit(
+                "update_config",
+                DEFAULT_CONFIG | config,
+                state.sid,
+            )
+            state.client.config = DEFAULT_CONFIG | config
+            await sio.emit("update_config", config, room)
+        except JSONDecodeError:
+            await sio.emit("err", {"type": "JSON_MALFORMED"})
+
+    else:
+        await sio.emit("err", {"type": "NO_ADMIN"}, sid)
+
+
 @sio.on("append")
 async def handle_append(sid: str, data: dict[str, Any]) -> None:
     """
@@ -297,6 +366,10 @@ async def handle_append(sid: str, data: dict[str, Any]) -> None:
     request is denied and a "msg" message is send to the client, detailing
     this.
 
+    If a waitingroom is forced or optional, it is checked, if one of the performers is
+    already in queue. In that case, a "ask_for_waitingroom" message is send to the
+    client.
+
     Otherwise the song is added to the queue. And all connected clients (web
     and playback client) are informed of the new state with a "state" message.
 
@@ -316,7 +389,30 @@ async def handle_append(sid: str, data: dict[str, Any]) -> None:
         room = session["room"]
     state = clients[room]
 
-    source_obj = state.config.sources[data["source"]]
+    if state.client.config["waiting_room_policy"] and (
+        state.client.config["waiting_room_policy"].lower() == "force"
+        or state.client.config["waiting_room_policy"].lower() == "optional"
+    ):
+        old_entry = state.queue.find_by_name(data["performer"])
+        if old_entry is not None:
+            await sio.emit(
+                "ask_for_waitingroom",
+                {
+                    "current_entry": {
+                        "source": data["source"],
+                        "performer": data["performer"],
+                        "ident": data["ident"],
+                    },
+                    "old_entry": {
+                        "artist": old_entry.artist,
+                        "title": old_entry.title,
+                        "performer": old_entry.performer,
+                    },
+                },
+            )
+            return
+
+    source_obj = state.client.sources[data["source"]]
 
     entry = await source_obj.get_entry(data["performer"], data["ident"])
 
@@ -333,6 +429,47 @@ async def handle_append(sid: str, data: dict[str, Any]) -> None:
     await append_to_queue(room, entry, sid)
 
 
+@sio.on("append-anyway")
+async def handle_append_anyway(sid: str, data: dict[str, Any]) -> None:
+    """
+    Appends a song to the queue, even if the performer is already in queue.
+
+    Works the same as handle_append, but without the check if the performer is already
+    in queue.
+
+    Only if the waiting_room_policy is not configured as forced.
+    """
+    async with sio.session(sid) as session:
+        room = session["room"]
+    state = clients[room]
+
+    if state.client.config["waiting_room_policy"].lower() == "force":
+        await sio.emit(
+            "err",
+            {"type": "WAITING_ROOM_FORCED"},
+            room=sid,
+        )
+        return
+
+    source_obj = state.client.sources[data["source"]]
+
+    entry = await source_obj.get_entry(data["performer"], data["ident"])
+
+    if entry is None:
+        await sio.emit(
+            "msg",
+            {"msg": f"Unable to append {data['ident']}. Maybe try again?"},
+            room=sid,
+        )
+        return
+
+    entry.uid = data["uid"] if "uid" in data else None
+
+    print(entry)
+
+    await append_to_queue(room, entry, sid)
+
+
 @sio.on("meta-info")
 async def handle_meta_info(sid: str, data: dict[str, Any]) -> None:
     """
@@ -586,10 +723,10 @@ async def handle_register_client(sid: str, data: dict[str, Any]) -> None:
         if data["secret"] == old_state.secret:
             logger.info("Got new client connection for %s", room)
             old_state.sid = sid
-            old_state.config = Config(
-                sources=old_state.config.sources,
-                sources_prio=old_state.config.sources_prio,
-                **data["config"],
+            old_state.client = Client(
+                sources=old_state.client.sources,
+                sources_prio=old_state.client.sources_prio,
+                config=DEFAULT_CONFIG | data["config"],
             )
             await sio.enter_room(sid, room)
             await sio.emit(
@@ -615,8 +752,13 @@ async def handle_register_client(sid: str, data: dict[str, Any]) -> None:
             waiting_room=initial_waiting_room,
             recent=initial_recent,
             sid=sid,
-            config=Config(sources={}, sources_prio=[], **data["config"]),
+            client=Client(
+                sources={},
+                sources_prio=[],
+                config=DEFAULT_CONFIG | data["config"],
+            ),
         )
+
         await sio.enter_room(sid, room)
         await sio.emit(
             "client-registered", {"success": True, "room": room}, room=sid
@@ -653,13 +795,13 @@ async def handle_sources(sid: str, data: dict[str, Any]) -> None:
     if sid != state.sid:
         return
 
-    unused_sources = state.config.sources.keys() - data["sources"]
-    new_sources = data["sources"] - state.config.sources.keys()
+    unused_sources = state.client.sources.keys() - data["sources"]
+    new_sources = data["sources"] - state.client.sources.keys()
 
     for source in unused_sources:
-        del state.config.sources[source]
+        del state.client.sources[source]
 
-    state.config.sources_prio = data["sources"]
+    state.client.sources_prio = data["sources"]
 
     for name in new_sources:
         await sio.emit("request-config", {"source": name}, room=sid)
@@ -690,12 +832,12 @@ async def handle_config_chunk(sid: str, data: dict[str, Any]) -> None:
     if sid != state.sid:
         return
 
-    if not data["source"] in state.config.sources:
-        state.config.sources[data["source"]] = available_sources[
+    if data["source"] not in state.client.sources:
+        state.client.sources[data["source"]] = available_sources[
             data["source"]
         ](data["config"])
     else:
-        state.config.sources[data["source"]].add_to_config(data["config"])
+        state.client.sources[data["source"]].add_to_config(data["config"])
 
 
 @sio.on("config")
@@ -722,7 +864,7 @@ async def handle_config(sid: str, data: dict[str, Any]) -> None:
     if sid != state.sid:
         return
 
-    state.config.sources[data["source"]] = available_sources[data["source"]](
+    state.client.sources[data["source"]] = available_sources[data["source"]](
         data["config"]
     )
 
@@ -910,8 +1052,8 @@ async def handle_search(sid: str, data: dict[str, Any]) -> None:
     query = data["query"]
     results_list = await asyncio.gather(
         *[
-            state.config.sources[source].search(query)
-            for source in state.config.sources_prio
+            state.client.sources[source].search(query)
+            for source in state.client.sources_prio
         ]
     )