diff --git a/src/main/java/run/halo/app/controller/content/api/OptionController.java b/src/main/java/run/halo/app/controller/content/api/OptionController.java
index 7160a3f31..e5d1bcd8d 100644
--- a/src/main/java/run/halo/app/controller/content/api/OptionController.java
+++ b/src/main/java/run/halo/app/controller/content/api/OptionController.java
@@ -1,7 +1,5 @@
 package run.halo.app.controller.content.api;
 
-import static java.util.stream.Collectors.toMap;
-
 import io.swagger.annotations.ApiOperation;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -18,8 +16,7 @@ import org.springframework.web.bind.annotation.RestController;
 import run.halo.app.model.dto.OptionDTO;
 import run.halo.app.model.properties.CommentProperties;
 import run.halo.app.model.support.BaseResponse;
-import run.halo.app.service.OptionService;
-import run.halo.app.service.impl.OptionFilter;
+import run.halo.app.service.ClientOptionService;
 
 /**
  * Content option controller.
@@ -31,27 +28,16 @@ import run.halo.app.service.impl.OptionFilter;
 @RequestMapping("/api/content/options")
 public class OptionController {
 
-    private final OptionService optionService;
+    private final ClientOptionService optionService;
 
-    private final OptionFilter optionFilter;
-
-    public OptionController(OptionService optionService) {
-        this.optionService = optionService;
-        optionFilter = new OptionFilter(optionService);
+    public OptionController(ClientOptionService clientOptionService) {
+        this.optionService = clientOptionService;
     }
 
     @GetMapping("list_view")
     @ApiOperation("Lists all options with list view")
     public List<OptionDTO> listAll() {
-        var options = optionService.listDtos();
-        var optionMap = options.stream()
-            .collect(toMap(OptionDTO::getKey, option -> option));
-        var keys = options.stream()
-            .map(OptionDTO::getKey)
-            .collect(Collectors.toUnmodifiableSet());
-        return optionFilter.filter(keys).stream()
-            .map(optionMap::get)
-            .collect(Collectors.toUnmodifiableList());
+        return optionService.listDtos();
     }
 
     @GetMapping("map_view")
@@ -62,28 +48,23 @@ public class OptionController {
         @RequestParam(value = "keys", required = false) String keys) {
         // handle for key list
         if (!CollectionUtils.isEmpty(keyList)) {
-            return optionService.listOptions(optionFilter.filter(keyList));
+            return optionService.listOptions(keyList);
         }
         // handle for keys
         if (StringUtils.hasText(keys)) {
             var nameSet = Arrays.stream(keys.split(","))
                 .map(String::trim)
                 .collect(Collectors.toUnmodifiableSet());
-            var filteredNames = optionFilter.filter(nameSet);
-            return optionService.listOptions(filteredNames);
+            return optionService.listOptions(nameSet);
         }
         // list all
-        Map<String, Object> options = optionService.listOptions();
-        return optionFilter.filter(options.keySet()).stream()
-            .collect(toMap(optionName -> optionName, options::get));
+        return optionService.listOptions();
     }
 
     @GetMapping("keys/{key}")
     @ApiOperation("Gets option value by option key")
     public BaseResponse<Object> getBy(@PathVariable("key") String key) {
-        Object optionValue = optionFilter.filter(key)
-            .flatMap(k -> optionService.getByKey(key))
-            .orElse(null);
+        Object optionValue = optionService.getByKey(key).orElse(null);
         return BaseResponse.ok(optionValue);
     }
 
@@ -95,7 +76,7 @@ public class OptionController {
         keys.add(CommentProperties.GRAVATAR_DEFAULT.getValue());
         keys.add(CommentProperties.CONTENT_PLACEHOLDER.getValue());
         keys.add(CommentProperties.GRAVATAR_SOURCE.getValue());
-        return optionService.listOptions(optionFilter.filter(keys));
+        return optionService.listOptions(keys);
     }
 
 }
diff --git a/src/main/java/run/halo/app/listener/freemarker/FreemarkerConfigAwareListener.java b/src/main/java/run/halo/app/listener/freemarker/FreemarkerConfigAwareListener.java
index 9c3777571..1f7131c1b 100644
--- a/src/main/java/run/halo/app/listener/freemarker/FreemarkerConfigAwareListener.java
+++ b/src/main/java/run/halo/app/listener/freemarker/FreemarkerConfigAwareListener.java
@@ -25,6 +25,7 @@ import run.halo.app.event.user.UserUpdatedEvent;
 import run.halo.app.model.properties.BlogProperties;
 import run.halo.app.model.properties.SeoProperties;
 import run.halo.app.model.support.HaloConst;
+import run.halo.app.service.ClientOptionService;
 import run.halo.app.service.OptionService;
 import run.halo.app.service.ThemeService;
 import run.halo.app.service.ThemeSettingService;
@@ -41,7 +42,7 @@ import run.halo.app.service.UserService;
 @Component
 public class FreemarkerConfigAwareListener {
 
-    private final OptionService optionService;
+    private final ClientOptionService optionService;
 
     private final Configuration configuration;
 
@@ -53,7 +54,7 @@ public class FreemarkerConfigAwareListener {
 
     private final AbstractStringCacheStore cacheStore;
 
-    public FreemarkerConfigAwareListener(OptionService optionService,
+    public FreemarkerConfigAwareListener(ClientOptionService optionService,
         Configuration configuration,
         ThemeService themeService,
         ThemeSettingService themeSettingService,
diff --git a/src/main/java/run/halo/app/service/ClientOptionService.java b/src/main/java/run/halo/app/service/ClientOptionService.java
new file mode 100644
index 000000000..71cbb48c6
--- /dev/null
+++ b/src/main/java/run/halo/app/service/ClientOptionService.java
@@ -0,0 +1,14 @@
+package run.halo.app.service;
+
+/**
+ * User client option service.
+ *
+ * @author LIlGG
+ * @date 2021/8/2
+ */
+public interface ClientOptionService extends OptionProvideService {
+    /**
+     * Flushes all pending changes to the database.
+     */
+    void flush();
+}
diff --git a/src/main/java/run/halo/app/service/OptionProvideService.java b/src/main/java/run/halo/app/service/OptionProvideService.java
new file mode 100644
index 000000000..7742fa817
--- /dev/null
+++ b/src/main/java/run/halo/app/service/OptionProvideService.java
@@ -0,0 +1,537 @@
+package run.halo.app.service;
+
+import com.qiniu.common.Zone;
+import com.qiniu.storage.Region;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import run.halo.app.exception.MissingPropertyException;
+import run.halo.app.model.dto.OptionDTO;
+import run.halo.app.model.dto.OptionSimpleDTO;
+import run.halo.app.model.enums.PostPermalinkType;
+import run.halo.app.model.enums.SheetPermalinkType;
+import run.halo.app.model.enums.ValueEnum;
+import run.halo.app.model.params.OptionQuery;
+import run.halo.app.model.properties.BlogProperties;
+import run.halo.app.model.properties.OtherProperties;
+import run.halo.app.model.properties.PermalinkProperties;
+import run.halo.app.model.properties.PropertyEnum;
+import run.halo.app.model.properties.QiniuOssProperties;
+import run.halo.app.model.properties.SeoProperties;
+
+
+/**
+ * Option parameter provision service.
+ *
+ * @author LIlGG
+ * @date 2021/8/2
+ */
+public interface OptionProvideService {
+    /**
+     * Get all options.
+     *
+     * @return Map
+     */
+    @NonNull
+    @Transactional
+    Map<String, Object> listOptions();
+
+    /**
+     * Lists options by key list.
+     *
+     * @param keys key list
+     * @return a map of option
+     */
+    @NonNull
+    default Map<String, Object> listOptions(@Nullable Collection<String> keys) {
+        if (CollectionUtils.isEmpty(keys)) {
+            return Collections.emptyMap();
+        }
+
+        Map<String, Object> optionMap = listOptions();
+
+        Map<String, Object> result = new HashMap<>(keys.size());
+
+        keys.stream()
+            .filter(optionMap::containsKey)
+            .forEach(key -> result.put(key, optionMap.get(key)));
+
+        return result;
+    }
+
+    /**
+     * Lists all option dtos.
+     *
+     * @return a list of option dto
+     */
+    @NonNull
+    default List<OptionDTO> listDtos() {
+        List<OptionDTO> result = new LinkedList<>();
+
+        listOptions().forEach((key, value) -> result.add(new OptionDTO(key, value)));
+
+        return result;
+    }
+
+    /**
+     * Pages option output dtos.
+     *
+     * @param pageable page info must not be null
+     * @param optionQuery optionQuery
+     * @return a page of option output dto
+     */
+    Page<OptionSimpleDTO> pageDtosBy(@NonNull Pageable pageable, OptionQuery optionQuery);
+
+    /**
+     * Get option by key.
+     *
+     * @param key option key must not be blank
+     * @return option value or null
+     */
+    @Nullable
+    default Object getByKeyOfNullable(@NonNull String key) {
+        return getByKey(key).orElse(null);
+    }
+
+    /**
+     * Gets option value of non null.
+     *
+     * @param key option key must not be null
+     * @return option value of non null
+     */
+    @NonNull
+    default Object getByKeyOfNonNull(@NonNull String key) {
+        return getByKey(key).orElseThrow(
+            () -> new MissingPropertyException("You have to config " + key + " setting"));
+    }
+
+    /**
+     * Get option by key.
+     *
+     * @param key option key must not be blank
+     * @return an optional option value
+     */
+    @NonNull
+    default Optional<Object> getByKey(@NonNull String key) {
+        Assert.hasText(key, "Option key must not be blank");
+
+        return Optional.ofNullable(listOptions().get(key));
+    }
+
+    /**
+     * Gets value by key.
+     *
+     * @param key key must not be null
+     * @param valueType value type must not be null
+     * @param <T> value type
+     * @return value
+     */
+    @NonNull
+    default  <T> Optional<T> getByKey(@NonNull String key, @NonNull Class<T> valueType) {
+        return getByKey(key).map(value -> PropertyEnum.convertTo(value.toString(), valueType));
+    }
+
+    /**
+     * Gets option value by blog property.
+     *
+     * @param property blog property must not be null
+     * @return an option value
+     */
+    @Nullable
+    default Object getByPropertyOfNullable(@NonNull PropertyEnum property) {
+        return getByProperty(property).orElse(null);
+    }
+
+    /**
+     * Gets option value by blog property.
+     *
+     * @param property blog property
+     * @return an optiona value
+     * @throws MissingPropertyException throws when property value dismisses
+     */
+    @NonNull
+    default Object getByPropertyOfNonNull(@NonNull PropertyEnum property) {
+        Assert.notNull(property, "Blog property must not be null");
+
+        return getByKeyOfNonNull(property.getValue());
+    }
+
+    /**
+     * Gets option value by blog property.
+     *
+     * @param property blog property must not be null
+     * @return an optional option value
+     */
+    @NonNull
+    default Optional<Object> getByProperty(@NonNull PropertyEnum property) {
+        Assert.notNull(property, "Blog property must not be null");
+
+        return getByKey(property.getValue());
+    }
+
+    /**
+     * Gets property value by blog property.
+     *
+     * @param property blog property must not be null
+     * @param propertyType property type must not be null
+     * @param <T> property type
+     * @return property value
+     */
+    default <T> Optional<T> getByProperty(@NonNull PropertyEnum property,
+        @NonNull Class<T> propertyType) {
+        return getByProperty(property)
+            .map(propertyValue -> PropertyEnum.convertTo(propertyValue.toString(), propertyType));
+    }
+
+    /**
+     * Gets property value by blog property.
+     *
+     * @param property blog property must not be null
+     * @param propertyType property type must not be null
+     * @param defaultValue default value
+     * @param <T> property type
+     * @return property value
+     */
+    default <T> T getByPropertyOrDefault(@NonNull PropertyEnum property,
+        @NonNull Class<T> propertyType,
+        T defaultValue) {
+        Assert.notNull(property, "Blog property must not be null");
+
+        return getByProperty(property, propertyType).orElse(defaultValue);
+    }
+
+    /**
+     * Gets property value by blog property.
+     *
+     * Default value from property default value.
+     *
+     * @param property blog property must not be null
+     * @param propertyType property type must not be null
+     * @param <T> property type
+     * @return property value
+     */
+    default  <T> T getByPropertyOrDefault(@NonNull PropertyEnum property,
+        @NonNull Class<T> propertyType) {
+        return getByProperty(property, propertyType).orElse(property.defaultValue(propertyType));
+    }
+
+    /**
+     * Gets value by key.
+     *
+     * @param key key must not be null
+     * @param valueType value type must not be null
+     * @param defaultValue default value
+     * @param <T> property type
+     * @return value
+     */
+    default <T> T getByKeyOrDefault(@NonNull String key, @NonNull Class<T> valueType,
+        T defaultValue) {
+        return getByKey(key, valueType).orElse(defaultValue);
+    }
+
+    /**
+     * Gets enum value by property.
+     *
+     * @param property property must not be blank
+     * @param valueType enum value type must not be null
+     * @param <T> enum value type
+     * @return an optional enum value
+     */
+    @NonNull
+    default <T extends Enum<T>> Optional<T> getEnumByProperty(@NonNull PropertyEnum property,
+        @NonNull Class<T> valueType) {
+        return getByProperty(property)
+            .map(value -> PropertyEnum.convertToEnum(value.toString(), valueType));
+    }
+
+    /**
+     * Gets enum value by property.
+     *
+     * @param property property must not be blank
+     * @param valueType enum value type must not be null
+     * @param defaultValue default value
+     * @param <T> enum value type
+     * @return enum value
+     */
+    @Nullable
+    default <T extends Enum<T>> T getEnumByPropertyOrDefault(@NonNull PropertyEnum property,
+        @NonNull Class<T> valueType, @Nullable T defaultValue) {
+        return getEnumByProperty(property, valueType).orElse(defaultValue);
+    }
+
+    /**
+     * Gets value enum by property.
+     *
+     * @param property property must not be blank
+     * @param valueType enum value type must not be null
+     * @param enumType enum type must not be null
+     * @param <V> enum value type
+     * @param <E> value enum type
+     * @return an optional value enum value
+     */
+    @NonNull
+    default <V, E extends Enum<E> & ValueEnum<V>> Optional<E> getValueEnumByProperty(
+        @NonNull PropertyEnum property,
+        @NonNull Class<V> valueType, @NonNull Class<E> enumType) {
+        return getByProperty(property).map(value -> ValueEnum
+            .valueToEnum(enumType, PropertyEnum.convertTo(value.toString(), valueType)));
+    }
+
+    /**
+     * Gets value enum by property.
+     *
+     * @param property property must not be blank
+     * @param valueType enum value type must not be null
+     * @param enumType enum type must not be null
+     * @param defaultValue default value enum value
+     * @param <V> enum value type
+     * @param <E> value enum type
+     * @return value enum value or null if the default value is null
+     */
+    @Nullable
+    default <V, E extends Enum<E> & ValueEnum<V>> E getValueEnumByPropertyOrDefault(
+        @NonNull PropertyEnum property,
+        @NonNull Class<V> valueType, @NonNull Class<E> enumType, @Nullable E defaultValue) {
+        return getValueEnumByProperty(property, valueType, enumType).orElse(defaultValue);
+    }
+
+    /**
+     * Gets post page size.
+     *
+     * @return page size
+     */
+    int getPostPageSize();
+
+    /**
+     * Gets archives page size.
+     *
+     * @return page size
+     */
+    int getArchivesPageSize();
+
+    /**
+     * Gets comment page size.
+     *
+     * @return page size
+     */
+    int getCommentPageSize();
+
+    /**
+     * Gets rss page size.
+     *
+     * @return page size
+     */
+    int getRssPageSize();
+
+    /**
+     * Get qi niu yun zone.
+     *
+     * @return qiniu zone
+     */
+    @NonNull
+    @Deprecated
+    default Zone getQnYunZone() {
+        return getByProperty(QiniuOssProperties.OSS_ZONE).map(qiniuZone -> {
+
+            Zone zone;
+            switch (qiniuZone.toString()) {
+                case "z0":
+                    zone = Zone.zone0();
+                    break;
+                case "z1":
+                    zone = Zone.zone1();
+                    break;
+                case "z2":
+                    zone = Zone.zone2();
+                    break;
+                case "na0":
+                    zone = Zone.zoneNa0();
+                    break;
+                case "as0":
+                    zone = Zone.zoneAs0();
+                    break;
+                default:
+                    // Default is detecting zone automatically
+                    zone = Zone.autoZone();
+            }
+            return zone;
+
+        }).orElseGet(Zone::autoZone);
+    }
+
+    /**
+     * Get qiniu oss region.
+     *
+     * @return qiniu region
+     */
+    @NonNull
+    Region getQiniuRegion();
+
+    /**
+     * Gets locale.
+     *
+     * @return locale user set or default locale
+     */
+    @NonNull
+    Locale getLocale();
+
+    /**
+     * Gets blog base url. (Without /)
+     *
+     * @return blog base url (If blog url isn't present, current machine IP address will be default)
+     */
+    @NonNull
+    String getBlogBaseUrl();
+
+    /**
+     * Gets blog title.
+     *
+     * @return blog title.
+     */
+    @NonNull
+    default String getBlogTitle() {
+        return getByProperty(BlogProperties.BLOG_TITLE).orElse("").toString();
+    }
+
+    /**
+     * Gets global seo keywords.
+     *
+     * @return keywords
+     */
+    default String getSeoKeywords() {
+        return getByProperty(SeoProperties.KEYWORDS).orElse("").toString();
+    }
+
+    /**
+     * Get global seo description.
+     *
+     * @return description
+     */
+    default String getSeoDescription() {
+        return getByProperty(SeoProperties.DESCRIPTION).orElse("").toString();
+    }
+
+    /**
+     * Gets blog birthday.
+     *
+     * @return birthday timestamp
+     */
+    long getBirthday();
+
+    /**
+     * Get post permalink type.
+     *
+     * @return PostPermalinkType
+     */
+    default PostPermalinkType getPostPermalinkType() {
+        return getEnumByPropertyOrDefault(PermalinkProperties.POST_PERMALINK_TYPE,
+            PostPermalinkType.class, PostPermalinkType.DEFAULT);
+    }
+
+    /**
+     * Get sheet permalink type.
+     *
+     * @return SheetPermalinkType
+     */
+    default SheetPermalinkType getSheetPermalinkType() {
+        return getEnumByPropertyOrDefault(PermalinkProperties.SHEET_PERMALINK_TYPE,
+            SheetPermalinkType.class, SheetPermalinkType.SECONDARY);
+    }
+
+    /**
+     * Get sheet custom prefix.
+     *
+     * @return sheet prefix.
+     */
+    default String getSheetPrefix() {
+        return getByPropertyOrDefault(PermalinkProperties.SHEET_PREFIX, String.class,
+            PermalinkProperties.SHEET_PREFIX.defaultValue());
+    }
+
+    /**
+     * Get links page custom prefix.
+     *
+     * @return links page prefix.
+     */
+    default String getLinksPrefix() {
+        return getByPropertyOrDefault(PermalinkProperties.LINKS_PREFIX, String.class,
+            PermalinkProperties.LINKS_PREFIX.defaultValue());
+    }
+
+    /**
+     * Get photos page custom prefix.
+     *
+     * @return photos page prefix.
+     */
+    default String getPhotosPrefix() {
+        return getByPropertyOrDefault(PermalinkProperties.PHOTOS_PREFIX, String.class,
+            PermalinkProperties.PHOTOS_PREFIX.defaultValue());
+    }
+
+    /**
+     * Get journals page custom prefix.
+     *
+     * @return journals page prefix.
+     */
+    default String getJournalsPrefix() {
+        return getByPropertyOrDefault(PermalinkProperties.JOURNALS_PREFIX, String.class,
+            PermalinkProperties.JOURNALS_PREFIX.defaultValue());
+    }
+
+    /**
+     * Get archives custom prefix.
+     *
+     * @return archives prefix.
+     */
+    default String getArchivesPrefix() {
+        return getByPropertyOrDefault(PermalinkProperties.ARCHIVES_PREFIX, String.class,
+            PermalinkProperties.ARCHIVES_PREFIX.defaultValue());
+    }
+
+    /**
+     * Get categories custom prefix.
+     *
+     * @return categories prefix.
+     */
+    default String getCategoriesPrefix() {
+        return getByPropertyOrDefault(PermalinkProperties.CATEGORIES_PREFIX, String.class,
+            PermalinkProperties.CATEGORIES_PREFIX.defaultValue());
+    }
+
+    /**
+     * Get tags custom prefix.
+     *
+     * @return tags prefix.
+     */
+    default String getTagsPrefix() {
+        return getByPropertyOrDefault(PermalinkProperties.TAGS_PREFIX, String.class,
+            PermalinkProperties.TAGS_PREFIX.defaultValue());
+    }
+
+    /**
+     * Get custom path suffix.
+     *
+     * @return path suffix.
+     */
+    default String getPathSuffix() {
+        return getByPropertyOrDefault(PermalinkProperties.PATH_SUFFIX, String.class,
+            PermalinkProperties.PATH_SUFFIX.defaultValue());
+    }
+
+    default Boolean isEnabledAbsolutePath() {
+        return getByPropertyOrDefault(OtherProperties.GLOBAL_ABSOLUTE_PATH_ENABLED, Boolean.class,
+            true);
+    }
+}
diff --git a/src/main/java/run/halo/app/service/OptionService.java b/src/main/java/run/halo/app/service/OptionService.java
index e099ff28a..72e6412a0 100755
--- a/src/main/java/run/halo/app/service/OptionService.java
+++ b/src/main/java/run/halo/app/service/OptionService.java
@@ -1,26 +1,13 @@
 package run.halo.app.service;
 
-import com.qiniu.common.Zone;
-import com.qiniu.storage.Region;
-import java.util.Collection;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
-import java.util.Optional;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.Pageable;
 import org.springframework.lang.NonNull;
 import org.springframework.lang.Nullable;
 import org.springframework.transaction.annotation.Transactional;
-import run.halo.app.exception.MissingPropertyException;
-import run.halo.app.model.dto.OptionDTO;
 import run.halo.app.model.dto.OptionSimpleDTO;
 import run.halo.app.model.entity.Option;
-import run.halo.app.model.enums.PostPermalinkType;
-import run.halo.app.model.enums.SheetPermalinkType;
-import run.halo.app.model.enums.ValueEnum;
 import run.halo.app.model.params.OptionParam;
-import run.halo.app.model.params.OptionQuery;
 import run.halo.app.model.properties.PropertyEnum;
 import run.halo.app.service.base.CrudService;
 
@@ -31,7 +18,7 @@ import run.halo.app.service.base.CrudService;
  * @author ryanwang
  * @date 2019-03-14
  */
-public interface OptionService extends CrudService<Option, Integer> {
+public interface OptionService extends CrudService<Option, Integer>, OptionProvideService {
 
     int DEFAULT_POST_PAGE_SIZE = 10;
 
@@ -91,41 +78,6 @@ public interface OptionService extends CrudService<Option, Integer> {
     @Transactional
     void saveProperties(@NonNull Map<? extends PropertyEnum, String> properties);
 
-    /**
-     * Get all options
-     *
-     * @return Map
-     */
-    @NonNull
-    @Transactional
-    Map<String, Object> listOptions();
-
-    /**
-     * Lists options by key list.
-     *
-     * @param keys key list
-     * @return a map of option
-     */
-    @NonNull
-    Map<String, Object> listOptions(@Nullable Collection<String> keys);
-
-    /**
-     * Lists all option dtos.
-     *
-     * @return a list of option dto
-     */
-    @NonNull
-    List<OptionDTO> listDtos();
-
-    /**
-     * Pages option output dtos.
-     *
-     * @param pageable page info must not be null
-     * @param optionQuery optionQuery
-     * @return a page of option output dto
-     */
-    Page<OptionSimpleDTO> pageDtosBy(@NonNull Pageable pageable, OptionQuery optionQuery);
-
     /**
      * Removes option permanently.
      *
@@ -135,340 +87,6 @@ public interface OptionService extends CrudService<Option, Integer> {
     @NonNull
     Option removePermanently(@NonNull Integer id);
 
-    /**
-     * Get option by key
-     *
-     * @param key option key must not be blank
-     * @return option value or null
-     */
-    @Nullable
-    Object getByKeyOfNullable(@NonNull String key);
-
-    /**
-     * Gets option value of non null.
-     *
-     * @param key option key must not be null
-     * @return option value of non null
-     */
-    @NonNull
-    Object getByKeyOfNonNull(@NonNull String key);
-
-    /**
-     * Get option by key
-     *
-     * @param key option key must not be blank
-     * @return an optional option value
-     */
-    @NonNull
-    Optional<Object> getByKey(@NonNull String key);
-
-    /**
-     * Gets value by key.
-     *
-     * @param key key must not be null
-     * @param valueType value type must not be null
-     * @param <T> value type
-     * @return value
-     */
-    @NonNull
-    <T> Optional<T> getByKey(@NonNull String key, @NonNull Class<T> valueType);
-
-    /**
-     * Gets option value by blog property.
-     *
-     * @param property blog property must not be null
-     * @return an option value
-     */
-    @Nullable
-    Object getByPropertyOfNullable(@NonNull PropertyEnum property);
-
-    /**
-     * Gets option value by blog property.
-     *
-     * @param property blog property
-     * @return an optiona value
-     * @throws MissingPropertyException throws when property value dismisses
-     */
-    @NonNull
-    Object getByPropertyOfNonNull(@NonNull PropertyEnum property);
-
-    /**
-     * Gets option value by blog property.
-     *
-     * @param property blog property must not be null
-     * @return an optional option value
-     */
-    @NonNull
-    Optional<Object> getByProperty(@NonNull PropertyEnum property);
-
-    /**
-     * Gets property value by blog property.
-     *
-     * @param property blog property must not be null
-     * @param propertyType property type must not be null
-     * @param <T> property type
-     * @return property value
-     */
-    <T> Optional<T> getByProperty(@NonNull PropertyEnum property, @NonNull Class<T> propertyType);
-
-    /**
-     * Gets property value by blog property.
-     *
-     * @param property blog property must not be null
-     * @param propertyType property type must not be null
-     * @param defaultValue default value
-     * @param <T> property type
-     * @return property value
-     */
-    <T> T getByPropertyOrDefault(@NonNull PropertyEnum property, @NonNull Class<T> propertyType,
-        T defaultValue);
-
-    /**
-     * Gets property value by blog property.
-     *
-     * Default value from property default value.
-     *
-     * @param property blog property must not be null
-     * @param propertyType property type must not be null
-     * @param <T> property type
-     * @return property value
-     */
-    <T> T getByPropertyOrDefault(@NonNull PropertyEnum property, @NonNull Class<T> propertyType);
-
-    /**
-     * Gets value by key.
-     *
-     * @param key key must not be null
-     * @param valueType value type must not be null
-     * @param defaultValue default value
-     * @param <T> property type
-     * @return value
-     */
-    <T> T getByKeyOrDefault(@NonNull String key, @NonNull Class<T> valueType, T defaultValue);
-
-    /**
-     * Gets enum value by property.
-     *
-     * @param property property must not be blank
-     * @param valueType enum value type must not be null
-     * @param <T> enum value type
-     * @return an optional enum value
-     */
-    @NonNull
-    <T extends Enum<T>> Optional<T> getEnumByProperty(@NonNull PropertyEnum property,
-        @NonNull Class<T> valueType);
-
-    /**
-     * Gets enum value by property.
-     *
-     * @param property property must not be blank
-     * @param valueType enum value type must not be null
-     * @param defaultValue default value
-     * @param <T> enum value type
-     * @return enum value
-     */
-    @Nullable
-    <T extends Enum<T>> T getEnumByPropertyOrDefault(@NonNull PropertyEnum property,
-        @NonNull Class<T> valueType, @Nullable T defaultValue);
-
-    /**
-     * Gets value enum by property.
-     *
-     * @param property property must not be blank
-     * @param valueType enum value type must not be null
-     * @param enumType enum type must not be null
-     * @param <V> enum value type
-     * @param <E> value enum type
-     * @return an optional value enum value
-     */
-    @NonNull
-    <V, E extends Enum<E> & ValueEnum<V>> Optional<E> getValueEnumByProperty(
-        @NonNull PropertyEnum property,
-        @NonNull Class<V> valueType, @NonNull Class<E> enumType);
-
-    /**
-     * Gets value enum by property.
-     *
-     * @param property property must not be blank
-     * @param valueType enum value type must not be null
-     * @param enumType enum type must not be null
-     * @param defaultValue default value enum value
-     * @param <V> enum value type
-     * @param <E> value enum type
-     * @return value enum value or null if the default value is null
-     */
-    @Nullable
-    <V, E extends Enum<E> & ValueEnum<V>> E getValueEnumByPropertyOrDefault(
-        @NonNull PropertyEnum property,
-        @NonNull Class<V> valueType, @NonNull Class<E> enumType, @Nullable E defaultValue);
-
-    /**
-     * Gets post page size.
-     *
-     * @return page size
-     */
-    int getPostPageSize();
-
-    /**
-     * Gets archives page size.
-     *
-     * @return page size
-     */
-    int getArchivesPageSize();
-
-    /**
-     * Gets comment page size.
-     *
-     * @return page size
-     */
-    int getCommentPageSize();
-
-    /**
-     * Gets rss page size.
-     *
-     * @return page size
-     */
-    int getRssPageSize();
-
-    /**
-     * Get qi niu yun zone.
-     *
-     * @return qiniu zone
-     */
-    @NonNull
-    @Deprecated
-    Zone getQnYunZone();
-
-    /**
-     * Get qiniu oss region.
-     *
-     * @return qiniu region
-     */
-    @NonNull
-    Region getQiniuRegion();
-
-    /**
-     * Gets locale.
-     *
-     * @return locale user set or default locale
-     */
-    @NonNull
-    Locale getLocale();
-
-    /**
-     * Gets blog base url. (Without /)
-     *
-     * @return blog base url (If blog url isn't present, current machine IP address will be default)
-     */
-    @NonNull
-    String getBlogBaseUrl();
-
-    /**
-     * Gets blog title.
-     *
-     * @return blog title.
-     */
-    @NonNull
-    String getBlogTitle();
-
-    /**
-     * Gets global seo keywords.
-     *
-     * @return keywords
-     */
-    String getSeoKeywords();
-
-    /**
-     * Get global seo description.
-     *
-     * @return description
-     */
-    String getSeoDescription();
-
-    /**
-     * Gets blog birthday.
-     *
-     * @return birthday timestamp
-     */
-    long getBirthday();
-
-    /**
-     * Get post permalink type.
-     *
-     * @return PostPermalinkType
-     */
-    PostPermalinkType getPostPermalinkType();
-
-    /**
-     * Get sheet permalink type.
-     *
-     * @return SheetPermalinkType
-     */
-    SheetPermalinkType getSheetPermalinkType();
-
-    /**
-     * Get sheet custom prefix.
-     *
-     * @return sheet prefix.
-     */
-    String getSheetPrefix();
-
-    /**
-     * Get links page custom prefix.
-     *
-     * @return links page prefix.
-     */
-    String getLinksPrefix();
-
-    /**
-     * Get photos page custom prefix.
-     *
-     * @return photos page prefix.
-     */
-    String getPhotosPrefix();
-
-    /**
-     * Get journals page custom prefix.
-     *
-     * @return journals page prefix.
-     */
-    String getJournalsPrefix();
-
-    /**
-     * Get archives custom prefix.
-     *
-     * @return archives prefix.
-     */
-    String getArchivesPrefix();
-
-    /**
-     * Get categories custom prefix.
-     *
-     * @return categories prefix.
-     */
-    String getCategoriesPrefix();
-
-    /**
-     * Get tags custom prefix.
-     *
-     * @return tags prefix.
-     */
-    String getTagsPrefix();
-
-    /**
-     * Get custom path suffix.
-     *
-     * @return path suffix.
-     */
-    String getPathSuffix();
-
-    /**
-     * Is enabled absolute path.
-     *
-     * @return true or false.
-     */
-    Boolean isEnabledAbsolutePath();
-
     /**
      * Converts to option output dto.
      *
diff --git a/src/main/java/run/halo/app/service/impl/ClientOptionServiceImpl.java b/src/main/java/run/halo/app/service/impl/ClientOptionServiceImpl.java
new file mode 100644
index 000000000..101e691bd
--- /dev/null
+++ b/src/main/java/run/halo/app/service/impl/ClientOptionServiceImpl.java
@@ -0,0 +1,93 @@
+package run.halo.app.service.impl;
+
+import static java.util.stream.Collectors.toMap;
+
+import com.qiniu.storage.Region;
+import java.util.Locale;
+import java.util.Map;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import run.halo.app.model.dto.OptionSimpleDTO;
+import run.halo.app.model.params.OptionQuery;
+import run.halo.app.service.ClientOptionService;
+import run.halo.app.service.OptionService;
+
+
+/**
+ * The client only provides filtered data
+ *
+ * @author LIlGG
+ * @date 2021/8/2
+ */
+@Service
+public class ClientOptionServiceImpl implements ClientOptionService {
+
+    private final OptionService optionService;
+
+    private final OptionFilter optionFilter;
+
+    ClientOptionServiceImpl(OptionService optionService) {
+        this.optionService = optionService;
+        this.optionFilter = new OptionFilter(optionService);
+    }
+
+    @Override
+    @Transactional
+    public Map<String, Object> listOptions() {
+        Map<String, Object> options = optionService.listOptions();
+        return optionFilter.filter(options.keySet()).parallelStream()
+            .collect(toMap(optionName -> optionName, options::get));
+    }
+
+    @Override
+    public Page<OptionSimpleDTO> pageDtosBy(Pageable pageable, OptionQuery optionQuery) {
+        return optionService.pageDtosBy(pageable, optionQuery);
+    }
+
+    @Override
+    public int getPostPageSize() {
+        return optionService.getPostPageSize();
+    }
+
+    @Override
+    public int getArchivesPageSize() {
+        return optionService.getArchivesPageSize();
+    }
+
+    @Override
+    public int getCommentPageSize() {
+        return optionService.getCommentPageSize();
+    }
+
+    @Override
+    public int getRssPageSize() {
+        return optionService.getRssPageSize();
+    }
+
+    @Override
+    public Region getQiniuRegion() {
+        return optionService.getQiniuRegion();
+    }
+
+    @Override
+    public Locale getLocale() {
+        return optionService.getLocale();
+    }
+
+    @Override
+    public String getBlogBaseUrl() {
+        return optionService.getBlogBaseUrl();
+    }
+
+    @Override
+    public long getBirthday() {
+        return optionService.getBirthday();
+    }
+
+    @Override
+    public void flush() {
+        optionService.flush();
+    }
+}
diff --git a/src/main/java/run/halo/app/service/impl/OptionServiceImpl.java b/src/main/java/run/halo/app/service/impl/OptionServiceImpl.java
index 681549fe8..ce6f2b1ba 100644
--- a/src/main/java/run/halo/app/service/impl/OptionServiceImpl.java
+++ b/src/main/java/run/halo/app/service/impl/OptionServiceImpl.java
@@ -228,32 +228,6 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer>
         });
     }
 
-    @Override
-    public Map<String, Object> listOptions(Collection<String> keys) {
-        if (CollectionUtils.isEmpty(keys)) {
-            return Collections.emptyMap();
-        }
-
-        Map<String, Object> optionMap = listOptions();
-
-        Map<String, Object> result = new HashMap<>(keys.size());
-
-        keys.stream()
-            .filter(optionMap::containsKey)
-            .forEach(key -> result.put(key, optionMap.get(key)));
-
-        return result;
-    }
-
-    @Override
-    public List<OptionDTO> listDtos() {
-        List<OptionDTO> result = new LinkedList<>();
-
-        listOptions().forEach((key, value) -> result.add(new OptionDTO(key, value)));
-
-        return result;
-    }
-
     @Override
     public Page<OptionSimpleDTO> pageDtosBy(Pageable pageable, OptionQuery optionQuery) {
         Assert.notNull(pageable, "Page info must not be null");
@@ -297,100 +271,6 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer>
         };
     }
 
-    @Override
-    public Object getByKeyOfNullable(String key) {
-        return getByKey(key).orElse(null);
-    }
-
-    @Override
-    public Object getByKeyOfNonNull(String key) {
-        return getByKey(key).orElseThrow(
-            () -> new MissingPropertyException("You have to config " + key + " setting"));
-    }
-
-    @Override
-    public Optional<Object> getByKey(String key) {
-        Assert.hasText(key, "Option key must not be blank");
-
-        return Optional.ofNullable(listOptions().get(key));
-    }
-
-    @Override
-    public <T> Optional<T> getByKey(String key, Class<T> valueType) {
-        return getByKey(key).map(value -> PropertyEnum.convertTo(value.toString(), valueType));
-    }
-
-    @Override
-    public Object getByPropertyOfNullable(PropertyEnum property) {
-        return getByProperty(property).orElse(null);
-    }
-
-    @Override
-    public Object getByPropertyOfNonNull(PropertyEnum property) {
-        Assert.notNull(property, "Blog property must not be null");
-
-        return getByKeyOfNonNull(property.getValue());
-    }
-
-    @Override
-    public Optional<Object> getByProperty(PropertyEnum property) {
-        Assert.notNull(property, "Blog property must not be null");
-
-        return getByKey(property.getValue());
-    }
-
-    @Override
-    public <T> Optional<T> getByProperty(PropertyEnum property, Class<T> propertyType) {
-        return getByProperty(property)
-            .map(propertyValue -> PropertyEnum.convertTo(propertyValue.toString(), propertyType));
-    }
-
-    @Override
-    public <T> T getByPropertyOrDefault(PropertyEnum property, Class<T> propertyType,
-        T defaultValue) {
-        Assert.notNull(property, "Blog property must not be null");
-
-        return getByProperty(property, propertyType).orElse(defaultValue);
-    }
-
-    @Override
-    public <T> T getByPropertyOrDefault(PropertyEnum property, Class<T> propertyType) {
-        return getByProperty(property, propertyType).orElse(property.defaultValue(propertyType));
-    }
-
-    @Override
-    public <T> T getByKeyOrDefault(String key, Class<T> valueType, T defaultValue) {
-        return getByKey(key, valueType).orElse(defaultValue);
-    }
-
-    @Override
-    public <T extends Enum<T>> Optional<T> getEnumByProperty(PropertyEnum property,
-        Class<T> valueType) {
-        return getByProperty(property)
-            .map(value -> PropertyEnum.convertToEnum(value.toString(), valueType));
-    }
-
-    @Override
-    public <T extends Enum<T>> T getEnumByPropertyOrDefault(PropertyEnum property,
-        Class<T> valueType, T defaultValue) {
-        return getEnumByProperty(property, valueType).orElse(defaultValue);
-    }
-
-    @Override
-    public <V, E extends Enum<E> & ValueEnum<V>> Optional<E> getValueEnumByProperty(
-        PropertyEnum property,
-        Class<V> valueType, Class<E> enumType) {
-        return getByProperty(property).map(value -> ValueEnum
-            .valueToEnum(enumType, PropertyEnum.convertTo(value.toString(), valueType)));
-    }
-
-    @Override
-    public <V, E extends Enum<E> & ValueEnum<V>> E getValueEnumByPropertyOrDefault(
-        PropertyEnum property,
-        Class<V> valueType, Class<E> enumType, E defaultValue) {
-        return getValueEnumByProperty(property, valueType, enumType).orElse(defaultValue);
-    }
-
     @Override
     public int getPostPageSize() {
         try {
@@ -438,36 +318,6 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer>
         }
     }
 
-    @Override
-    public Zone getQnYunZone() {
-        return getByProperty(QiniuOssProperties.OSS_ZONE).map(qiniuZone -> {
-
-            Zone zone;
-            switch (qiniuZone.toString()) {
-                case "z0":
-                    zone = Zone.zone0();
-                    break;
-                case "z1":
-                    zone = Zone.zone1();
-                    break;
-                case "z2":
-                    zone = Zone.zone2();
-                    break;
-                case "na0":
-                    zone = Zone.zoneNa0();
-                    break;
-                case "as0":
-                    zone = Zone.zoneAs0();
-                    break;
-                default:
-                    // Default is detecting zone automatically
-                    zone = Zone.autoZone();
-            }
-            return zone;
-
-        }).orElseGet(Zone::autoZone);
-    }
-
     @Override
     public Region getQiniuRegion() {
         return getByProperty(QiniuOssProperties.OSS_ZONE).map(qiniuZone -> {
@@ -525,21 +375,6 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer>
         return blogUrl;
     }
 
-    @Override
-    public String getBlogTitle() {
-        return getByProperty(BlogProperties.BLOG_TITLE).orElse("").toString();
-    }
-
-    @Override
-    public String getSeoKeywords() {
-        return getByProperty(SeoProperties.KEYWORDS).orElse("").toString();
-    }
-
-    @Override
-    public String getSeoDescription() {
-        return getByProperty(SeoProperties.DESCRIPTION).orElse("").toString();
-    }
-
     @Override
     public long getBirthday() {
         return getByProperty(PrimaryProperties.BIRTHDAY, Long.class).orElseGet(() -> {
@@ -609,12 +444,6 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer>
             PermalinkProperties.PATH_SUFFIX.defaultValue());
     }
 
-    @Override
-    public Boolean isEnabledAbsolutePath() {
-        return getByPropertyOrDefault(OtherProperties.GLOBAL_ABSOLUTE_PATH_ENABLED, Boolean.class,
-            true);
-    }
-
     @Override
     public OptionSimpleDTO convertToDto(Option option) {
         Assert.notNull(option, "Option must not be null");
diff --git a/src/test/java/run/halo/app/service/impl/ClientOptionServiceImplTest.java b/src/test/java/run/halo/app/service/impl/ClientOptionServiceImplTest.java
new file mode 100644
index 000000000..bf5f2a9cd
--- /dev/null
+++ b/src/test/java/run/halo/app/service/impl/ClientOptionServiceImplTest.java
@@ -0,0 +1,71 @@
+package run.halo.app.service.impl;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static run.halo.app.service.OptionService.OPTIONS_KEY;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import run.halo.app.cache.AbstractStringCacheStore;
+import run.halo.app.model.properties.AliOssProperties;
+import run.halo.app.model.properties.CommentProperties;
+
+
+/**
+ * @author LIlGG
+ * @date 2021/8/2
+ */
+@SpringBootTest
+@ActiveProfiles("test")
+class ClientOptionServiceImplTest {
+
+    @Autowired
+    AbstractStringCacheStore cacheStore;
+
+    @Autowired
+    ClientOptionServiceImpl clientOptionService;
+
+    Map<String, Object> map = new HashMap<>();
+
+    {
+        map.put(AliOssProperties.OSS_DOMAIN.getValue(), "1");
+        map.put(CommentProperties.CONTENT_PLACEHOLDER.getValue(), "2");
+        map.put(CommentProperties.GRAVATAR_SOURCE.getValue(), "3");
+    }
+
+    @BeforeEach
+    void setUp() {
+        cacheStore.putAny(OPTIONS_KEY, map);
+    }
+
+    @Test
+    void listOptionsTest() {
+        Map<String, Object> options = clientOptionService.listOptions();
+
+        assertFalse(options.containsKey(AliOssProperties.OSS_DOMAIN.getValue()));
+    }
+
+    @Test
+    void listOptionsKeyTest() {
+        List<String> keys = new ArrayList<>();
+        keys.add(AliOssProperties.OSS_DOMAIN.getValue());
+        keys.add(CommentProperties.CONTENT_PLACEHOLDER.getValue());
+        keys.add(CommentProperties.GRAVATAR_SOURCE.getValue());
+        Map<String, Object> options = clientOptionService.listOptions(keys);
+
+        assertFalse(options.containsKey(AliOssProperties.OSS_DOMAIN.getValue()));
+    }
+
+    @Test
+    void getByKeyTest() {
+        assertNull(clientOptionService.getByKey(AliOssProperties.OSS_DOMAIN.getValue())
+            .orElse(null));
+    }
+}