diff --git a/ui/artalk-sidebar/src/App.vue b/ui/artalk-sidebar/src/App.vue
index 575436cd..38c30f13 100644
--- a/ui/artalk-sidebar/src/App.vue
+++ b/ui/artalk-sidebar/src/App.vue
@@ -67,7 +67,7 @@ function syncArtalk(artalk: Artalk) {
     .getApi()
     .user.getUserStatus({
       email: artalkUserData.email,
-      name: artalkUserData.nick,
+      name: artalkUserData.name,
     })
     .then((res) => {
       if (res.data.is_admin && !res.data.is_login) {
@@ -76,7 +76,7 @@ function syncArtalk(artalk: Artalk) {
         // 将全部通知标记为已读
         artalk.ctx.getApi().notifies.markAllNotifyRead({
           email: artalkUserData.email,
-          name: artalkUserData.nick,
+          name: artalkUserData.name,
         })
       }
     })
diff --git a/ui/artalk-sidebar/src/components/AppHeader.vue b/ui/artalk-sidebar/src/components/AppHeader.vue
index 20b8c1ed..ffa98a31 100644
--- a/ui/artalk-sidebar/src/components/AppHeader.vue
+++ b/ui/artalk-sidebar/src/components/AppHeader.vue
@@ -8,7 +8,7 @@ const nav = useNavStore()
 const router = useRouter()
 const user = useUserStore()
 const { t } = useI18n()
-const { site: curtSite, isAdmin, avatar } = storeToRefs(user)
+const { site: curtSite, is_admin: isAdmin, avatar } = storeToRefs(user)
 
 const avatarClickHandler = () => {
   if (!isOpenFromSidebar()) logout()
diff --git a/ui/artalk-sidebar/src/components/AppNavigationMenu.ts b/ui/artalk-sidebar/src/components/AppNavigationMenu.ts
index d26417f8..f9760ed1 100644
--- a/ui/artalk-sidebar/src/components/AppNavigationMenu.ts
+++ b/ui/artalk-sidebar/src/components/AppNavigationMenu.ts
@@ -79,7 +79,7 @@ export interface SearchStateApi {
  */
 export const useNavigationMenu = (props: NavigationStoreProps = {}) => {
   const router = useRouter()
-  const { isAdmin } = storeToRefs(useUserStore())
+  const { is_admin: isAdmin } = storeToRefs(useUserStore())
   const { tabs, curtPage, curtTab, isMobile, isSearchEnabled } = storeToRefs(useNavStore())
 
   /**
diff --git a/ui/artalk-sidebar/src/global.ts b/ui/artalk-sidebar/src/global.ts
index f501db45..312b2a54 100644
--- a/ui/artalk-sidebar/src/global.ts
+++ b/ui/artalk-sidebar/src/global.ts
@@ -1,5 +1,5 @@
 import Artalk from 'artalk'
-import type { ArtalkType } from 'artalk'
+import type { LocalUser } from 'artalk'
 
 export let artalk: Artalk | null = null
 
@@ -24,10 +24,19 @@ function getBootParams() {
     window.history.replaceState({}, '', window.location.pathname)
   }
 
+  const userFromURL = JSON.parse(p.get('user') || '{}')
+  const user: LocalUser = {
+    name: userFromURL.name || '',
+    email: userFromURL.email || '',
+    link: userFromURL.link || '',
+    token: userFromURL.token || '',
+    is_admin: userFromURL.is_admin || false,
+  }
+
   return {
+    user,
     pageKey: p.get('pageKey') || '',
     site: p.get('site') || '',
-    user: <ArtalkType.LocalUser>JSON.parse(p.get('user') || '{}'),
     view: p.get('view') || '',
     viewParams: <any>null,
     darkMode: p.get('darkMode') === '1',
diff --git a/ui/artalk-sidebar/src/pages/comments.vue b/ui/artalk-sidebar/src/pages/comments.vue
index 60d764b9..ea9f19da 100644
--- a/ui/artalk-sidebar/src/pages/comments.vue
+++ b/ui/artalk-sidebar/src/pages/comments.vue
@@ -15,7 +15,7 @@ const search = ref('')
 
 onMounted(() => {
   // 初始化导航条
-  if (user.isAdmin) {
+  if (user.is_admin) {
     nav.updateTabs(
       {
         all: 'all',
@@ -55,7 +55,7 @@ onMounted(() => {
     listFetchParamsModifier: (params) => {
       params.site_name = curtSite.value // 站点名
 
-      let scope = user.isAdmin ? 'site' : 'user'
+      let scope = user.is_admin ? 'site' : 'user'
       let type = curtTab.value
 
       if (curtTab.value === 'personal_all') {
diff --git a/ui/artalk-sidebar/src/pages/login.vue b/ui/artalk-sidebar/src/pages/login.vue
index d4c1dadf..5b08c48c 100644
--- a/ui/artalk-sidebar/src/pages/login.vue
+++ b/ui/artalk-sidebar/src/pages/login.vue
@@ -51,12 +51,8 @@ function login(username?: string) {
       password: userForm.value.password,
     })
     .then((res) => {
-      const user = res.data.user
       artalk.ctx.get('user').update({
-        nick: user.name,
-        email: user.email,
-        link: user.link,
-        isAdmin: user.is_admin,
+        ...res.data.user,
         token: res.data.token,
       })
       useUserStore().sync()
diff --git a/ui/artalk-sidebar/src/stores/user.ts b/ui/artalk-sidebar/src/stores/user.ts
index e18303e1..65c5aed5 100644
--- a/ui/artalk-sidebar/src/stores/user.ts
+++ b/ui/artalk-sidebar/src/stores/user.ts
@@ -1,42 +1,36 @@
+import type { LocalUser } from 'artalk'
 import { defineStore } from 'pinia'
 import sha256 from 'crypto-js/sha256'
 import md5 from 'crypto-js/md5'
 import { bootParams, getArtalk } from '../global'
 
+interface UserState extends LocalUser {
+  /**
+   * Current site name
+   */
+  site: string
+}
+
 export const useUserStore = defineStore('user', {
-  state: () => ({
-    site: bootParams.site || '',
-    name: bootParams.user.nick || '',
-    email: bootParams.user.email || '',
-    isAdmin: bootParams.user.isAdmin || false,
-    token: bootParams.user.token || '',
-  }),
+  state: () =>
+    <UserState>{
+      site: bootParams.site || '',
+      ...bootParams.user,
+    },
   actions: {
     logout() {
-      this.site = ''
-      this.name = ''
-      this.email = ''
-      this.isAdmin = false
-      this.token = ''
-
+      this.$reset()
       getArtalk()?.ctx.get('user').logout()
     },
     sync() {
       const user = getArtalk()?.ctx.get('user')
       if (!user) throw new Error('Artalk is not initialized')
       if (!user.checkHasBasicUserInfo()) throw new Error('User is not logged in')
-      const userData = user.getData()
-      this.site = ''
-      this.name = userData.nick
-      this.email = userData.email
-      this.isAdmin = userData.isAdmin
-      this.token = userData.token
+      this.$patch({ ...user.getData(), site: '' })
     },
   },
   getters: {
-    avatar: (state) => {
-      return getGravatar(state.email)
-    },
+    avatar: (state) => getGravatar(state.email),
   },
 })
 
diff --git a/ui/artalk/src/components/checker/admin.ts b/ui/artalk/src/components/checker/admin.ts
index bc26e7ef..954bef55 100644
--- a/ui/artalk/src/components/checker/admin.ts
+++ b/ui/artalk/src/components/checker/admin.ts
@@ -8,7 +8,7 @@ const AdminChecker: Checker<{ token: string }> = {
   async request(checker, inputVal) {
     return (
       await checker.getApi().user.login({
-        name: checker.getUser().getData().nick,
+        name: checker.getUser().getData().name,
         email: checker.getUser().getData().email,
         password: inputVal,
       })
@@ -21,7 +21,7 @@ const AdminChecker: Checker<{ token: string }> = {
 
   onSuccess(checker, res, inputVal, formEl) {
     checker.getUser().update({
-      isAdmin: true,
+      is_admin: true,
       token: res.token,
     })
     checker.getOpts().onReload()
diff --git a/ui/artalk/src/config.ts b/ui/artalk/src/config.ts
index dc69f215..62de44a3 100644
--- a/ui/artalk/src/config.ts
+++ b/ui/artalk/src/config.ts
@@ -105,7 +105,7 @@ export function convertApiOptions(conf: Partial<ArtalkConfig>, ctx?: ContextApi)
     getApiToken: () => ctx?.get('user').getData().token,
     userInfo: ctx?.get('user').checkHasBasicUserInfo()
       ? {
-          name: ctx?.get('user').getData().nick,
+          name: ctx?.get('user').getData().name,
           email: ctx?.get('user').getData().email,
         }
       : undefined,
diff --git a/ui/artalk/src/editor/editor.html b/ui/artalk/src/editor/editor.html
index 9f767418..9d6a4215 100644
--- a/ui/artalk/src/editor/editor.html
+++ b/ui/artalk/src/editor/editor.html
@@ -1,6 +1,6 @@
 <div class="atk-main-editor">
   <div class="atk-header">
-    <input name="nick" class="atk-nick" type="text" required="required" />
+    <input name="name" class="atk-name" type="text" required="required" />
     <input name="email" class="atk-email" type="email" required="required" />
     <input name="link" class="atk-link" type="url" />
   </div>
diff --git a/ui/artalk/src/editor/editor.ts b/ui/artalk/src/editor/editor.ts
index e7b4e0de..6a3678d7 100644
--- a/ui/artalk/src/editor/editor.ts
+++ b/ui/artalk/src/editor/editor.ts
@@ -31,7 +31,7 @@ class Editor extends Component implements EditorApi {
   }
 
   getHeaderInputEls() {
-    return { nick: this.ui.$nick, email: this.ui.$email, link: this.ui.$link }
+    return { name: this.ui.$name, email: this.ui.$email, link: this.ui.$link }
   }
 
   getContentFinal() {
diff --git a/ui/artalk/src/editor/ui.ts b/ui/artalk/src/editor/ui.ts
index c54b09a3..8cfb34c0 100644
--- a/ui/artalk/src/editor/ui.ts
+++ b/ui/artalk/src/editor/ui.ts
@@ -3,7 +3,7 @@ import EditorHTML from './editor.html?raw'
 
 const Sel = {
   $header: '.atk-header',
-  $nick: '.atk-header [name="nick"]',
+  $name: '.atk-header [name="name"]',
   $email: '.atk-header [name="email"]',
   $link: '.atk-header [name="link"]',
   $textareaWrap: '.atk-textarea-wrap',
@@ -19,7 +19,7 @@ const Sel = {
 
 export interface EditorUI extends Record<keyof typeof Sel, HTMLElement> {
   $el: HTMLElement
-  $nick: HTMLInputElement
+  $name: HTMLInputElement
   $email: HTMLInputElement
   $link: HTMLInputElement
   $textarea: HTMLTextAreaElement
diff --git a/ui/artalk/src/lib/user.ts b/ui/artalk/src/lib/user.ts
index fec4f76c..8d0de67a 100644
--- a/ui/artalk/src/lib/user.ts
+++ b/ui/artalk/src/lib/user.ts
@@ -15,11 +15,11 @@ class User {
 
     // Initialize
     this.data = {
-      nick: localUser.nick || '',
+      name: localUser.name || localUser.nick || '', // nick is deprecated (for historical compatibility)
       email: localUser.email || '',
       link: localUser.link || '',
       token: localUser.token || '',
-      isAdmin: localUser.isAdmin || false,
+      is_admin: localUser.is_admin || localUser.isAdmin || false,
     }
   }
 
@@ -40,18 +40,18 @@ class User {
   /**
    * Logout
    *
-   * @description Logout will clear login status, but not clear user data (nick, email, link)
+   * @description Logout will clear login status, but not clear user data (name, email, link)
    */
   logout() {
     this.update({
       token: '',
-      isAdmin: false,
+      is_admin: false,
     })
   }
 
   /** Check if user has filled basic data */
   checkHasBasicUserInfo() {
-    return !!this.data.nick && !!this.data.email
+    return !!this.data.name && !!this.data.email
   }
 }
 
diff --git a/ui/artalk/src/load.ts b/ui/artalk/src/load.ts
index 878c1e20..1fb43a8d 100644
--- a/ui/artalk/src/load.ts
+++ b/ui/artalk/src/load.ts
@@ -138,7 +138,7 @@ export function onLoadErr(ctx: ContextApi, err: any) {
     errMsg: err.msg || String(err),
     errData: err.data,
     retryFn: () => load(ctx),
-    onOpenSidebar: ctx.get('user').getData().isAdmin
+    onOpenSidebar: ctx.get('user').getData().is_admin
       ? () =>
           ctx.showSidebar({
             view: sidebarOpenView as any,
diff --git a/ui/artalk/src/plugins/admin-only-elem.ts b/ui/artalk/src/plugins/admin-only-elem.ts
index d9ed6b31..7f7b3c1b 100644
--- a/ui/artalk/src/plugins/admin-only-elem.ts
+++ b/ui/artalk/src/plugins/admin-only-elem.ts
@@ -3,7 +3,7 @@ import type { ArtalkPlugin } from '@/types'
 export const AdminOnlyElem: ArtalkPlugin = (ctx) => {
   const scanApply = () => {
     applyAdminOnlyEls(
-      ctx.get('user').getData().isAdmin,
+      ctx.get('user').getData().is_admin,
       getAdminOnlyEls({
         $root: ctx.$root,
       }),
diff --git a/ui/artalk/src/plugins/editor/closable.ts b/ui/artalk/src/plugins/editor/closable.ts
index c5f2b98b..58ac8959 100644
--- a/ui/artalk/src/plugins/editor/closable.ts
+++ b/ui/artalk/src/plugins/editor/closable.ts
@@ -34,7 +34,7 @@ export default class Closable extends EditorPlug {
           Utils.createElement(`<div class="atk-comment-closed">${$t('onlyAdminCanReply')}</div>`),
         )
 
-    if (!this.kit.useUser().getData().isAdmin) {
+    if (!this.kit.useUser().getData().is_admin) {
       this.kit.useUI().$textarea.style.display = 'none'
       this.kit.useEvents().trigger('panel-close')
       this.kit.useUI().$bottom.style.display = 'none'
diff --git a/ui/artalk/src/plugins/editor/header-user.ts b/ui/artalk/src/plugins/editor/header-user.ts
index a6ba2e45..1eb5113c 100644
--- a/ui/artalk/src/plugins/editor/header-user.ts
+++ b/ui/artalk/src/plugins/editor/header-user.ts
@@ -14,13 +14,19 @@ export default class HeaderUser extends EditorPlug {
       this.kit.useUser().update({ [field]: $input.value.trim() })
 
       // remote fetch user info
-      if (field === 'nick' || field === 'email') this.fetchUserInfo() // must after update user data, since fetchUserInfo() will use User.data
+      if (field === 'name' || field === 'email') this.fetchUserInfo() // must after update user data, since fetchUserInfo() will use User.data
+    }
+
+    const placeholders = {
+      name: $t('nick'),
+      email: $t('email'),
+      link: $t('link'),
     }
 
     this.kit.useMounted(() => {
       Object.entries(this.kit.useEditor().getHeaderInputEls()).forEach(([key, $input]) => {
         // set placeholder
-        $input.placeholder = `${$t(key as any)}`
+        $input.placeholder = placeholders[key]
 
         // sync header values from User.data
         $input.value = this.kit.useUser().getData()[key] || ''
diff --git a/ui/artalk/src/plugins/editor/state-edit.ts b/ui/artalk/src/plugins/editor/state-edit.ts
index 68279216..834d69d7 100644
--- a/ui/artalk/src/plugins/editor/state-edit.ts
+++ b/ui/artalk/src/plugins/editor/state-edit.ts
@@ -30,7 +30,7 @@ export default class StateEdit extends EditorPlug {
         req: async () => {
           const saveData = {
             content: this.kit.useEditor().getContentFinal(),
-            nick: this.kit.useUI().$nick.value,
+            nick: this.kit.useUI().$name.value,
             email: this.kit.useUI().$email.value,
             link: this.kit.useUI().$link.value,
           }
@@ -69,7 +69,7 @@ export default class StateEdit extends EditorPlug {
 
     ui.$header.style.display = 'none' // TODO: support modify header information
 
-    ui.$nick.value = comment.nick || ''
+    ui.$name.value = comment.nick || ''
     ui.$email.value = comment.email || ''
     ui.$link.value = comment.link || ''
 
@@ -91,8 +91,8 @@ export default class StateEdit extends EditorPlug {
 
     this.comment = undefined
 
-    const { nick, email, link } = this.kit.useUser().getData()
-    ui.$nick.value = nick
+    const { name, email, link } = this.kit.useUser().getData()
+    ui.$name.value = name
     ui.$email.value = email
     ui.$link.value = link
 
diff --git a/ui/artalk/src/plugins/editor/submit-add.ts b/ui/artalk/src/plugins/editor/submit-add.ts
index 0f3322df..62ca0d62 100644
--- a/ui/artalk/src/plugins/editor/submit-add.ts
+++ b/ui/artalk/src/plugins/editor/submit-add.ts
@@ -15,12 +15,12 @@ export default class SubmitAddPreset {
   }
 
   async getSubmitAddParams() {
-    const { nick, email, link } = this.kit.useUser().getData()
+    const { name, email, link } = this.kit.useUser().getData()
     const conf = this.kit.useConf()
 
     return {
       content: this.kit.useEditor().getContentFinal(),
-      name: nick,
+      name,
       email,
       link,
       rid: 0,
diff --git a/ui/artalk/src/plugins/list/sidebar-btn.ts b/ui/artalk/src/plugins/list/sidebar-btn.ts
index 139ab75f..2f9c82e5 100644
--- a/ui/artalk/src/plugins/list/sidebar-btn.ts
+++ b/ui/artalk/src/plugins/list/sidebar-btn.ts
@@ -9,12 +9,12 @@ export const SidebarBtn: ArtalkPlugin = (ctx) => {
     const user = ctx.get('user').getData()
 
     // 已输入个人信息
-    if (!!user.nick && !!user.email) {
+    if (!!user.name && !!user.email) {
       $openSidebarBtn.classList.remove('atk-hide')
 
       // update button text (normal user or admin)
       const $btnText = $openSidebarBtn.querySelector<HTMLElement>('.atk-text')
-      if ($btnText) $btnText.innerText = !user.isAdmin ? $t('msgCenter') : $t('ctrlCenter')
+      if ($btnText) $btnText.innerText = !user.is_admin ? $t('msgCenter') : $t('ctrlCenter')
     } else {
       $openSidebarBtn.classList.add('atk-hide')
     }
diff --git a/ui/artalk/src/types/config.ts b/ui/artalk/src/types/config.ts
index 2ba54d64..74c72bd3 100644
--- a/ui/artalk/src/types/config.ts
+++ b/ui/artalk/src/types/config.ts
@@ -155,22 +155,23 @@ export interface ArtalkConfig {
 }
 
 /**
- * 本地持久化用户数据
- * @note 始终保持一层结构,不支持多层结构
+ * Local User Data (in localStorage)
+ *
+ * @note Keep flat for easy handling
  */
 export interface LocalUser {
-  /** 昵称 */
-  nick: string
+  /** Username (aka. Nickname) */
+  name: string
 
-  /** 邮箱 */
+  /** Email */
   email: string
 
-  /** 链接 */
+  /** Link (aka. Website) */
   link: string
 
-  /** TOKEN */
+  /** Token (for authorization) */
   token: string
 
-  /** 是否为管理员 */
-  isAdmin: boolean
+  /** Admin flag */
+  is_admin: boolean
 }
diff --git a/ui/plugin-auth/EditorUser.tsx b/ui/plugin-auth/EditorUser.tsx
index be6bd94b..59260917 100644
--- a/ui/plugin-auth/EditorUser.tsx
+++ b/ui/plugin-auth/EditorUser.tsx
@@ -7,10 +7,10 @@ const EditorUser = ({ ctx }: { ctx: ContextApi }) => {
     window.confirm(ctx.$t('logoutConfirm')) &&
       ctx.get('user').update({
         token: '',
-        nick: '',
+        name: '',
         email: '',
         link: '',
-        isAdmin: false,
+        is_admin: false,
       })
   }
 
@@ -33,7 +33,7 @@ const EditorUser = ({ ctx }: { ctx: ContextApi }) => {
         <div class="atk-editor-user">
           <div class="atk-user-profile-btn atk-user-btn">
             {/* <div class="atk-avatar" style={{ "background-image": `url('https://avatars.githubusercontent.com/u/76841221?s=200&v=4')` }}></div> */}
-            <div class="atk-name">{user().nick}</div>
+            <div class="atk-name">{user().name}</div>
           </div>
           <div class="atk-logout atk-user-btn" onClick={logoutHandler} aria-label="Logout">
             <svg
diff --git a/ui/plugin-auth/lib/token-login.ts b/ui/plugin-auth/lib/token-login.ts
index d05280c7..d7925c90 100644
--- a/ui/plugin-auth/lib/token-login.ts
+++ b/ui/plugin-auth/lib/token-login.ts
@@ -1,22 +1,13 @@
-import type { ContextApi } from 'artalk'
+import type { ContextApi, LocalUser } from 'artalk'
 
 interface ResponseLoginData {
-  user: {
-    name: string
-    email: string
-    link: string
-    is_admin: boolean
-  }
+  user: LocalUser
   token: string
 }
 
-export const loginByApiRes = (ctx: ContextApi, data: ResponseLoginData) => {
-  const { user, token } = data
+export const loginByApiRes = (ctx: ContextApi, { user, token }: ResponseLoginData) => {
   ctx.get('user').update({
-    nick: user.name,
-    email: user.email,
-    link: user.link,
-    isAdmin: user.is_admin,
+    ...user,
     token,
   })
 }
@@ -29,10 +20,10 @@ export const loginByToken = (ctx: ContextApi, token: string) => {
     .then((res) => {
       const { user } = res.data
       ctx.get('user').update({
-        nick: user.name,
+        name: user.name,
         email: user.email,
         link: user.link,
-        isAdmin: user.is_admin,
+        is_admin: user.is_admin,
       })
     })
 }
diff --git a/ui/plugin-auth/main.tsx b/ui/plugin-auth/main.tsx
index 067f0496..ce11bd46 100644
--- a/ui/plugin-auth/main.tsx
+++ b/ui/plugin-auth/main.tsx
@@ -30,7 +30,7 @@ export const ArtalkAuthPlugin: ArtalkPlugin = (ctx) => {
 
   const onSkip = () => {
     ctx.get('editor').getUI().$header.style.display = ''
-    ctx.get('editor').getUI().$nick.focus()
+    ctx.get('editor').getUI().$name.focus()
     ctx.updateConf({
       beforeSubmit: undefined,
     })