diff --git a/package-lock.json b/package-lock.json
index d42101a6df..f58d07e913 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -709,8 +709,8 @@
     },
     "@types/moment": {
       "version": "2.13.0",
-      "resolved": false,
-      "integrity": "sha1-YE69GJvDvDShVIaJQE5hoqSqyJY=",
+      "resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz",
+      "integrity": "sha512-DyuyYGpV6r+4Z1bUznLi/Y7HpGn4iQ4IVcGn8zrr1P4KotKLdH0sbK1TFR6RGyX6B+G8u83wCzL+bpawKU/hdQ==",
       "dev": true,
       "requires": {
         "moment": "*"
@@ -6876,8 +6876,8 @@
     },
     "grunt-karma": {
       "version": "2.0.0",
-      "resolved": false,
-      "integrity": "sha1-dTWD0RXf3AVf5X5Y+W1rPH5hIRg=",
+      "resolved": "https://registry.npmjs.org/grunt-karma/-/grunt-karma-2.0.0.tgz",
+      "integrity": "sha512-/5plsdrES8dWrGhg33Q7AiYU1PUHXtMcZLP2pAppUJJKNmCpiGZXpVfHZ7KO19buVxb555UFbfhhbY7FccXH4g==",
       "dev": true,
       "requires": {
         "lodash": "^3.10.1"
@@ -6886,7 +6886,7 @@
         "lodash": {
           "version": "3.10.1",
           "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
-          "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
+          "integrity": "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==",
           "dev": true
         }
       }
@@ -8967,8 +8967,8 @@
     },
     "jasmine-core": {
       "version": "2.99.1",
-      "resolved": false,
-      "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=",
+      "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz",
+      "integrity": "sha512-ra97U4qu3OCcIxvN6eg3kyy8bLrID/TgxafSGMMICg3SFx5C/sUfDPpiOh7yoIsHdtjrOVdtT9rieYhqOsh9Ww==",
       "dev": true
     },
     "jit-grunt": {
@@ -9563,14 +9563,14 @@
     },
     "karma-jasmine": {
       "version": "1.1.2",
-      "resolved": false,
-      "integrity": "sha1-OU8rJf+0pkS5rabyLUQ+L9CIhsM=",
+      "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.2.tgz",
+      "integrity": "sha512-SENGE9DhlIIFTSZWiNq4eGeXL8G6z9cqHIOdkx9jh1qhhQqwEy3tAoLRyER0vOcHqdOlKmGpOuXk+HOipIy7sg==",
       "dev": true
     },
     "karma-ng-html2js-preprocessor": {
       "version": "1.0.0",
-      "resolved": false,
-      "integrity": "sha1-ENjIz6pBNvHIp22RpMvO7evsSjE=",
+      "resolved": "https://registry.npmjs.org/karma-ng-html2js-preprocessor/-/karma-ng-html2js-preprocessor-1.0.0.tgz",
+      "integrity": "sha512-Ho7VxRglFeIsRDimaC0ZFdwb4P5FS3o36o4PpuEAkIdhb6BrucGGpUuVWEB4KQIOtZISJaN6eTcXTfCX3kbEHw==",
       "dev": true
     },
     "karma-sourcemap-loader": {
@@ -13320,8 +13320,8 @@
     },
     "react-addons-test-utils": {
       "version": "15.6.2",
-      "resolved": false,
-      "integrity": "sha1-wStu/cIkfBDae4dw0YUICnsEcVY=",
+      "resolved": "https://registry.npmjs.org/react-addons-test-utils/-/react-addons-test-utils-15.6.2.tgz",
+      "integrity": "sha512-6IUCnLp7jQRBftm2anf8rP8W+8M2PsC7GPyMFe2Wef3Wfml7j2KybVL//Ty7bRDBqLh8AG4m/zNZbFlwulldFw==",
       "dev": true
     },
     "react-autocomplete": {
@@ -16867,7 +16867,7 @@
         "core-util-is": {
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
-          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+          "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
         }
       }
     },
diff --git a/scripts/api/templates.ts b/scripts/api/templates.ts
index bbc588bd21..4d57bb6cda 100644
--- a/scripts/api/templates.ts
+++ b/scripts/api/templates.ts
@@ -1,10 +1,109 @@
+import {
+    applyMiddleware,
+    canEdit,
+    cleanData,
+    prepareData,
+    willCreateNew,
+} from 'apps/authoring-react/toolbar/template-helpers';
+import {httpRequestJsonLocal} from 'core/helpers/network';
 import ng from 'core/services/ng';
-import {ITemplate} from 'superdesk-api';
+import {clone} from 'lodash';
+import {IArticle, IDesk, ITemplate} from 'superdesk-api';
 
 function getById(id: ITemplate['_id']): Promise<ITemplate> {
-    return ng.get('templates').find(id);
+    return httpRequestJsonLocal<ITemplate>({
+        method: 'GET',
+        path: `/content_templates/${id}`,
+    });
+}
+
+function createTemplate(payload) {
+    return httpRequestJsonLocal<ITemplate>({
+        method: 'POST',
+        path: '/content_templates',
+        payload,
+    });
+}
+
+function updateTemplate(payload, template: ITemplate) {
+    return httpRequestJsonLocal<ITemplate>({
+        method: 'PATCH',
+        path: `/content_templates/${template._id}`,
+        payload,
+        headers: {'If-Match': template._etag},
+    });
+}
+
+/**
+ * Creates or updates a template. If the article has an existing template it will be updated.
+ *
+ * @templateName - template name from the form input
+ * @selectedDeskId - deskId selected in the form
+ */
+function createTemplateFromArticle(
+    // The new template will be based on this article
+    sourceArticle: IArticle,
+    templateName: string,
+    selectedDeskId: IDesk['_id'] | null,
+): Promise<ITemplate> {
+    return getById(sourceArticle.template).then((resultTemplate) => {
+        const data = prepareData(resultTemplate);
+        const deskId = selectedDeskId || data.desk;
+        const templateArticle = data.template;
+        const item: IArticle = clone(ng.get('templates').pickItemData(sourceArticle));
+        const userId = ng.get('session').identity._id;
+
+        return applyMiddleware(item).then((itemAfterMiddleware) => {
+            const newTemplate: Partial<ITemplate> = {
+                template_name: templateName,
+                template_type: 'create',
+                template_desks: selectedDeskId != null ? [deskId] : null,
+                is_public: templateArticle.is_public,
+                data: itemAfterMiddleware,
+            };
+
+            let templateTemp: Partial<ITemplate> = templateArticle != null ? templateArticle : newTemplate;
+            let diff = templateArticle != null ? newTemplate : null;
+
+            if (willCreateNew(templateArticle, templateName, selectedDeskId != null)) {
+                templateTemp = newTemplate;
+                diff = null;
+
+                if (!canEdit(templateArticle, selectedDeskId != null)) {
+                    templateTemp.is_public = false;
+                    templateTemp.user = userId;
+                    templateTemp.template_desks = null;
+                }
+            }
+
+            const hasLinks = templateTemp._links != null;
+            const payload: Partial<ITemplate> = diff != null ? cleanData(diff) : cleanData(templateTemp);
+
+            // if the template is made private, set the current user as template owner
+            if (templateArticle.is_public && (diff?.is_public === false || templateTemp.is_public === false)) {
+                payload.user = userId;
+            }
+
+            const requestPayload: Partial<ITemplate> = {
+                ...payload,
+                data: cleanData<IArticle>(sourceArticle),
+            };
+
+            return (hasLinks
+                ? updateTemplate(requestPayload, resultTemplate)
+                : createTemplate(requestPayload)
+            )
+                .then((_data) => {
+                    return _data;
+                }, (response) => {
+                    return Promise.reject(response);
+                });
+        });
+    });
 }
 
 export const templates = {
     getById,
+    createTemplateFromArticle,
+    prepareData,
 };
diff --git a/scripts/apps/authoring-react/authoring-integration-wrapper.tsx b/scripts/apps/authoring-react/authoring-integration-wrapper.tsx
index bea368ab5b..1c47c1d1d3 100644
--- a/scripts/apps/authoring-react/authoring-integration-wrapper.tsx
+++ b/scripts/apps/authoring-react/authoring-integration-wrapper.tsx
@@ -33,6 +33,7 @@ import {CreatedModifiedInfo} from './subcomponents/created-modified-info';
 import {dispatchInternalEvent} from 'core/internal-events';
 import {IArticleActionInteractive} from 'core/interactive-article-actions-panel/interfaces';
 import {ARTICLE_RELATED_RESOURCE_NAMES} from 'core/constants';
+import {TemplateModal} from './toolbar/template-modal';
 import {IProps} from './authoring-angular-integration';
 import {showModal} from '@superdesk/common';
 import ExportModal from './toolbar/export-modal';
@@ -222,6 +223,20 @@ export class AuthoringIntegrationWrapper extends React.PureComponent<IPropsWrapp
                 (Component) => (props: {item: IArticle}) => <Component article={props.item} />,
             );
 
+        const saveAsTemplate = (item: IArticle): IAuthoringAction => ({
+            label: gettext('Save as template'),
+            onTrigger: () => (
+                showModal(({closeModal}) => {
+                    return (
+                        <TemplateModal
+                            closeModal={closeModal}
+                            item={item}
+                        />
+                    );
+                })
+            ),
+        });
+
         return (
             <WithInteractiveArticleActionsPanel location="authoring">
                 {(panelState, panelActions) => {
@@ -269,6 +284,7 @@ export class AuthoringIntegrationWrapper extends React.PureComponent<IPropsWrapp
                                     const [authoringActionsFromExtensions, articleActionsFromExtensions] = res;
 
                                     return [
+                                        saveAsTemplate(item),
                                         getExportModal(getLatestItem, handleUnsavedChanges, hasUnsavedChanges),
                                         ...authoringActionsFromExtensions,
                                         ...articleActionsFromExtensions,
diff --git a/scripts/apps/authoring-react/toolbar/template-helpers.ts b/scripts/apps/authoring-react/toolbar/template-helpers.ts
new file mode 100644
index 0000000000..b4800a4f5a
--- /dev/null
+++ b/scripts/apps/authoring-react/toolbar/template-helpers.ts
@@ -0,0 +1,119 @@
+import ng from 'core/services/ng';
+import {extensions} from 'appConfig';
+import {IArticle, ICustomFieldType, ITemplate, IVocabulary} from 'superdesk-api';
+
+export function applyMiddleware(_item: IArticle): Promise<IArticle> {
+    // Custom field types with `onTemplateCreate` defined. From all extensions.
+    const fieldTypes: {[id: string]: ICustomFieldType<any, any, any, any>} = {};
+
+    Object.values(extensions).forEach((ext) => {
+        ext?.activationResult?.contributions?.customFieldTypes?.forEach(
+            (customField: ICustomFieldType<any, any, any, any>) => {
+                if (customField.onTemplateCreate != null) {
+                    fieldTypes[customField.id] = customField;
+                }
+            },
+        );
+    });
+
+    return ng.get('vocabularies').getVocabularies().then((_vocabularies: Array<IVocabulary>) => {
+        const fakeScope: any = {};
+
+        return ng.get('content').setupAuthoring(_item.profile, fakeScope, _item).then(() => {
+            let itemNext: IArticle = {..._item};
+
+            for (const fieldId of Object.keys(fakeScope.editor)) {
+                const vocabulary = _vocabularies.find(({_id}) => _id === fieldId);
+
+                if (vocabulary != null && fieldTypes[vocabulary.custom_field_type] != null) {
+                    const config = vocabulary.custom_field_config ?? {};
+                    const customField = fieldTypes[vocabulary.custom_field_type];
+
+                    itemNext = {
+                        ...itemNext,
+                        extra: {
+                            ...itemNext.extra,
+                            [fieldId]: customField.onTemplateCreate(
+                                itemNext?.extra?.[fieldId],
+                                config,
+                            ),
+                        },
+                    };
+                }
+            }
+
+            return itemNext;
+        });
+    });
+}
+
+export function prepareData(template: ITemplate) {
+    return {
+        name: template.template_name,
+        desk: template.template_desks != null ? template.template_desks[0] : null,
+        template,
+    };
+}
+
+export function cleanData<T>(data: Partial<T>): Partial<T> {
+    [
+        '_type',
+        '_status',
+        '_updated',
+        '_created',
+        '_etag',
+        '_links',
+        '_id',
+        '_current_version',
+        '_etag',
+        '_links',
+        'expiry',
+        'lock_user',
+        'original_id',
+        'schedule_settings',
+        'semantics',
+        '_autosave',
+        '_editable',
+        '_latest_version',
+        '_locked',
+        'time_zone',
+    ].forEach((field) => {
+        delete data[field];
+    });
+
+    return data;
+}
+
+/**
+ * Determines whether the template will be overwritten or a new one will be created.
+ * The is_public parameter is needed because we get it from user input and not from the fetched template.
+ * is_public is set from the user - if the template is made as a desk template then is_public is true.
+ */
+export function canEdit(template: ITemplate, isPublic: boolean): boolean {
+    const privileges = ng.get('privileges');
+
+    if (template == null) {
+        return false;
+    } else if (template?.is_public && !isPublic) {
+        // if template is changed from public to private, always
+        // create a copy of a template and don't modify the original one.
+        return false;
+    } else if (isPublic) {
+        return privileges.userHasPrivileges({content_templates: 1});
+    } else if (template?.user === ng.get('session').identity._id) {
+        return true;
+    } else {
+        return privileges.userHasPrivileges({personal_template: 1}); // can edit templates of other users
+    }
+}
+
+export const wasRenamed = (
+    template: ITemplate,
+    templateName: string,
+) => template != null && templateName !== template.template_name;
+
+export const willCreateNew = (
+    template: ITemplate,
+    templateName: string,
+    isPublic: boolean,
+) => template == null || wasRenamed(template, templateName) || canEdit(template, isPublic) !== true;
diff --git a/scripts/apps/authoring-react/toolbar/template-modal.tsx b/scripts/apps/authoring-react/toolbar/template-modal.tsx
new file mode 100644
index 0000000000..c34191c292
--- /dev/null
+++ b/scripts/apps/authoring-react/toolbar/template-modal.tsx
@@ -0,0 +1,184 @@
+import {sdApi} from 'api';
+import {Spacer} from 'core/ui/components/Spacer';
+import {gettext} from 'core/utils';
+import React from 'react';
+import {IArticle, ITemplate} from 'superdesk-api';
+import {Alert, Button, Checkbox, Input, Modal, Option, Select} from 'superdesk-ui-framework/react';
+import {canEdit, wasRenamed} from './template-helpers';
+
+interface IProps {
+    item: IArticle;
+    closeModal: () => void;
+}
+
+interface IStateLoading {
+    initialized: false;
+}
+
+interface IStateLoaded {
+    initialized: true;
+    templateName: string | null;
+    isDeskTemplate: boolean;
+    responseError: string | null;
+    deskId: string | null;
+    template: ITemplate | null;
+}
+
+type IState = IStateLoaded | IStateLoading;
+
+export class TemplateModal extends React.PureComponent<IProps, IState> {
+    constructor(props: IProps) {
+        super(props);
+
+        this.state = {
+            initialized: false,
+        };
+    }
+
+    componentDidMount(): void {
+        sdApi.templates.getById(this.props.item.template).then((res) => {
+            this.setState({
+                initialized: true,
+                template: res,
+                templateName: res.template_name,
+                isDeskTemplate: true,
+                responseError: null,
+                deskId: res.template_desks[0],
+            });
+        });
+    }
+
+    render(): JSX.Element {
+        const availableDesks = sdApi.desks.getAllDesks().toArray();
+
+        if (!this.state.initialized) {
+            return null;
+        }
+
+        const state = this.state;
+
+        return (
+            <Modal
+                visible
+                onHide={() => this.props.closeModal()}
+                size="medium"
+                zIndex={1050}
+                headerTemplate={gettext('Save as template')}
+            >
+                <Spacer v gap="16">
+                    <Input
+                        type="text"
+                        label={gettext('Template name')}
+                        value={state.templateName}
+                        onChange={(value) => this.setState({
+                            ...state,
+                            templateName: value,
+                        })}
+                    />
+                    {
+                        state.responseError != null && (
+                            <Alert
+                                margin="none"
+                                size="small"
+                                type="alert"
+                            >
+                                {state.responseError}
+                            </Alert>
+                        )
+                    }
+                    {
+                        /**
+                         * A new template will be created:
+                         * - if the input template name differs from the initially fetched template name
+                         * - if the initially fetched template from the article is null
+                         * - if the initially fetched template from the article can't be edited
+                         *
+                         * Else the existing template will be updated
+                         */
+                        wasRenamed(state.template, state.templateName)
+                            || state.template == null
+                            || canEdit(state.template, state.deskId != null) !== true
+                            ? (
+                                <Alert
+                                    margin="none"
+                                    size="small"
+                                    type="warning"
+                                    style="hollow"
+                                >
+                                    {gettext('A new template will be created')}
+                                </Alert>
+                            )
+                            : (
+                                <Alert
+                                    margin="none"
+                                    size="small"
+                                    type="warning"
+                                    style="hollow"
+                                >
+                                    {gettext('Template will be updated')}
+                                </Alert>
+                            )
+                    }
+                    {
+                        availableDesks != null && state.template.is_public &&
+                        (
+                            <>
+                                <Checkbox
+                                    label={{text: gettext('Desk template')}}
+                                    checked={state.isDeskTemplate}
+                                    onChange={() => this.setState({
+                                        ...state,
+                                        deskId: state.isDeskTemplate ? null : state.deskId,
+                                        isDeskTemplate: !state.isDeskTemplate,
+                                    })}
+                                />
+                                {
+                                    state.isDeskTemplate && (
+                                        <Select
+                                            label={gettext('Desks')}
+                                            value={state.deskId}
+                                            onChange={(value) => {
+                                                this.setState({...state, deskId: value});
+                                            }}
+                                        >
+                                            <Option />
+                                            {
+                                                availableDesks.map(({_id, name}) => (
+                                                    <Option key={_id} value={_id}>{name}</Option>
+                                                ))
+                                            }
+                                        </Select>
+                                    )
+                                }
+                            </>
+                        )
+                    }
+                    <Spacer h gap="8" justifyContent="end" noGrow>
+                        <Button
+                            text={gettext('Cancel')}
+                            onClick={() => this.props.closeModal()}
+                        />
+                        <Button
+                            type="primary"
+                            text={gettext('Save')}
+                            onClick={() => {
+                                sdApi.templates.createTemplateFromArticle(
+                                    this.props.item,
+                                    state.templateName,
+                                    state.deskId,
+                                )
+                                    .then(() => {
+                                        this.props.closeModal();
+                                    })
+                                    .catch((error) => {
+                                        this.setState({...state, responseError: error._issues.is_public});
+                                    });
+                            }}
+                        />
+                    </Spacer>
+
+                </Spacer>
+            </Modal>
+        );
+    }
+}
diff --git a/scripts/apps/templates/controllers/CreateTemplateController.ts b/scripts/apps/templates/controllers/CreateTemplateController.ts
index 2fab89ac37..3be60e02ce 100644
--- a/scripts/apps/templates/controllers/CreateTemplateController.ts
+++ b/scripts/apps/templates/controllers/CreateTemplateController.ts
@@ -1,57 +1,6 @@
 import notifySaveError from '../helpers';
-import {extensions} from 'appConfig';
-import {IArticle, ICustomFieldType, IVocabulary} from 'superdesk-api';
-
-/**
- * Iterate content profile fields.
- * Check if field is a custom field from extension.
- * If it is, run `onTemplateCreate` middleware on it
- * and update the value.
- */
-function applyMiddleware(_item: IArticle, content, vocabularies): Promise<IArticle> {
-    // Custom field types with `onTemplateCreate` defined. From all extensions.
-    const fieldTypes: {[id: string]: ICustomFieldType<any, any, any, any>} = {};
-
-    Object.values(extensions).forEach((ext) => {
-        ext?.activationResult?.contributions?.customFieldTypes?.forEach(
-            (customField: ICustomFieldType<any, any, any, any>) => {
-                if (customField.onTemplateCreate != null) {
-                    fieldTypes[customField.id] = customField;
-                }
-            },
-        );
-    });
-
-    return vocabularies.getVocabularies().then((_vocabularies: Array<IVocabulary>) => {
-        const fakeScope: any = {};
-
-        return content.setupAuthoring(_item.profile, fakeScope, _item).then(() => {
-            let itemNext: IArticle = {..._item};
-
-            for (const fieldId of Object.keys(fakeScope.editor)) {
-                const vocabulary = _vocabularies.find(({_id}) => _id === fieldId);
-
-                if (vocabulary != null && fieldTypes[vocabulary.custom_field_type] != null) {
-                    const config = vocabulary.custom_field_config ?? {};
-                    const customField = fieldTypes[vocabulary.custom_field_type];
-
-                    itemNext = {
-                        ...itemNext,
-                        extra: {
-                            ...itemNext.extra,
-                            [fieldId]: customField.onTemplateCreate(
-                                itemNext?.extra?.[fieldId],
-                                config,
-                            ),
-                        },
-                    };
-                }
-            }
-
-            return itemNext;
-        });
-    });
-}
+import {sdApi} from 'api';
+import {willCreateNew} from 'apps/authoring-react/toolbar/template-helpers';
 
 CreateTemplateController.$inject = [
     'item',
@@ -74,10 +23,6 @@ export function CreateTemplateController(
     $q,
     notify,
     _,
-    privileges,
-    session,
-    content,
-    vocabularies,
 ) {
     var self = this;
 
@@ -95,10 +40,12 @@ export function CreateTemplateController(
     function activate() {
         if (item.template) {
             api.find('content_templates', item.template).then((template) => {
-                self.name = template.template_name;
-                self.desk = !_.isNil(template.template_desks) ? template.template_desks[0] : null;
-                self.is_public = template.is_public !== false;
-                self.template = template;
+                const data = sdApi.templates.prepareData(template);
+
+                self.name = data.template.template_name;
+                self.desk = data.desk;
+                self.is_public = data.template.is_public;
+                self.template = data.template;
             });
         }
 
@@ -107,74 +54,18 @@ export function CreateTemplateController(
         });
     }
 
-    self.canEdit = () => {
-        if (self.template == null) {
-            return false; // no template exists yet
-        } else if (self.template?.is_public === true && self.is_public === false) {
-            // if template is changed from public to private, always create a copy of a template
-            // and don't modify the original one.
-            return false;
-        } else if (self.is_public === true) {
-            return privileges.userHasPrivileges({content_templates: 1});
-        } else if (self.template?.user === session.identity._id) {
-            return true; // can always edit own templates
-        } else {
-            return privileges.userHasPrivileges({personal_template: 1}); // can edit templates of other users
-        }
-    };
-
-    self.wasRenamed = () => {
-        return self.template != null && self.name !== self.template.template_name;
-    };
-
-    self.willCreateNew = () =>
-        self.template == null // no template exists yet
-        || self.wasRenamed()
-        || self.canEdit() !== true;
+    self.willCreateNew = () => willCreateNew(self.template, self.name, self.is_public);
 
     function save() {
-        const _item: IArticle = JSON.parse(JSON.stringify(templates.pickItemData(item)));
-        const sessionId = session.identity._id;
-
-        return applyMiddleware(_item, content, vocabularies).then((itemAfterMiddleware) => {
-            var data = {
-                template_name: self.name,
-                template_type: self.type,
-                template_desks: self.is_public ? [self.desk] : null,
-                is_public: self.is_public,
-                data: itemAfterMiddleware,
-            };
-
-            var template = self.template ? self.template : data;
-            var diff: any = self.template ? data : null;
-
-            // in case there is old template but user renames it
-            // or user is not allowed to edit it - create a new one
-            if (self.willCreateNew()) {
-                template = data;
-                diff = null;
-
-                if (self.canEdit() !== true) {
-                    template.is_public = false;
-                    template.user = sessionId;
-                    template.template_desks = null;
-                }
-            }
-
-            // if template is made private, set current user as template owner
-            if (template.is_public === true && diff?.is_public === false) {
-                diff.user = sessionId;
-            }
-
-            return api.save('content_templates', template, diff)
-                .then((_data) => {
-                    self._issues = null;
-                    return _data;
-                }, (response) => {
-                    notifySaveError(response, notify);
-                    self._issues = response.data._issues;
-                    return $q.reject(self._issues);
-                });
-        });
+        return sdApi.templates.createTemplateFromArticle(item, self.name, self.is_public ? self.desk : null)
+            .then((data) => {
+                self._issues = null;
+                return data;
+            })
+            .catch((error) => {
+                notifySaveError({data: error}, notify);
+                self._issues = error._issues;
+                return $q.reject(self._issues);
+            });
     }
 }
diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts
index a9f8ffe572..a139af30fc 100644
--- a/scripts/core/superdesk-api.d.ts
+++ b/scripts/core/superdesk-api.d.ts
@@ -1041,7 +1041,7 @@ declare module 'superdesk-api' {
             }
         };
         version: any;
-        template: any;
+        template: ITemplate['_id'];
         original_creator: string;
         unique_id: any;
         operation: any;
@@ -3061,14 +3061,14 @@ declare module 'superdesk-api' {
     }
 
     export interface ITemplate extends IBaseRestApiResponse {
-        data: IArticle,
-        is_public: boolean,
+        data: Partial<IArticle>;
+        is_public: boolean;
         next_run?: any;
         schedule?: any;
-        template_desks: Array<IDesk['_id']>,
-        template_name: string,
-        template_type: 'create' | 'kill' | string,
-        user: IUser['_id']
+        template_desks: Array<IDesk['_id']>;
+        template_name: string;
+        template_type: 'create' | 'kill' | string;
+        user: IUser['_id'];
     }