diff --git a/dev_docs/assets/saved_object_vs_data_indices.png b/dev_docs/assets/saved_object_vs_data_indices.png
new file mode 100644
index 0000000000000..e79a5cd848db1
Binary files /dev/null and b/dev_docs/assets/saved_object_vs_data_indices.png differ
diff --git a/dev_docs/key_concepts/saved_objects.mdx b/dev_docs/key_concepts/saved_objects.mdx
new file mode 100644
index 0000000000000..d89342765c8f1
--- /dev/null
+++ b/dev_docs/key_concepts/saved_objects.mdx
@@ -0,0 +1,74 @@
+---
+id: kibDevDocsSavedObjectsIntro
+slug: /kibana-dev-docs/saved-objects-intro
+title: Saved Objects
+summary: Saved Objects are a key concept to understand when building a Kibana plugin.
+date: 2021-02-02
+tags: ['kibana','dev', 'contributor', 'api docs']
+---
+
+"Saved Objects" are developer defined, persisted entities, stored in the Kibana system index (which is also sometimes referred to as the `.kibana` index).
+The Saved Objects service allows Kibana plugins to use Elasticsearch like a primary database. Think of it as an Object Document Mapper for Elasticsearch.
+ Some examples of Saved Object types are dashboards, lens, canvas workpads, index patterns, cases, ml jobs, and advanced settings. Some Saved Object types are
+ exposed to the user in the [Saved Object management UI](https://www.elastic.co/guide/en/kibana/current/managing-saved-objects.html), but not all.
+
+Developers create and manage their Saved Objects using the SavedObjectClient, while other data in Elasticsearch should be accessed via the data plugin's search
+services.
+
+![image](../assets/saved_object_vs_data_indices.png)
+
+
+
+
+## References
+
+In order to support import and export, and space-sharing capabilities, Saved Objects need to explicitly list any references they contain to other Saved Objects.
+The parent should have a reference to it's children, not the other way around. That way when a "parent" is exported (or shared to a space),
+ all the "children" will be automatically included. However, when a "child" is exported, it will not include all "parents".
+
+
+
+## Migrations and Backward compatibility
+
+As your plugin evolves, you may need to change your Saved Object type in a breaking way (for example, changing the type of an attribtue, or removing
+an attribute). If that happens, you should write a migration to upgrade the Saved Objects that existed prior to the change.
+
+.
+
+## Security
+
+Saved Objects can be secured using Kibana's Privileges model, unlike data that comes from data indices, which is secured using Elasticsearch's Privileges model.
+
+### Space awareness
+
+Saved Objects are "space aware". They exist in the space they were created in, and any spaces they have been shared with.
+
+### Feature controls and RBAC
+
+Feature controls provide another level of isolation and shareability for Saved Objects. Admins can give users and roles read, write or none permissions for each Saved Object type.
+
+### Object level security (OLS)
+
+OLS is an oft-requested feature that is not implemented yet. When it is, it will provide users with even more sharing and privacy flexibility. Individual
+objects can be private to the user, shared with a selection of others, or made public. Much like how sharing Google Docs works.
+
+## Scalability
+
+By default all saved object types go into a single index. If you expect your saved object type to have a lot of unique fields, or if you expect there
+to be many of them, you can have your objects go in a separate index by using the `indexPattern` field. Reporting and task manager are two
+examples of features that use this capability.
+
+## Searchability
+
+Because saved objects are stored in system indices, they cannot be searched like other data can. If you see the phrase “[X] as data” it is
+referring to this searching limitation. Users will not be able to create custom dashboards using saved object data, like they would for data stored
+in Elasticsearch data indices.
+
+## Saved Objects by value
+
+Sometimes Saved Objects end up persisted inside another Saved Object. We call these Saved Objects “by value”, as opposed to "by
+ reference". If an end user creates a visualization and adds it to a dashboard without saving it to the visualization
+ library, the data ends up nested inside the dashboard Saved Object. This helps keep the visualization library smaller. It also avoids
+ issues with edits propagating - since an entity can only exist in a single place.
+ Note that from the end user stand point, we don’t use these terms “by reference” and “by value”.
+
diff --git a/dev_docs/tutorials/saved_objects.mdx b/dev_docs/tutorials/saved_objects.mdx
new file mode 100644
index 0000000000000..bd7d231218af1
--- /dev/null
+++ b/dev_docs/tutorials/saved_objects.mdx
@@ -0,0 +1,250 @@
+---
+id: kibDevTutorialSavedObject
+slug: /kibana-dev-docs/tutorial/saved-objects
+title: Register a new saved object type
+summary: Learn how to register a new saved object type.
+date: 2021-02-05
+tags: ['kibana','onboarding', 'dev', 'architecture', 'tutorials']
+---
+
+Saved Object type definitions should be defined in their own `my_plugin/server/saved_objects` directory.
+
+The folder should contain a file per type, named after the snake_case name of the type, and an index.ts file exporting all the types.
+
+**src/plugins/my_plugin/server/saved_objects/dashboard_visualization.ts**
+
+```ts
+import { SavedObjectsType } from 'src/core/server';
+
+export const dashboardVisualization: SavedObjectsType = {
+ name: 'dashboard_visualization', [1]
+ hidden: false,
+ namespaceType: 'single',
+ mappings: {
+ dynamic: false,
+ properties: {
+ description: {
+ type: 'text',
+ },
+ hits: {
+ type: 'integer',
+ },
+ },
+ },
+ migrations: {
+ '1.0.0': migratedashboardVisualizationToV1,
+ '2.0.0': migratedashboardVisualizationToV2,
+ },
+};
+```
+
+[1] Since the name of a Saved Object type forms part of the url path for the public Saved Objects HTTP API,
+these should follow our API URL path convention and always be written as snake case.
+
+**src/plugins/my_plugin/server/saved_objects/index.ts**
+
+```ts
+export { dashboardVisualization } from './dashboard_visualization';
+export { dashboard } from './dashboard';
+```
+
+**src/plugins/my_plugin/server/plugin.ts**
+
+```ts
+import { dashboard, dashboardVisualization } from './saved_objects';
+
+export class MyPlugin implements Plugin {
+ setup({ savedObjects }) {
+ savedObjects.registerType(dashboard);
+ savedObjects.registerType(dashboardVisualization);
+ }
+}
+```
+
+## Mappings
+
+Each Saved Object type can define its own Elasticsearch field mappings. Because multiple Saved Object
+types can share the same index, mappings defined by a type will be nested under a top-level field that matches the type name.
+
+For example, the mappings defined by the dashboard_visualization Saved Object type:
+
+**src/plugins/my_plugin/server/saved_objects/dashboard_visualization.ts**
+
+```ts
+import { SavedObjectsType } from 'src/core/server';
+
+export const dashboardVisualization: SavedObjectsType = {
+ name: 'dashboard_visualization',
+ ...
+ mappings: {
+ properties: {
+ dynamic: false,
+ description: {
+ type: 'text',
+ },
+ hits: {
+ type: 'integer',
+ },
+ },
+ },
+ migrations: { ... },
+};
+```
+
+Will result in the following mappings being applied to the .kibana index:
+
+```ts
+{
+ "mappings": {
+ "dynamic": "strict",
+ "properties": {
+ ...
+ "dashboard_vizualization": {
+ "dynamic": false,
+ "properties": {
+ "description": {
+ "type": "text",
+ },
+ "hits": {
+ "type": "integer",
+ },
+ },
+ }
+ }
+ }
+}
+```
+Do not use field mappings like you would use data types for the columns of a SQL database. Instead, field mappings are analogous to a
+SQL index. Only specify field mappings for the fields you wish to search on or query. By specifying `dynamic: false`
+ in any level of your mappings, Elasticsearch will accept and store any other fields even if they are not specified in your mappings.
+
+Since Elasticsearch has a default limit of 1000 fields per index, plugins should carefully consider the
+fields they add to the mappings. Similarly, Saved Object types should never use `dynamic: true` as this can cause an arbitrary
+ amount of fields to be added to the .kibana index.
+
+ ## References
+
+Declare by adding an id, type and name to the
+ `references` array.
+
+```ts
+router.get(
+ { path: '/some-path', validate: false },
+ async (context, req, res) => {
+ const object = await context.core.savedObjects.client.create(
+ 'dashboard',
+ {
+ title: 'my dashboard',
+ panels: [
+ { visualization: 'vis1' }, [1]
+ ],
+ indexPattern: 'indexPattern1'
+ },
+ { references: [
+ { id: '...', type: 'visualization', name: 'vis1' },
+ { id: '...', type: 'index_pattern', name: 'indexPattern1' },
+ ]
+ }
+ )
+ ...
+ }
+);
+```
+[1] Note how `dashboard.panels[0].visualization` stores the name property of the reference (not the id directly) to be able to uniquely
+identify this reference. This guarantees that the id the reference points to always remains up to date. If a
+ visualization id was directly stored in `dashboard.panels[0].visualization` there is a risk that this id gets updated without
+ updating the reference in the references array.
+
+## Writing migrations
+
+Saved Objects support schema changes between Kibana versions, which we call migrations. Migrations are
+ applied when a Kibana installation is upgraded from one version to the next, when exports are imported via
+ the Saved Objects Management UI, or when a new object is created via the HTTP API.
+
+Each Saved Object type may define migrations for its schema. Migrations are specified by the Kibana version number, receive an input document,
+ and must return the fully migrated document to be persisted to Elasticsearch.
+
+Let’s say we want to define two migrations: - In version 1.1.0, we want to drop the subtitle field and append it to the title - In version
+ 1.4.0, we want to add a new id field to every panel with a newly generated UUID.
+
+First, the current mappings should always reflect the latest or "target" schema. Next, we should define a migration function for each step in the schema evolution:
+
+**src/plugins/my_plugin/server/saved_objects/dashboard_visualization.ts**
+
+```ts
+import { SavedObjectsType, SavedObjectMigrationFn } from 'src/core/server';
+import uuid from 'uuid';
+
+interface DashboardVisualizationPre110 {
+ title: string;
+ subtitle: string;
+ panels: Array<{}>;
+}
+interface DashboardVisualization110 {
+ title: string;
+ panels: Array<{}>;
+}
+
+interface DashboardVisualization140 {
+ title: string;
+ panels: Array<{ id: string }>;
+}
+
+const migrateDashboardVisualization110: SavedObjectMigrationFn<
+ DashboardVisualizationPre110, [1]
+ DashboardVisualization110
+> = (doc) => {
+ const { subtitle, ...attributesWithoutSubtitle } = doc.attributes;
+ return {
+ ...doc, [2]
+ attributes: {
+ ...attributesWithoutSubtitle,
+ title: `${doc.attributes.title} - ${doc.attributes.subtitle}`,
+ },
+ };
+};
+
+const migrateDashboardVisualization140: SavedObjectMigrationFn<
+ DashboardVisualization110,
+ DashboardVisualization140
+> = (doc) => {
+ const outPanels = doc.attributes.panels?.map((panel) => {
+ return { ...panel, id: uuid.v4() };
+ });
+ return {
+ ...doc,
+ attributes: {
+ ...doc.attributes,
+ panels: outPanels,
+ },
+ };
+};
+
+export const dashboardVisualization: SavedObjectsType = {
+ name: 'dashboard_visualization', [1]
+ /** ... */
+ migrations: {
+ // Takes a pre 1.1.0 doc, and converts it to 1.1.0
+ '1.1.0': migrateDashboardVisualization110,
+
+ // Takes a 1.1.0 doc, and converts it to 1.4.0
+ '1.4.0': migrateDashboardVisualization140, [3]
+ },
+};
+```
+[1] It is useful to define an interface for each version of the schema. This allows TypeScript to ensure that you are properly handling the input and output
+ types correctly as the schema evolves.
+
+[2] Returning a shallow copy is necessary to avoid type errors when using different types for the input and output shape.
+
+[3] Migrations do not have to be defined for every version. The version number of a migration must always be the earliest Kibana version
+ in which this migration was released. So if you are creating a migration which will
+ be part of the v7.10.0 release, but will also be backported and released as v7.9.3, the migration version should be: 7.9.3.
+
+ Migrations should be written defensively, an exception in a migration function will prevent a Kibana upgrade from succeeding and will cause downtime for our users.
+ Having said that, if a
+ document is encountered that is not in the expected shape, migrations are encouraged to throw an exception to abort the upgrade. In most scenarios, it is better to
+ fail an upgrade than to silently ignore a corrupt document which can cause unexpected behaviour at some future point in time.
+
+It is critical that you have extensive tests to ensure that migrations behave as expected with all possible input documents. Given how simple it is to test all the branch
+conditions in a migration function and the high impact of a bug in this code, there’s really no reason not to aim for 100% test code coverage.