diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PageFactory.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PageFactory.java
index b396135fd..36a5e9b59 100644
--- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PageFactory.java
+++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PageFactory.java
@@ -248,6 +248,11 @@ public class PageFactory {
     }
 
     public Page registerPage() throws IOException {
+        if (config.get().isTrue(PluginSettings.FRONTEND_BETA)) {
+            String reactHtml = getResource("index.html");
+            return () -> reactHtml;
+        }
+
         return new LoginPage(getResource("register.html"), serverInfo.get(), locale.get(), theme.get(), versionChecker.get());
     }
 
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HtmlLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HtmlLang.java
index a04dec52b..693e4d445 100644
--- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HtmlLang.java
+++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HtmlLang.java
@@ -287,6 +287,7 @@ public enum HtmlLang implements Lang {
     REGISTER_COMPLETE_INSTRUCTIONS_2("html.register.completion2", "Code expires in 15 minutes"),
     REGISTER_COMPLETE_INSTRUCTIONS_3("html.register.completion3", "Use the following command in game to finish registration:"),
     REGISTER_COMPLETE_INSTRUCTIONS_4("html.register.completion4", "Or using console:"),
+    REGISTER_SUCCESS("html.register.success", "Registered a new user successfully! You can now login."),
     REGISTER_FAILED("html.register.error.failed", "Registration failed: "),
     REGISTER_CHECK_FAILED("html.register.error.checkFailed", "Checking registration status failed: "),
 
diff --git a/Plan/react/dashboard/src/App.js b/Plan/react/dashboard/src/App.js
index 2e25d97b0..fbe40cad1 100644
--- a/Plan/react/dashboard/src/App.js
+++ b/Plan/react/dashboard/src/App.js
@@ -28,7 +28,6 @@ const ServerPvpPve = React.lazy(() => import("./views/server/ServerPvpPve"));
 const PlayerbaseOverview = React.lazy(() => import("./views/server/PlayerbaseOverview"));
 const ServerPlayers = React.lazy(() => import("./views/server/ServerPlayers"));
 const ServerGeolocations = React.lazy(() => import("./views/server/ServerGeolocations"));
-const LoginPage = React.lazy(() => import("./views/layout/LoginPage"));
 const ServerPerformance = React.lazy(() => import("./views/server/ServerPerformance"));
 const ServerPluginData = React.lazy(() => import("./views/server/ServerPluginData"));
 const ServerWidePluginData = React.lazy(() => import("./views/server/ServerWidePluginData"));
@@ -50,6 +49,8 @@ const QueryPage = React.lazy(() => import("./views/layout/QueryPage"));
 const NewQueryView = React.lazy(() => import("./views/query/NewQueryView"));
 const QueryResultView = React.lazy(() => import("./views/query/QueryResultView"));
 
+const LoginPage = React.lazy(() => import("./views/layout/LoginPage"));
+const RegisterPage = React.lazy(() => import("./views/layout/RegisterPage"));
 const ErrorsPage = React.lazy(() => import("./views/layout/ErrorsPage"));
 const SwaggerView = React.lazy(() => import("./views/SwaggerView"));
 
@@ -90,6 +91,7 @@ function App() {
                             <Route path="" element={<MainPageRedirect/>}/>
                             <Route path="/" element={<MainPageRedirect/>}/>
                             <Route path="/login" element={<Lazy><LoginPage/></Lazy>}/>
+                            <Route path="/register" element={<Lazy><RegisterPage/></Lazy>}/>
                             <Route path="/player/:identifier" element={<Lazy><PlayerPage/></Lazy>}>
                                 <Route path="" element={<Lazy><OverviewRedirect/></Lazy>}/>
                                 <Route path="overview" element={<Lazy><PlayerOverview/></Lazy>}/>
diff --git a/Plan/react/dashboard/src/components/modal/FinalizeRegistrationModal.js b/Plan/react/dashboard/src/components/modal/FinalizeRegistrationModal.js
new file mode 100644
index 000000000..1d1ab22ec
--- /dev/null
+++ b/Plan/react/dashboard/src/components/modal/FinalizeRegistrationModal.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import {useTranslation} from "react-i18next";
+import {useMetadata} from "../../hooks/metadataHook";
+import {Modal} from "react-bootstrap-v5";
+import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
+import {faHandPointRight} from "@fortawesome/free-regular-svg-icons";
+
+const FinalizeRegistrationModal = ({show, toggle, registerCode}) => {
+    const {t} = useTranslation();
+    const {mainCommand} = useMetadata();
+
+    return (
+        <Modal id={"finalizeModal"}
+               aria-labelledby={"finalizeModalLable"}
+               show={show}
+               onHide={toggle}
+        >
+            <Modal.Header className="bg-white">
+                <Modal.Title id={"finalizeModalLabel"}>
+                    <Fa icon={faHandPointRight}/> {t('html.register.completion')}
+                </Modal.Title>
+                <button aria-label="Close" className="btn-close" onClick={toggle}/>
+            </Modal.Header>
+            <Modal.Body className={"bg-white"}>
+                <p>{t('html.register.completion1')} {t('html.register.completion2')}</p>
+                <p>{t('html.register.completion3')}</p>
+                <p><code>/{mainCommand} register --code {registerCode}</code></p>
+                <p>{t('html.register.completion4')}</p>
+                <p><code>{mainCommand} register superuser --code {registerCode}</code></p>
+            </Modal.Body>
+        </Modal>
+    )
+};
+
+export default FinalizeRegistrationModal
\ No newline at end of file
diff --git a/Plan/react/dashboard/src/service/authenticationService.js b/Plan/react/dashboard/src/service/authenticationService.js
index 29dc9daf0..cc275309b 100644
--- a/Plan/react/dashboard/src/service/authenticationService.js
+++ b/Plan/react/dashboard/src/service/authenticationService.js
@@ -8,4 +8,14 @@ export const fetchWhoAmI = async () => {
 export const fetchLogin = async (username, password) => {
     const url = '/auth/login';
     return doSomePostRequest(url, [standard200option], `user=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`);
+}
+
+export const postRegister = async (username, password) => {
+    const url = '/auth/register';
+    return doSomePostRequest(url, [standard200option], `user=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`);
+}
+
+export const fetchRegisterCheck = async (code) => {
+    const url = `/auth/register?code=${encodeURIComponent(code)}`;
+    return doGetRequest(url);
 }
\ No newline at end of file
diff --git a/Plan/react/dashboard/src/views/layout/LoginPage.js b/Plan/react/dashboard/src/views/layout/LoginPage.js
index ff10e9899..ac1256d6e 100644
--- a/Plan/react/dashboard/src/views/layout/LoginPage.js
+++ b/Plan/react/dashboard/src/views/layout/LoginPage.js
@@ -125,6 +125,7 @@ const LoginPage = () => {
 
     const [forgotPasswordModalOpen, setForgotPasswordModalOpen] = useState(false);
 
+    const [successMessage, setSuccessMessage] = useState('')
     const [failMessage, setFailMessage] = useState('');
     const [redirectTo, setRedirectTo] = useState(undefined);
 
@@ -138,6 +139,9 @@ const LoginPage = () => {
         const cameFrom = urlParams.get('from');
         if (cameFrom) setRedirectTo(cameFrom);
 
+        const registerSuccess = urlParams.get('registerSuccess');
+        if (registerSuccess) setSuccessMessage(t('html.register.success'))
+
         return () => {
             document.body.classList.remove("bg-plan", "plan-bg-gradient");
         }
@@ -189,8 +193,9 @@ const LoginPage = () => {
                 <Logo/>
                 <LoginCard>
                     {failMessage && <Alert className='alert-danger'>{failMessage}</Alert>}
+                    {successMessage && <Alert className='alert-success'>{successMessage}</Alert>}
                     <LoginForm login={login}/>
-                    <hr className="bg-secondary"/>
+                    <hr className="col-secondary"/>
                     <ForgotPasswordButton onClick={togglePasswordModal}/>
                     <CreateAccountLink/>
                     <ColorChooserButton/>
diff --git a/Plan/react/dashboard/src/views/layout/RegisterPage.js b/Plan/react/dashboard/src/views/layout/RegisterPage.js
new file mode 100644
index 000000000..7b214f415
--- /dev/null
+++ b/Plan/react/dashboard/src/views/layout/RegisterPage.js
@@ -0,0 +1,200 @@
+import React, {useCallback, useEffect, useState} from 'react';
+
+import logo from '../../Flaticon_circle.png'
+import {Alert, Card, Col, Row} from "react-bootstrap-v5";
+import {Link, useNavigate} from "react-router-dom";
+import {useTranslation} from "react-i18next";
+import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
+import {faPalette} from "@fortawesome/free-solid-svg-icons";
+import {useTheme} from "../../hooks/themeHook";
+import ColorSelectorModal from "../../components/modal/ColorSelectorModal";
+import {useAuth} from "../../hooks/authenticationHook";
+import FinalizeRegistrationModal from "../../components/modal/FinalizeRegistrationModal";
+import {fetchRegisterCheck, postRegister} from "../../service/authenticationService";
+import {baseAddress} from "../../service/backendConfiguration";
+
+const Logo = () => {
+    return (
+        <Col md={12} className='mt-5 text-center'>
+            <img alt="logo" className="w-15" src={logo}/>
+        </Col>
+    )
+};
+
+const RegisterCard = ({children}) => {
+    return (
+        <Row className="justify-content-center container-fluid">
+            <Col xl={6} lg={7} md={9}>
+                <Card className='o-hidden border-0 shadow-lg my-5'>
+                    <Card.Body className='p-0'>
+                        <Row>
+                            <Col lg={12}>
+                                <div className='p-5'>
+                                    {children}
+                                </div>
+                            </Col>
+                        </Row>
+                    </Card.Body>
+                </Card>
+            </Col>
+        </Row>
+    )
+}
+
+const RegisterForm = ({register}) => {
+    const {t} = useTranslation();
+
+    const [username, setUsername] = useState('');
+    const [password, setPassword] = useState('');
+
+    const onRegister = useCallback(event => {
+        event.preventDefault();
+        register(username, password).then(() => setPassword(''));
+    }, [username, password, setPassword, register]);
+
+    return (
+        <form className="user">
+            <div className="mb-3">
+                <input autoComplete="username" className="form-control form-control-user"
+                       id="inputUser"
+                       placeholder={t('html.login.username')} type="text"
+                       value={username} onChange={event => setUsername(event.target.value)}/>
+                <div className={"form-text"}>{t('html.register.usernameTip')}</div>
+            </div>
+            <div className="mb-3">
+                <input autoComplete="current-password" className="form-control form-control-user"
+                       id="inputPassword" placeholder={t('html.login.password')} type="password"
+                       value={password} onChange={event => setPassword(event.target.value)}/>
+                <div className={"form-text"}>{t('html.register.passwordTip')}</div>
+            </div>
+            <button className="btn bg-plan btn-user w-100" id="register-button" onClick={onRegister}>
+                {t('html.register.register')}
+            </button>
+        </form>
+    );
+}
+
+const ColorChooserButton = () => {
+    const {t} = useTranslation();
+    const {toggleColorChooser} = useTheme();
+
+    return (
+        <div className='text-center'>
+            <button className="btn col-plan" onClick={toggleColorChooser}
+                    title={t('html.label.themeSelect')}>
+                <Fa icon={faPalette}/>
+            </button>
+        </div>
+    )
+}
+
+const LoginLink = () => {
+    const {t} = useTranslation();
+
+    return (
+        <div className='text-center'>
+            <Link to='/login' className='col-plan small'>{t('html.register.login')}</Link>
+        </div>
+    )
+}
+
+const RegisterPage = () => {
+    const {t} = useTranslation();
+    const navigate = useNavigate();
+    const {authLoaded, authRequired, loggedIn} = useAuth();
+
+    const [finalizeRegistrationModalOpen, setFinalizeRegistrationModalOpen] = useState(false);
+
+    const [registerCode, setRegisterCode] = useState(undefined);
+    const [failMessage, setFailMessage] = useState('');
+
+    const toggleRegistrationModal = useCallback(() => setFinalizeRegistrationModalOpen(!finalizeRegistrationModalOpen),
+        [setFinalizeRegistrationModalOpen, finalizeRegistrationModalOpen])
+
+    useEffect(() => {
+        document.body.classList.add("bg-plan", "plan-bg-gradient");
+
+        return () => {
+            document.body.classList.remove("bg-plan", "plan-bg-gradient");
+        }
+    }, []);
+
+    const checkRegistration = async (code) => {
+        if (!code) {
+            setFinalizeRegistrationModalOpen(false);
+            return setFailMessage("Register code was not received.");
+        }
+        if (!finalizeRegistrationModalOpen) {
+            setFinalizeRegistrationModalOpen(true);
+        }
+
+        const {data, error} = await fetchRegisterCheck(code);
+        if (error) {
+            setFailMessage(t('html.register.error.checkFailed') + error)
+        } else if (data && data.success) {
+            navigate(baseAddress + '/login?registerSuccess=true');
+        } else {
+            setTimeout(() => checkRegistration(code), 5000);
+        }
+    }
+
+    const register = async (username, password) => {
+        if (!username || username.length < 1) {
+            return setFailMessage(t('html.register.error.noUsername'));
+        }
+        if (username.length > 50) {
+            return setFailMessage(t('html.register.error.usernameLength') + username.length);
+        }
+        if (!password || password.length < 1) {
+            return setFailMessage(t('html.register.error.noPassword'));
+        }
+
+        const {data, error} = await postRegister(username, password);
+
+        if (error) {
+            setFailMessage(t('html.register.error.failed') + (error.data && error.data.error ? error.data.error : error.message));
+        } else if (data && data.code) {
+            setRegisterCode(data.code);
+            setFinalizeRegistrationModalOpen(true);
+            setTimeout(() => checkRegistration(data.code), 10000);
+        } else {
+            setFailMessage(t('html.register.error.failed') + data ? data.error : t('generic.noData'));
+        }
+    }
+
+    if (!authLoaded) {
+        return <></>
+    }
+
+    if (!authRequired || loggedIn) {
+        navigate('../');
+    }
+
+    return (
+        <>
+            <main className="container">
+                <Logo/>
+                <RegisterCard>
+                    <div className="text-center">
+                        <h1 className="h4 text-gray-900 mb-4">{t('html.register.createNewUser')}</h1>
+                    </div>
+                    {failMessage && <Alert className='alert-danger'>{failMessage}</Alert>}
+                    <RegisterForm register={register}/>
+                    <hr className="col-secondary"/>
+                    <LoginLink/>
+                    <ColorChooserButton/>
+                </RegisterCard>
+            </main>
+            <aside>
+                <ColorSelectorModal/>
+                <FinalizeRegistrationModal
+                    show={finalizeRegistrationModalOpen}
+                    toggle={toggleRegistrationModal}
+                    registerCode={registerCode}
+                />
+            </aside>
+        </>
+    )
+};
+
+export default RegisterPage
\ No newline at end of file