diff --git a/garnet/bin/setui/BUILD.gn b/garnet/bin/setui/BUILD.gn
index bada97b84cfadb8093dfa94f3478e4e8ed31327d..1b7c3782fe5c5d3a2086f987b524ebc0c2b2110b 100644
--- a/garnet/bin/setui/BUILD.gn
+++ b/garnet/bin/setui/BUILD.gn
@@ -33,6 +33,9 @@ rustc_binary("bin") {
     "//third_party/rust_crates:futures-preview",
     "//third_party/rust_crates:log",
     "//third_party/rust_crates:parking_lot",
+    "//third_party/rust_crates:serde",
+    "//third_party/rust_crates:serde_derive",
+    "//third_party/rust_crates:serde_json",
   ]
 }
 
diff --git a/garnet/bin/setui/meta/setui_service.cmx b/garnet/bin/setui/meta/setui_service.cmx
index 98a36e88ef4d7072b3fba7541429b4f05ce3f7fd..d8580f06a6ef5cf5d67ae61ff611975d77f076fa 100644
--- a/garnet/bin/setui/meta/setui_service.cmx
+++ b/garnet/bin/setui/meta/setui_service.cmx
@@ -3,6 +3,9 @@
         "data": "data/setui_service"
     },
     "sandbox": {
+        "features": [
+            "isolated-persistent-storage"
+        ],
         "services": [
             "fuchsia.logger.LogSink",
             "fuchsia.net.Connectivity",
diff --git a/garnet/bin/setui/src/common.rs b/garnet/bin/setui/src/common.rs
index e6c60f9967dbb71763e2fb4c6d19fdfa7df8aad1..8cb30715c7d73674c95e409d1f0cdf87f5ebd36f 100644
--- a/garnet/bin/setui/src/common.rs
+++ b/garnet/bin/setui/src/common.rs
@@ -7,6 +7,8 @@ use fidl_fuchsia_setui::*;
 use std::sync::mpsc::Sender;
 
 pub type ProcessMutation = dyn Fn(&Mutation) -> Result<Option<SettingData>, Error> + Send + Sync;
+pub type BoxedSettingCodec<T> = Box<dyn SettingCodec<T> + Send + Sync>;
+pub type BoxedStore = Box<dyn Store + Send + Sync>;
 
 /// Trait defining interface setting service uses to relay operations. Each
 /// adapter specifies the setting type it handles, which it will accept
@@ -23,3 +25,18 @@ pub trait Adapter {
     /// Otherwise, the sender is stored for later invocation.
     fn listen(&self, sender: Sender<SettingData>, last_seen_data: Option<&SettingData>);
 }
+
+/// Trait for encoding and decoding Settings.
+pub trait SettingCodec<T: ToString> {
+    fn encode(&self, data: SettingData) -> Result<T, Error>;
+
+    fn decode(&self, encoded: T) -> Result<SettingData, Error>;
+}
+
+pub trait Store {
+    /// Writes value to presistent storage.
+    fn write(&self, data: SettingData) -> Result<(), Error>;
+
+    /// Reads value from persistent storage
+    fn read(&self) -> Result<Option<SettingData>, Error>;
+}
diff --git a/garnet/bin/setui/src/default_store.rs b/garnet/bin/setui/src/default_store.rs
new file mode 100644
index 0000000000000000000000000000000000000000..25ae01b585e7356cd2a892cff66ef491ed3ed139
--- /dev/null
+++ b/garnet/bin/setui/src/default_store.rs
@@ -0,0 +1,46 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::common::*;
+use failure::Error;
+use fidl_fuchsia_setui::*;
+use serde_json::Value;
+use std::fs::File;
+use std::io::BufReader;
+use std::io::Write;
+use std::path::Path;
+
+pub struct DefaultStore {
+    file_path: String,
+    codec: BoxedSettingCodec<Value>,
+}
+
+impl DefaultStore {
+    pub fn new(file_path: String, codec: BoxedSettingCodec<Value>) -> DefaultStore {
+        return DefaultStore { file_path: file_path, codec: codec };
+    }
+}
+
+impl Store for DefaultStore {
+    /// Writes value to presistent storage.
+    fn write(&self, data: SettingData) -> Result<(), Error> {
+        let encoded_data = self.codec.encode(data)?;
+        let mut file = File::create(self.file_path.clone())?;
+        file.write_all(encoded_data.to_string().as_bytes())?;
+
+        return Ok(());
+    }
+
+    /// Reads value from persistent storage
+    fn read(&self) -> Result<Option<SettingData>, Error> {
+        if !Path::new(&self.file_path).exists() {
+            return Ok(None);
+        }
+
+        let reader = BufReader::new(File::open(self.file_path.clone())?);
+        let json = serde_json::from_reader(reader)?;
+
+        return Ok(Some(self.codec.decode(json)?));
+    }
+}
diff --git a/garnet/bin/setui/src/json_codec.rs b/garnet/bin/setui/src/json_codec.rs
new file mode 100644
index 0000000000000000000000000000000000000000..78977ff2ee150985efcda5a7a55cc8fa0044a25f
--- /dev/null
+++ b/garnet/bin/setui/src/json_codec.rs
@@ -0,0 +1,214 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::common::SettingCodec;
+use failure::{format_err, Error};
+use fidl_fuchsia_setui::*;
+use serde_json::json;
+use serde_json::Value;
+
+pub struct JsonCodec {}
+
+impl JsonCodec {
+    pub fn new() -> JsonCodec {
+        return JsonCodec {};
+    }
+}
+
+const KEY_TYPE: &str = "type";
+const KEY_DATA: &str = "data";
+
+impl SettingCodec<Value> for JsonCodec {
+    fn encode(&self, data: SettingData) -> Result<Value, Error> {
+        if let Some(setting_type) = get_type(&data) {
+            let mut data_value;
+            match setting_type {
+                TYPE_STRING_VALUE => {
+                    data_value = Some(encode_string_value(&data)?);
+                }
+                TYPE_ACCOUNT_VALUE => {
+                    data_value = Some(encode_account_settings(&data)?);
+                }
+                _ => {
+                    return Err(format_err!("type encoding not available"));
+                }
+            }
+
+            return Ok(json!({KEY_TYPE: setting_type, KEY_DATA: data_value.unwrap()}));
+        } else {
+            return Err(format_err!("unhandled data type"));
+        }
+    }
+
+    fn decode(&self, encoded: Value) -> Result<SettingData, Error> {
+        if let Value::Object(mapping) = encoded {
+            // Get value mapped to the type key
+            if let (Some(type_value), Some(data_value)) =
+                (mapping.get(KEY_TYPE), mapping.get(KEY_DATA))
+            {
+                // Retrieve setting type from value.
+                if let Some(setting_type) = type_value.as_u64() {
+                    match setting_type {
+                        TYPE_STRING_VALUE => return decode_string_value(data_value),
+                        TYPE_ACCOUNT_VALUE => return decode_account(data_value),
+                        _ => return Err(format_err!("unsupported encoded type")),
+                    }
+                } else {
+                    return Err(format_err!("type is not a number"));
+                }
+            } else {
+                return Err(format_err!("type or data not present"));
+            }
+        } else {
+            return Err(format_err!("root node should be an object"));
+        }
+    }
+}
+
+const TYPE_STRING_VALUE: u64 = 1;
+const TYPE_ACCOUNT_VALUE: u64 = 2;
+
+fn get_type(setting_data: &SettingData) -> Option<u64> {
+    match setting_data {
+        SettingData::StringValue(_val) => Some(TYPE_STRING_VALUE),
+        SettingData::Account(_val) => Some(TYPE_ACCOUNT_VALUE),
+        _ => None,
+    }
+}
+
+fn encode_string_value(data: &SettingData) -> Result<Value, Error> {
+    if let SettingData::StringValue(string_val) = data {
+        return Ok(json!(string_val));
+    } else {
+        return Err(format_err!("not a string value"));
+    }
+}
+
+fn decode_string_value(encoded: &Value) -> Result<SettingData, Error> {
+    if let Value::String(string_val) = encoded {
+        return Ok(SettingData::StringValue(string_val.clone()));
+    }
+
+    return Err(format_err!("not a string type"));
+}
+
+const ACCOUNT_SETTINGS_MODE_KEY: &str = "mode";
+
+fn encode_account_settings(data: &SettingData) -> Result<Value, Error> {
+    if let SettingData::Account(account_settings) = data {
+        if account_settings.mode == None {
+            return Ok(json!({}));
+        }
+
+        let encoded_mode = encode_login_mode(account_settings.mode.unwrap())?;
+        return Ok(json!({ ACCOUNT_SETTINGS_MODE_KEY: encoded_mode }));
+    } else {
+        return Err(format_err!("not an account setting val"));
+    }
+}
+
+fn decode_account(encoded: &Value) -> Result<SettingData, Error> {
+    if let Value::Object(mapping) = encoded {
+        match mapping.get(ACCOUNT_SETTINGS_MODE_KEY) {
+            Some(val) => {
+                Ok(SettingData::Account(AccountSettings { mode: Some(decode_login_mode(val)?) }))
+            }
+            _ => Ok(SettingData::Account(AccountSettings { mode: None })),
+        }
+    } else {
+        return Err(format_err!("malformed account json"));
+    }
+}
+
+const LOGIN_MODE_NONE: u64 = 0;
+const LOGIN_MODE_GUEST_OVERRIDE: u64 = 1;
+
+fn encode_login_mode(mode: LoginOverride) -> Result<Value, Error> {
+    match mode {
+        LoginOverride::None => {
+            return Ok(json!(LOGIN_MODE_NONE));
+        }
+        LoginOverride::AutologinGuest => {
+            return Ok(json!(LOGIN_MODE_GUEST_OVERRIDE));
+        }
+    }
+}
+
+fn decode_login_mode(value: &Value) -> Result<LoginOverride, Error> {
+    if let Value::Number(number) = value {
+        if let Some(val) = number.as_u64() {
+            match val {
+                LOGIN_MODE_NONE => {
+                    return Ok(LoginOverride::None);
+                }
+                LOGIN_MODE_GUEST_OVERRIDE => {
+                    return Ok(LoginOverride::AutologinGuest);
+                }
+                _ => {
+                    return Err(format_err!("not a decodable type"));
+                }
+            }
+        } else {
+            return Err(format_err!("incorrect number format"));
+        }
+    } else {
+        return Err(format_err!("not a number"));
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    /// A basic test to exercise that basic functionality works. In this case, we
+    /// mutate the unknown type, reserved for testing. We should always immediately
+    /// receive back an Ok response.
+    #[test]
+    fn test_encode_string() {
+        let test_val = "foo bar";
+        let codec = JsonCodec::new();
+        let encode_result = codec.encode(SettingData::StringValue(test_val.to_string()));
+        assert!(encode_result.is_ok());
+
+        let encoded_value = encode_result.unwrap();
+        let decode_result = codec.decode(encoded_value);
+        assert!(decode_result.is_ok());
+
+        match decode_result.unwrap() {
+            SettingData::StringValue(val) => {
+                assert_eq!(val, test_val);
+            }
+            _ => {
+                panic!("wrong type!");
+            }
+        }
+    }
+
+    #[test]
+    fn test_encode_account() {
+        let codec = JsonCodec::new();
+        let test_override = LoginOverride::AutologinGuest;
+
+        let encode_result =
+            codec.encode(SettingData::Account(AccountSettings { mode: Some(test_override) }));
+        assert!(encode_result.is_ok());
+
+        let encoded_value = encode_result.unwrap();
+        let decode_result = codec.decode(encoded_value);
+        assert!(decode_result.is_ok());
+
+        match decode_result.unwrap() {
+            SettingData::Account(account_settings) => {
+                if let Some(login_override) = account_settings.mode {
+                    assert_eq!(test_override, login_override);
+                } else {
+                    panic!("override should have been set");
+                }
+            }
+            _ => {
+                panic!("wrong setting data type");
+            }
+        }
+    }
+}
diff --git a/garnet/bin/setui/src/main.rs b/garnet/bin/setui/src/main.rs
index ce0163714fe672a56c595c5b6f0ae6568001efae..da32da390ab49f7ace9e3ff44ae63dfe7de239da 100644
--- a/garnet/bin/setui/src/main.rs
+++ b/garnet/bin/setui/src/main.rs
@@ -4,6 +4,8 @@
 #![feature(async_await, await_macro, futures_api)]
 
 use {
+    crate::default_store::DefaultStore,
+    crate::json_codec::JsonCodec,
     crate::mutation::*,
     crate::setting_adapter::SettingAdapter,
     failure::Error,
@@ -18,7 +20,9 @@ use {
 };
 
 mod common;
+mod default_store;
 mod fidl_clone;
+mod json_codec;
 mod mutation;
 mod setting_adapter;
 mod setui_handler;
@@ -35,6 +39,7 @@ fn main() -> Result<(), Error> {
     // TODO(SU-210): Remove once other adapters are ready.
     handler.register_adapter(Box::new(SettingAdapter::new(
         SettingType::Unknown,
+        Box::new(DefaultStore::new("/data/unknown.dat".to_string(), Box::new(JsonCodec::new()))),
         Box::new(process_string_mutation),
         None,
     )));
diff --git a/garnet/bin/setui/src/setting_adapter.rs b/garnet/bin/setui/src/setting_adapter.rs
index a039d522215144cc3ab361d39b89bff1d0691243..aac18fab8e7cb355a20466d266bd1a7def4ae837 100644
--- a/garnet/bin/setui/src/setting_adapter.rs
+++ b/garnet/bin/setui/src/setting_adapter.rs
@@ -5,6 +5,7 @@
 use crate::common::*;
 use crate::fidl_clone::*;
 use fidl_fuchsia_setui::*;
+use fuchsia_syslog::fx_log_err;
 use std::sync::mpsc::Sender;
 use std::sync::Mutex;
 
@@ -17,20 +18,34 @@ pub struct SettingAdapter {
     latest_val: Option<SettingData>,
     senders: Mutex<Vec<Sender<SettingData>>>,
     mutation_process: Box<ProcessMutation>,
+    store: Mutex<BoxedStore>,
 }
 
 impl SettingAdapter {
     pub fn new(
         setting_type: SettingType,
+        store: BoxedStore,
         mutation_processor: Box<ProcessMutation>,
         default_value: Option<SettingData>,
     ) -> SettingAdapter {
-        return SettingAdapter {
+        let mut adapter = SettingAdapter {
             setting_type: setting_type,
             latest_val: default_value,
             senders: Mutex::new(vec![]),
             mutation_process: mutation_processor,
+            store: Mutex::new(store),
         };
+        adapter.initialize();
+
+        return adapter;
+    }
+
+    fn initialize(&mut self) {
+        if let Ok(store) = self.store.lock() {
+            if let Ok(Some(value)) = store.read() {
+                self.latest_val = Some(value);
+            }
+        }
     }
 }
 
@@ -46,6 +61,12 @@ impl Adapter for SettingAdapter {
             Ok(Some(setting)) => {
                 self.latest_val = Some(setting.clone());
 
+                if let Ok(store) = self.store.lock() {
+                    if store.write(setting.clone()).is_err() {
+                        fx_log_err!("failed to write setting to file");
+                    }
+                }
+
                 if let Ok(mut senders) = self.senders.lock() {
                     while !senders.is_empty() {
                         let sender_option = senders.pop();
diff --git a/garnet/bin/setui/src/setui_handler.rs b/garnet/bin/setui/src/setui_handler.rs
index 506f276eb5f506d82b88ee7d3b8db02392d9c83f..e8c3c595e3524a2a713b2140fd6eb486fce5987d 100644
--- a/garnet/bin/setui/src/setui_handler.rs
+++ b/garnet/bin/setui/src/setui_handler.rs
@@ -115,9 +115,28 @@ impl SetUIHandler {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::common::Store;
     use crate::mutation::*;
     use crate::setting_adapter::SettingAdapter;
 
+    struct TestStore {}
+
+    impl TestStore {
+        fn new() -> TestStore {
+            TestStore {}
+        }
+    }
+
+    impl Store for TestStore {
+        fn write(&self, _data: SettingData) -> Result<(), Error> {
+            Ok(())
+        }
+
+        fn read(&self) -> Result<Option<SettingData>, Error> {
+            Ok(None)
+        }
+    }
+
     /// A basic test to exercise that basic functionality works. In this case, we
     /// mutate the unknown type, reserved for testing. We should always immediately
     /// receive back an Ok response.
@@ -145,6 +164,7 @@ mod tests {
         let handler = SetUIHandler::new();
         handler.register_adapter(Box::new(SettingAdapter::new(
             SettingType::Unknown,
+            Box::new(TestStore::new()),
             Box::new(process_string_mutation),
             None,
         )));