diff --git a/IPython/html/static/services/config.js b/IPython/html/static/services/config.js
new file mode 100644
index 0000000..a3632f7
--- /dev/null
+++ b/IPython/html/static/services/config.js
@@ -0,0 +1,67 @@
+// Copyright (c) IPython Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+define([
+ 'jquery',
+ 'base/js/utils',
+ ],
+function($, utils) {
+ var ConfigSection = function(section_name, options) {
+ this.section_name = section_name;
+ this.base_url = options.base_url;
+ this.data = {};
+
+ var that = this;
+
+ /* .loaded is a promise, fulfilled the first time the config is loaded
+ * from the server. Code can do:
+ * conf.loaded.then(function() { ... using conf.data ... });
+ */
+ this._one_load_finished = false;
+ this.loaded = new Promise(function(resolve, reject) {
+ that._finish_firstload = resolve;
+ });
+ };
+
+ ConfigSection.prototype.api_url = function() {
+ return utils.url_join_encode(this.base_url, 'api/config', this.section_name);
+ };
+
+ ConfigSection.prototype._load_done = function() {
+ if (!this._one_load_finished) {
+ this._one_load_finished = true;
+ this._finish_firstload();
+ }
+ };
+
+ ConfigSection.prototype.load = function() {
+ var that = this;
+ return utils.promising_ajax(this.api_url(), {
+ cache: false,
+ type: "GET",
+ dataType: "json",
+ }).then(function(data) {
+ that.data = data;
+ that._load_done();
+ return data;
+ });
+ };
+
+ ConfigSection.prototype.update = function(newdata) {
+ var that = this;
+ return utils.promising_ajax(this.api_url(), {
+ processData: false,
+ type : "PATCH",
+ data: JSON.stringify(newdata),
+ dataType : "json",
+ contentType: 'application/json',
+ }).then(function(data) {
+ that.data = data;
+ that._load_done();
+ return data;
+ });
+ };
+
+ return {ConfigSection: ConfigSection};
+
+});