diff --git a/app/assets/javascripts/content_editor/extensions/image.js b/app/assets/javascripts/content_editor/extensions/image.js
index 465f75624ad444a5b9044962a27ffadc3f0dc7bc..f7deabb538504ae4c83c3790751350256125ad6e 100644
--- a/app/assets/javascripts/content_editor/extensions/image.js
+++ b/app/assets/javascripts/content_editor/extensions/image.js
@@ -2,6 +2,7 @@ import { Image } from '@tiptap/extension-image';
 import { VueNodeViewRenderer } from '@tiptap/vue-2';
 import { PARSE_HTML_PRIORITY_HIGH } from '../constants';
 import ImageWrapper from '../components/wrappers/image.vue';
+import { getSourceMapAttributes } from '../services/markdown_sourcemap';
 
 const resolveImageEl = (element) =>
   element.nodeName === 'IMG' ? element : element.querySelector('img');
@@ -76,6 +77,7 @@ export default Image.extend({
         default: false,
         renderHTML: () => '',
       },
+      ...getSourceMapAttributes(resolveImageEl),
     };
   },
   parseHTML() {
diff --git a/app/assets/javascripts/content_editor/extensions/playable.js b/app/assets/javascripts/content_editor/extensions/playable.js
index 3ac44c28f7e92c53ab28340755499061c29843a4..24c2d6e64ee4854b4650795a2cbbc3868aef5025 100644
--- a/app/assets/javascripts/content_editor/extensions/playable.js
+++ b/app/assets/javascripts/content_editor/extensions/playable.js
@@ -1,6 +1,7 @@
 import { Node } from '@tiptap/core';
 import { VueNodeViewRenderer } from '@tiptap/vue-2';
 import PlayableWrapper from '../components/wrappers/playable.vue';
+import { getSourceMapAttributes } from '../services/markdown_sourcemap';
 
 const queryPlayableElement = (element, mediaType) => element.querySelector(mediaType);
 
@@ -37,6 +38,7 @@ export default Node.create({
         parseHTML: (element) =>
           queryPlayableElement(element, this.options.mediaType).getAttribute('height'),
       },
+      ...getSourceMapAttributes((element) => queryPlayableElement(element, this.options.mediaType)),
     };
   },
 
diff --git a/app/assets/javascripts/content_editor/extensions/sourcemap.js b/app/assets/javascripts/content_editor/extensions/sourcemap.js
index 8ac4a7d07200fde1545be44d40a52c27f79f2b76..1eb0efb2b0dd24dd8d24429b883cbadff73f3ca1 100644
--- a/app/assets/javascripts/content_editor/extensions/sourcemap.js
+++ b/app/assets/javascripts/content_editor/extensions/sourcemap.js
@@ -1,5 +1,5 @@
 import { Extension } from '@tiptap/core';
-import { getMarkdownSource, docHasSourceMap } from '../services/markdown_sourcemap';
+import { getSourceMapAttributes } from '../services/markdown_sourcemap';
 import Audio from './audio';
 import Blockquote from './blockquote';
 import Bold from './bold';
@@ -37,8 +37,6 @@ export default Extension.create({
   name: 'sourcemap',
 
   addGlobalAttributes() {
-    const preserveMarkdown = () => gon.features?.preserveMarkdown;
-
     return [
       {
         types: [
@@ -75,30 +73,7 @@ export default Extension.create({
           Video.name,
           ...HTMLNodes.map((htmlNode) => htmlNode.name),
         ],
-        attributes: {
-          /**
-           * The reason to add a function that returns an empty
-           * string in these attributes is indicate that these
-           * attributes shouldn’t be rendered in the ProseMirror
-           * view.
-           */
-          sourceMarkdown: {
-            default: null,
-            parseHTML: (element) => (preserveMarkdown() ? getMarkdownSource(element) : null),
-            renderHTML: () => '',
-          },
-          sourceMapKey: {
-            default: null,
-            parseHTML: (element) => (preserveMarkdown() ? element.dataset.sourcepos : null),
-            renderHTML: () => '',
-          },
-          sourceTagName: {
-            default: null,
-            parseHTML: (element) =>
-              preserveMarkdown() && docHasSourceMap(element) ? element.tagName.toLowerCase() : null,
-            renderHTML: () => '',
-          },
-        },
+        attributes: getSourceMapAttributes(),
       },
     ];
   },
diff --git a/app/assets/javascripts/content_editor/services/markdown_sourcemap.js b/app/assets/javascripts/content_editor/services/markdown_sourcemap.js
index d7b0d48c54f190aaad2788163264d131a497c203..b2754dc4f4fcaa3e69feea7f95285838715b76d4 100644
--- a/app/assets/javascripts/content_editor/services/markdown_sourcemap.js
+++ b/app/assets/javascripts/content_editor/services/markdown_sourcemap.js
@@ -1,3 +1,7 @@
+import { identity } from 'lodash';
+
+const preserveMarkdown = () => gon.features?.preserveMarkdown;
+
 export const docHasSourceMap = (element) => {
   const commentNode = element.ownerDocument.body.lastChild;
   return Boolean(commentNode?.nodeName === '#comment' && commentNode.textContent);
@@ -24,6 +28,15 @@ const getRangeFromSourcePos = (sourcePos) => {
   };
 };
 
+const getMarkdownSourceKey = (element) => {
+  return element.dataset.sourcepos;
+};
+
+const getSourceTagName = (element) => {
+  if (!docHasSourceMap(element)) return undefined;
+  return element.tagName.toLowerCase();
+};
+
 export const getMarkdownSource = (element) => {
   if (!element.dataset.sourcepos) return undefined;
 
@@ -51,3 +64,23 @@ export const getMarkdownSource = (element) => {
     return undefined;
   }
 };
+
+export const getSourceMapAttributes = (queryElement = identity) => {
+  return {
+    sourceMarkdown: {
+      default: null,
+      parseHTML: (el) => (preserveMarkdown() ? getMarkdownSource(queryElement(el)) : null),
+      renderHTML: () => '',
+    },
+    sourceMapKey: {
+      default: null,
+      parseHTML: (el) => (preserveMarkdown() ? getMarkdownSourceKey(queryElement(el)) : null),
+      renderHTML: () => '',
+    },
+    sourceTagName: {
+      default: null,
+      parseHTML: (el) => (preserveMarkdown() ? getSourceTagName(queryElement(el)) : null),
+      renderHTML: () => '',
+    },
+  };
+};
diff --git a/app/assets/javascripts/content_editor/services/serializer/image.js b/app/assets/javascripts/content_editor/services/serializer/image.js
index de809d0bf2e3b1f411b89cabf7bd14de71d1c74d..e0b5deab38b403d90249a3479dd254edbce0c91b 100644
--- a/app/assets/javascripts/content_editor/services/serializer/image.js
+++ b/app/assets/javascripts/content_editor/services/serializer/image.js
@@ -17,7 +17,7 @@ const image = preserveUnchanged({
     if (realSrc.startsWith('data:') || realSrc.startsWith('blob:')) return;
 
     if (realSrc) {
-      if (sourceTagName && !sourceMarkdown) {
+      if (sourceTagName === 'img' && !sourceMarkdown) {
         const attrs = pickBy({ alt, title, width, height }, identity);
         state.write(openTag(sourceTagName, { src: realSrc, ...attrs }));
         return;
diff --git a/spec/frontend/content_editor/services/serializer/image_spec.js b/spec/frontend/content_editor/services/serializer/image_spec.js
index d20f32717ed0b7c7ef1e4777fd9c42f5cfd49568..32310ff265d6ceeee94256f45b2f121ffb0476f5 100644
--- a/spec/frontend/content_editor/services/serializer/image_spec.js
+++ b/spec/frontend/content_editor/services/serializer/image_spec.js
@@ -111,3 +111,17 @@ it('serializes image as an HTML tag if sourceTagName is defined', () => {
     '<img src="img.jpg" alt="image" title="image title">',
   );
 });
+
+it('does not serialize image as HTML if sourceTagName is not img', () => {
+  const imageAttrs = { src: 'img.jpg', alt: 'image', ...sourceTag('a') };
+
+  expect(serialize(paragraph(image(imageAttrs)))).toBe('![image](img.jpg)');
+
+  expect(serialize(paragraph(image({ ...imageAttrs, width: 300, height: 300 })))).toBe(
+    '![image](img.jpg){width=300 height=300}',
+  );
+
+  expect(serialize(paragraph(image({ ...imageAttrs, title: 'image title' })))).toBe(
+    '![image](img.jpg "image title")',
+  );
+});
diff --git a/spec/support/helpers/rich_text_editor_helpers.rb b/spec/support/helpers/rich_text_editor_helpers.rb
index 22cf169f6e099317e0c26321355b6d224d8a249c..8ebade8b1c27692437405c169018d974381b74d8 100644
--- a/spec/support/helpers/rich_text_editor_helpers.rb
+++ b/spec/support/helpers/rich_text_editor_helpers.rb
@@ -57,4 +57,22 @@ def click_edit_diagram_button
   def expect_drawio_editor_is_opened
     expect(page).to have_css('#drawio-frame', visible: :hidden)
   end
+
+  def drag_element(element, dx, dy)
+    page.execute_script(<<-JS, element, dx, dy)
+      function simulateDragDrop(element, dx, dy) {
+        const rect = element.getBoundingClientRect();
+        const events = ['mousedown', 'mousemove', 'mouseup'];
+        events.forEach((eventType, index) => {
+          const event = new MouseEvent(eventType, {
+            bubbles: true,
+            screenX: rect.left + (index ? dx : 0),
+            screenY: rect.top + (index ? dy : 0)
+          });
+          element.dispatchEvent(event);
+        });
+      }
+      simulateDragDrop(arguments[0], arguments[1], arguments[2]);
+    JS
+  end
 end
diff --git a/spec/support/shared_examples/features/rich_text_editor/media_shared_examples.rb b/spec/support/shared_examples/features/rich_text_editor/media_shared_examples.rb
index 9e5976d126f94a3e5d1d5aa6a6f5eb74fbdb48ef..3d51eb53dc1f38fdbacaf67477de7f028cf4bd80 100644
--- a/spec/support/shared_examples/features/rich_text_editor/media_shared_examples.rb
+++ b/spec/support/shared_examples/features/rich_text_editor/media_shared_examples.rb
@@ -24,4 +24,25 @@
       expect_media_bubble_menu_to_be_visible
     end
   end
+
+  describe 'resizing images' do
+    it 'renders correctly with an image as initial content after image is resized' do
+      click_attachment_button
+
+      switch_to_content_editor
+      display_media_bubble_menu '[data-testid="content_editor_editablebox"] img[src]', 'dk.png'
+
+      within content_editor_testid do
+        drag_element(find('[data-testid="image-resize-se"]'), -200, -200)
+      end
+
+      wait_until_hidden_field_is_updated(/width=/)
+      switch_to_markdown_editor
+
+      textarea_value = page.find('textarea').value
+
+      expect(textarea_value).to start_with('![dk.png](/uploads/')
+      expect(textarea_value).to end_with('/dk.png){width=260 height=182}')
+    end
+  end
 end