diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 8a804f0ba691b4e092da0998a8190a26142bd743..0daf484b86d9e31f9380a81d81850df83aadc238 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -575,6 +575,78 @@ local function update_index_parts_1_6_0(parts)
     return result
 end
 
+--
+-- Get field index by format field name.
+--
+local function format_field_index_by_name(format, name)
+    for k, v in pairs(format) do
+        if v.name == name then
+            return k
+        end
+    end
+    return nil
+end
+
+--
+-- Get field 0-based index and relative JSON path to data by
+-- field 1-based index or full JSON path. A particular case of a
+-- full JSON path is the format field name.
+--
+local function format_field_resolve(format, path, part_idx)
+    assert(type(path) == 'number' or type(path) == 'string')
+    local idx = nil
+    local relative_path = nil
+    local field_name = nil
+    -- Path doesn't require resolve.
+    if type(path) == 'number' then
+        idx = path
+        goto done
+    end
+    -- An attempt to interpret a path as the full field name.
+    idx = format_field_index_by_name(format, path)
+    if idx ~= nil then
+        relative_path = nil
+        goto done
+    end
+    -- Check if the initial part of the JSON path is a token of
+    -- the form [%d].
+    field_name = string.match(path, "^%[(%d+)%]")
+    idx = tonumber(field_name)
+    if idx ~= nil then
+        relative_path = string.sub(path, string.len(field_name) + 3)
+        goto done
+    end
+    -- Check if the initial part of the JSON path is a token of
+    -- the form ["%s"] or ['%s'].
+    field_name = string.match(path, '^%["([^%]]+)"%]') or
+                 string.match(path, "^%['([^%]]+)'%]")
+    idx = format_field_index_by_name(format, field_name)
+    if idx ~= nil then
+        relative_path = string.sub(path, string.len(field_name) + 5)
+        goto done
+    end
+    -- Check if the initial part of the JSON path is a string
+    -- token: assume that it ends with .*[ or .*.
+    field_name = string.match(path, "^([^.[]+)")
+    idx = format_field_index_by_name(format, field_name)
+    if idx ~= nil then
+        relative_path = string.sub(path, string.len(field_name) + 1)
+        goto done
+    end
+    -- Can't resolve field index by path.
+    assert(idx == nil)
+    box.error(box.error.ILLEGAL_PARAMS, "options.parts[" .. part_idx .. "]: " ..
+              "field was not found by name '" .. path .. "'")
+
+::done::
+    if idx <= 0 then
+        box.error(box.error.ILLEGAL_PARAMS,
+                  "options.parts[" .. part_idx .. "]: " ..
+                  "field (number) must be one-based")
+    end
+    return idx - 1, relative_path
+end
+
 local function update_index_parts(format, parts)
     if type(parts) ~= "table" then
         box.error(box.error.ILLEGAL_PARAMS,
@@ -622,25 +694,16 @@ local function update_index_parts(format, parts)
                 end
             end
         end
-        if type(part.field) ~= 'number' and type(part.field) ~= 'string' then
-            box.error(box.error.ILLEGAL_PARAMS,
-                      "options.parts[" .. i .. "]: field (name or number) is expected")
-        elseif type(part.field) == 'string' then
-            for k,v in pairs(format) do
-                if v.name == part.field then
-                    part.field = k
-                    break
-                end
-            end
-            if type(part.field) == 'string' then
-                box.error(box.error.ILLEGAL_PARAMS,
-                          "options.parts[" .. i .. "]: field was not found by name '" .. part.field .. "'")
-            end
-        elseif part.field == 0 then
-            box.error(box.error.ILLEGAL_PARAMS,
-                      "options.parts[" .. i .. "]: field (number) must be one-based")
+        if type(part.field) == 'number' or type(part.field) == 'string' then
+            local idx, path = format_field_resolve(format, part.field, i)
+            part.field = idx
+            part.path = path or part.path
+            parts_can_be_simplified = parts_can_be_simplified and part.path == nil
+        else
+            box.error(box.error.ILLEGAL_PARAMS, "options.parts[" .. i .. "]: " ..
+                      "field (name or number) is expected")
         end
-        local fmt = format[part.field]
+        local fmt = format[part.field + 1]
         if part.type == nil then
             if fmt and fmt.type then
                 part.type = fmt.type
@@ -666,7 +729,6 @@ local function update_index_parts(format, parts)
                 parts_can_be_simplified = false
             end
         end
-        part.field = part.field - 1
         table.insert(result, part)
     end
     return result, parts_can_be_simplified
diff --git a/test/engine/json.result b/test/engine/json.result
index 3a5f472bced407fde262ca718768347cc8274324..1bac85eddbedd41b47a14d9396efeef15db8335c 100644
--- a/test/engine/json.result
+++ b/test/engine/json.result
@@ -122,6 +122,100 @@ idx:max()
 s:drop()
 ---
 ...
+-- Test user-friendly index creation interface.
+s = box.schema.space.create('withdata', {engine = engine})
+---
+...
+format = {{'data', 'map'}, {'meta', 'str'}}
+---
+...
+s:format(format)
+---
+...
+s:create_index('pk_invalid', {parts = {{']sad.FIO["sname"]', 'str'}}})
+---
+- error: 'Illegal parameters, options.parts[1]: field was not found by name '']sad.FIO["sname"]'''
+...
+s:create_index('pk_unexistent', {parts = {{'unexistent.FIO["sname"]', 'str'}}})
+---
+- error: 'Illegal parameters, options.parts[1]: field was not found by name ''unexistent.FIO["sname"]'''
+...
+pk = s:create_index('pk', {parts = {{'data.FIO["sname"]', 'str'}}})
+---
+...
+pk ~= nil
+---
+- true
+...
+sk2 = s:create_index('sk2', {parts = {{'["data"].FIO["sname"]', 'str'}}})
+---
+...
+sk2 ~= nil
+---
+- true
+...
+sk3 = s:create_index('sk3', {parts = {{'[\'data\'].FIO["sname"]', 'str'}}})
+---
+...
+sk3 ~= nil
+---
+- true
+...
+sk4 = s:create_index('sk4', {parts = {{'[1].FIO["sname"]', 'str'}}})
+---
+...
+sk4 ~= nil
+---
+- true
+...
+pk.fieldno == sk2.fieldno
+---
+- true
+...
+sk2.fieldno == sk3.fieldno
+---
+- true
+...
+sk3.fieldno == sk4.fieldno
+---
+- true
+...
+pk.path == sk2.path
+---
+- true
+...
+sk2.path == sk3.path
+---
+- true
+...
+sk3.path == sk4.path
+---
+- true
+...
+s:insert{{town = 'London', FIO = {fname = 'James', sname = 'Bond'}}, "mi6"}
+---
+- [{'town': 'London', 'FIO': {'fname': 'James', 'sname': 'Bond'}}, 'mi6']
+...
+s:insert{{town = 'Moscow', FIO = {fname = 'Max', sname = 'Isaev', data = "extra"}}, "test"}
+---
+- [{'town': 'Moscow', 'FIO': {'fname': 'Max', 'data': 'extra', 'sname': 'Isaev'}},
+  'test']
+...
+pk:get({'Bond'}) == sk2:get({'Bond'})
+---
+- true
+...
+sk2:get({'Bond'}) == sk3:get({'Bond'})
+---
+- true
+...
+sk3:get({'Bond'}) == sk4:get({'Bond'})
+---
+- true
+...
+s:drop()
+---
+...
 -- Test upsert of JSON-indexed data.
 s = box.schema.create_space('withdata', {engine = engine})
 ---
diff --git a/test/engine/json.test.lua b/test/engine/json.test.lua
index 181eae02cd9e7d23cda8233b133dfa76f5a30eaf..9afa3daa2cfa1a8164c79bb9bb3bc92dfa0d8c38 100644
--- a/test/engine/json.test.lua
+++ b/test/engine/json.test.lua
@@ -34,6 +34,33 @@ idx:min()
 idx:max()
 s:drop()
 
+-- Test user-friendly index creation interface.
+s = box.schema.space.create('withdata', {engine = engine})
+format = {{'data', 'map'}, {'meta', 'str'}}
+s:format(format)
+s:create_index('pk_invalid', {parts = {{']sad.FIO["sname"]', 'str'}}})
+s:create_index('pk_unexistent', {parts = {{'unexistent.FIO["sname"]', 'str'}}})
+pk = s:create_index('pk', {parts = {{'data.FIO["sname"]', 'str'}}})
+pk ~= nil
+sk2 = s:create_index('sk2', {parts = {{'["data"].FIO["sname"]', 'str'}}})
+sk2 ~= nil
+sk3 = s:create_index('sk3', {parts = {{'[\'data\'].FIO["sname"]', 'str'}}})
+sk3 ~= nil
+sk4 = s:create_index('sk4', {parts = {{'[1].FIO["sname"]', 'str'}}})
+sk4 ~= nil
+pk.fieldno == sk2.fieldno
+sk2.fieldno == sk3.fieldno
+sk3.fieldno == sk4.fieldno
+pk.path == sk2.path
+sk2.path == sk3.path
+sk3.path == sk4.path
+s:insert{{town = 'London', FIO = {fname = 'James', sname = 'Bond'}}, "mi6"}
+s:insert{{town = 'Moscow', FIO = {fname = 'Max', sname = 'Isaev', data = "extra"}}, "test"}
+pk:get({'Bond'}) == sk2:get({'Bond'})
+sk2:get({'Bond'}) == sk3:get({'Bond'})
+sk3:get({'Bond'}) == sk4:get({'Bond'})
+s:drop()
+
 -- Test upsert of JSON-indexed data.
 s = box.schema.create_space('withdata', {engine = engine})
 parts = {}