Edit in JSFiddle

const editor = grapesjs.init({
  container: '#gjs',
  height: '100%',
  storageManager: false,
  modal: { custom: true },
  components: `
    <div style="padding: 25px">
      Double click on image to open custom modal.
    </div>
    <img/>
  `,
});

// Custom modal
let bstModal, modalTitle, modalBody;
editor.on('modal', (props) => {
  if (!bstModal) {
    const modalEl = document.getElementById('gjsModal');
    modalTitle = document.querySelector('.modal-title');
    modalBody = document.querySelector('.modal-body');
    bstModal = new bootstrap.Modal(modalEl);
    // update GrapesJS modal state when Bootstrap modal is closed
    modalEl.addEventListener('hide.bs.modal', props.close);
  }

  if (props.open) {
    // clear the current elements
    modalTitle.innerHTML = '';
    modalBody.innerHTML = '';
    // Add new elements
    modalTitle.appendChild(props.title);
    modalBody.appendChild(props.content);
    bstModal.show();
  } else {
    bstModal.hide();
  }
});
<div id="gjs"></div>

<!-- Bootstrap Modal -->
<div class="modal fade" id="gjsModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Title</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
        </button>
      </div>
      <div class="modal-body">Content</div>
    </div>
  </div>
</div>
body, html {
  margin: 0;
  height: 100%;
}