diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index f1f867d64b..8cd4afaa38 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -68,6 +68,7 @@ Changelog
  * Fix: CSS build scripts now output to the correct directory paths on Windows (Vince Salvino)
  * Fix: Capture log output from style fallback to avoid noise in unit tests (Matt Westcott)
  * Fix: Switch widgets on/off states are now visually different for high-contrast mode users (Sakshi Uppoor)
+ * Fix: Nested InlinePanel usage no longer fails to save when creating two or more items (Indresh P, Rinish Sam)
 
 
 2.14.1 (12.08.2021)
diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst
index 0d82accb9c..6326bc23ac 100644
--- a/CONTRIBUTORS.rst
+++ b/CONTRIBUTORS.rst
@@ -545,6 +545,8 @@ Contributors
 * Joe Howard
 * Jochen Wersdörfer
 * Sakshi Uppoor
+* Indresh P
+* Rinish Sam
 
 Translators
 ===========
diff --git a/client/src/entrypoints/admin/__snapshots__/expanding-formset.test.js.snap b/client/src/entrypoints/admin/__snapshots__/expanding-formset.test.js.snap
new file mode 100644
index 0000000000..ed7bff7185
--- /dev/null
+++ b/client/src/entrypoints/admin/__snapshots__/expanding-formset.test.js.snap
@@ -0,0 +1,33 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`buildExpandingFormset should add an expanded item if the add button is not disabled 1`] = `
+<li
+  data-contentpath-disabled=""
+  data-inline-panel-child=""
+  id="inline_child_form_fields-2"
+>
+  
+          
+  <input
+    id="id_form_fields-2-label"
+    name="form_fields-2-label"
+    type="text"
+  />
+  
+          
+  <input
+    id="id_form_fields-2-id"
+    name="form_fields-2-id"
+    type="hidden"
+  />
+  
+          
+  <input
+    id="id_form_fields-2-DELETE"
+    name="form_fields-2-DELETE"
+    type="hidden"
+  />
+  
+        
+</li>
+`;
diff --git a/client/src/entrypoints/admin/expanding-formset.test.js b/client/src/entrypoints/admin/expanding-formset.test.js
new file mode 100644
index 0000000000..9827904179
--- /dev/null
+++ b/client/src/entrypoints/admin/expanding-formset.test.js
@@ -0,0 +1,213 @@
+/* global buildExpandingFormset */
+import $ from 'jquery';
+window.$ = $;
+
+import './expanding_formset';
+
+describe('buildExpandingFormset', () => {
+  it('exposes module as global', () => {
+    expect(window.buildExpandingFormset).toBeDefined();
+  });
+
+
+  it('should add an expanded item if the add button is not disabled', () => {
+    const prefix = 'id_form_fields';
+    document.body.innerHTML = `
+    <div class="object" id="content">
+      <input type="hidden" name="form_fields-TOTAL_FORMS" value="2" id="${prefix}-TOTAL_FORMS">
+      <ul id="${prefix}-FORMS">
+        ${[0, 1].map(id => `
+        <li id="inline_child_form_fields-${id}" data-inline-panel-child data-contentpath-disabled>
+          <input type="text" name="form_fields-${id}-label" value="Subject" id="id_form_fields-${id}-label">
+          <input type="hidden" name="form_fields-${id}-id" value="${id + 1}" id="id_form_fields-${id}-id">
+          <input type="hidden" name="form_fields-${id}-DELETE" id="id_form_fields-${id}-DELETE">
+        </li>
+        `)}
+      </ul>
+      <button class="button" id="${prefix}-ADD" type="button">
+        Add form fields
+      </button>
+      <script type="text/django-form-template" id="${prefix}-EMPTY_FORM_TEMPLATE">
+        <li id="inline_child_form_fields-__prefix__" data-inline-panel-child data-contentpath-disabled>
+          <input type="text" name="form_fields-__prefix__-label" id="id_form_fields-__prefix__-label">
+          <input type="hidden" name="form_fields-__prefix__-id" id="id_form_fields-__prefix__-id">
+          <input type="hidden" name="form_fields-__prefix__-DELETE" id="id_form_fields-__prefix__-DELETE">
+        </li>
+      </script>
+    </div>`;
+
+    const onAdd = jest.fn();
+    const onInit = jest.fn();
+
+    expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('2');
+    expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(2);
+    expect(onAdd).not.toHaveBeenCalled();
+    expect(onInit).not.toHaveBeenCalled();
+
+    // initialise expanding formset
+    buildExpandingFormset(prefix, { onInit, onAdd });
+
+    // check that init calls only were made for existing items
+    expect(onAdd).not.toHaveBeenCalled();
+    expect(onInit).toHaveBeenCalledTimes(2);
+    expect(onInit).toHaveBeenNthCalledWith(1, 0); // zero indexed
+    expect(onInit).toHaveBeenNthCalledWith(2, 1);
+
+    // click the 'add' button
+    document.getElementById(`${prefix}-ADD`).dispatchEvent(new MouseEvent('click'));
+
+    // check that template was generated and additional onInit / onAdd called
+    expect(onAdd).toHaveBeenCalledWith(2); // zero indexed
+    expect(onInit).toHaveBeenCalledTimes(3);
+    expect(onInit).toHaveBeenLastCalledWith(2);
+    expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('3');
+    expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(3);
+
+
+    // check template was created into a new form item or malformed
+    expect(document.getElementById('inline_child_form_fields-__prefix__')).toBeNull();
+    const newFormHtml = document.getElementById(`inline_child_form_fields-${2}`);
+    expect(newFormHtml.querySelectorAll('[id*="__prefix__"]')).toHaveLength(0);
+    expect(newFormHtml.querySelectorAll(`[id*="form_fields-${2}"]`)).toHaveLength(3);
+
+    expect(newFormHtml).toMatchSnapshot();
+  });
+
+  it('should not add an expanded item if the add button is disabled', () => {
+    const prefix = 'id_form_fields';
+    document.body.innerHTML = `
+    <div class="object" id="content">
+      <input type="hidden" name="form_fields-TOTAL_FORMS" value="2" id="${prefix}-TOTAL_FORMS">
+      <ul id="${prefix}-FORMS">
+        ${[0, 1].map(id => `
+        <li id="inline_child_form_fields-${id}" data-inline-panel-child data-contentpath-disabled>
+          <input type="text" name="form_fields-${id}-label" value="Subject" id="id_form_fields-${id}-label">
+          <input type="hidden" name="form_fields-${id}-id" value="${id + 1}" id="id_form_fields-${id}-id">
+          <input type="hidden" name="form_fields-${id}-DELETE" id="id_form_fields-${id}-DELETE">
+        </li>
+        `)}
+      </ul>
+      <button class="button disabled" id="${prefix}-ADD" type="button">
+        Add form fields (DISABLED)
+      </button>
+      <script type="text/django-form-template" id="${prefix}-EMPTY_FORM_TEMPLATE">
+        <li id="inline_child_form_fields-__prefix__" data-inline-panel-child data-contentpath-disabled>
+          <input type="text" name="form_fields-__prefix__-label" id="id_form_fields-__prefix__-label">
+          <input type="hidden" name="form_fields-__prefix__-id" id="id_form_fields-__prefix__-id">
+          <input type="hidden" name="form_fields-__prefix__-DELETE" id="id_form_fields-__prefix__-DELETE">
+        </li>
+      </script>
+    </div>`;
+
+    const onAdd = jest.fn();
+    const onInit = jest.fn();
+
+    expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('2');
+    expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(2);
+    expect(onAdd).not.toHaveBeenCalled();
+    expect(onInit).not.toHaveBeenCalled();
+
+    // initialise expanding formset
+    buildExpandingFormset(prefix, { onInit, onAdd });
+
+    // check that init calls only were made for existing items
+    expect(onInit).toHaveBeenCalledTimes(2);
+    expect(onInit).toHaveBeenNthCalledWith(1, 0); // zero indexed
+    expect(onInit).toHaveBeenNthCalledWith(2, 1);
+
+    // click the 'add' button
+    document.getElementById(`${prefix}-ADD`).dispatchEvent(new MouseEvent('click'));
+
+    // check that no template was generated and additional onInit / onAdd not called
+    expect(onAdd).not.toHaveBeenCalled();
+    expect(onInit).toHaveBeenCalledTimes(2);
+    expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('2');
+    expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(2);
+
+    // check template was not created into a new form item or malformed
+    expect(document.getElementById('inline_child_form_fields-__prefix__')).toBeNull();
+  });
+
+  it('should replace the __prefix__ correctly for nested formset templates', () => {
+    const prefix = 'id_venues';
+    const nestedPrefix = 'events';
+
+    const nestedTemplate = `
+<script type="text/django-form-template" id="${prefix}-__prefix__-events-EMPTY_FORM_TEMPLATE">
+  <ul class="controls">
+    <li>
+      <button type="button" class="button" id="${prefix}-__prefix__-${nestedPrefix}-__prefix__-DELETE-button">
+        Delete
+      </button>
+    </li>
+  </ul>
+  <fieldset>
+    <legend>Events</legend>
+    <input type="text" name="venues-__prefix__-events-__prefix__-name" id="id_venues-__prefix__-events-__prefix__-name">
+  </fieldset>
+<-/script>
+    `;
+
+
+    document.body.innerHTML = `
+    <div class="object" id="content">
+      <input type="hidden" name="form_fields-TOTAL_FORMS" value="2" id="${prefix}-TOTAL_FORMS">
+      <ul id="${prefix}-FORMS">
+        ${[0, 1].map(id => `
+        <li id="inline_child_form_fields-${id}" data-inline-panel-child data-contentpath-disabled>
+          <input type="text" name="form_fields-${id}-label" value="Subject" id="id_form_fields-${id}-label">
+          <input type="hidden" name="form_fields-${id}-id" value="${id + 1}" id="id_form_fields-${id}-id">
+          <input type="hidden" name="form_fields-${id}-DELETE" id="id_form_fields-${id}-DELETE">
+        </li>
+        `)}
+      </ul>
+      <button class="button" id="${prefix}-ADD" type="button">
+        Add Venue
+      </button>
+      <script type="text/django-form-template" id="${prefix}-EMPTY_FORM_TEMPLATE">
+        <li id="inline_child_form_fields-__prefix__" data-inline-panel-child data-contentpath-disabled>
+          <input type="text" name="form_fields-__prefix__-label" id="id_form_fields-__prefix__-label">
+          <input type="hidden" name="form_fields-__prefix__-id" id="id_form_fields-__prefix__-id">
+          <input type="hidden" name="form_fields-__prefix__-DELETE" id="id_form_fields-__prefix__-DELETE">
+        </li>
+        ${nestedTemplate}
+      </script>
+    </div>`;
+
+
+    const onAdd = jest.fn();
+    const onInit = jest.fn();
+
+    expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('2');
+    expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(2);
+    expect(onAdd).not.toHaveBeenCalled();
+    expect(onInit).not.toHaveBeenCalled();
+
+    // initialise expanding formset
+    buildExpandingFormset(prefix, { onInit, onAdd });
+
+    // check that init calls only were made for existing items
+    expect(onAdd).not.toHaveBeenCalled();
+    expect(onInit).toHaveBeenCalledTimes(2);
+    expect(onInit).toHaveBeenNthCalledWith(1, 0); // zero indexed
+    expect(onInit).toHaveBeenNthCalledWith(2, 1);
+
+    // click the 'add' button
+    document.getElementById(`${prefix}-ADD`).dispatchEvent(new MouseEvent('click'));
+
+    // check that template was generated and additional onInit / onAdd called
+    expect(onAdd).toHaveBeenCalledWith(2); // zero indexed
+    expect(onInit).toHaveBeenCalledTimes(3);
+    expect(onInit).toHaveBeenLastCalledWith(2);
+    expect(document.getElementById(`${prefix}-TOTAL_FORMS`).value).toEqual('3');
+    expect(document.querySelectorAll('[data-inline-panel-child]')).toHaveLength(3);
+
+    // check the nested template was created with the correct prefixes
+    const newTemplate = document.getElementById(`${prefix}-2-events-EMPTY_FORM_TEMPLATE`);
+    expect(newTemplate).toBeTruthy();
+    expect(newTemplate.textContent).toContain('id="id_venues-2-events-__prefix__-DELETE-button"');
+    expect(newTemplate.textContent).toContain(
+      '<input type="text" name="venues-2-events-__prefix__-name" id="id_venues-2-events-__prefix__-name">'
+    );
+  });
+});
diff --git a/client/src/entrypoints/admin/expanding_formset.js b/client/src/entrypoints/admin/expanding_formset.js
index d9fcddcbec..e68cc1be64 100644
--- a/client/src/entrypoints/admin/expanding_formset.js
+++ b/client/src/entrypoints/admin/expanding_formset.js
@@ -23,7 +23,7 @@ function buildExpandingFormset(prefix, opts = {}) {
   addButton.on('click', () => {
     if (addButton.hasClass('disabled')) return false;
     const newFormHtml = emptyFormTemplate
-      .replace(/__prefix__/g, formCount)
+      .replace(/__prefix__(.*?['"])/g, formCount + '$1')
       .replace(/<-(-*)\/script>/g, '<$1/script>');
     formContainer.append(newFormHtml);
     if (opts.onAdd) opts.onAdd(formCount);
diff --git a/client/src/entrypoints/admin/page-editor.js b/client/src/entrypoints/admin/page-editor.js
index 0c2c3c5864..50c8d29847 100644
--- a/client/src/entrypoints/admin/page-editor.js
+++ b/client/src/entrypoints/admin/page-editor.js
@@ -165,7 +165,7 @@ function InlinePanel(opts) {  // lgtm[js/unused-local-variable]
   // eslint-disable-next-line no-undef
   buildExpandingFormset(opts.formsetPrefix, {
     onAdd(formCount) {
-      const newChildPrefix = opts.emptyChildFormPrefix.replace(/__prefix__/g, formCount);
+      const newChildPrefix = opts.emptyChildFormPrefix.replace(/__prefix__(.*?['"])/g, formCount + '$1');
       self.initChildControls(newChildPrefix);
       if (opts.canOrder) {
         /* NB form hidden inputs use 0-based index and only increment formCount *after* this function is run.
diff --git a/docs/releases/2.15.rst b/docs/releases/2.15.rst
index 85fe92f4e0..59d25281c5 100644
--- a/docs/releases/2.15.rst
+++ b/docs/releases/2.15.rst
@@ -88,6 +88,7 @@ Bug fixes
  * CSS build scripts now output to the correct directory paths on Windows (Vince Salvino)
  * Capture log output from style fallback to avoid noise in unit tests (Matt Westcott)
  * Switch widgets on/off states are now visually different for high-contrast mode users (Sakshi Uppoor)
+ * Nested InlinePanel usage no longer fails to save when creating two or more items (Indresh P, Rinish Sam)
 
 Upgrade considerations
 ======================