How to add form and fields in Shopify AppBridge Modal

Topic summary

A developer is struggling to add form fields to a Shopify AppBridge Modal in their Rails application. The initial issue involves creating a modal with proper form elements that can be submitted.

Key Challenge:

  • AppBridge Modal renders in Shopify Admin while the Rails app runs in an iframe (embedded)
  • Cannot directly access or manipulate the modal’s DOM to inject HTML form content
  • The message property only accepts text, not HTML markup

Attempted Solutions:

  • Using innerHTML on content targets - renders as text instead of HTML
  • Trying to append HTML to modal sections via document.querySelector('.Polaris-Modal-Section') - fails due to iframe isolation

Context:

  • Shopify deprecated polaris_modal, forcing developers to use AppBridge Modals
  • AppBridge Modals are intentionally limited in customization options
  • The discussion references Shopify’s documentation explaining why AppBridge modals replaced Polaris modals

Current Status:
The issue remains unresolved. The architectural constraint of the modal existing in Shopify Admin while the app runs in an iframe prevents direct DOM manipulation, leaving the developer without a clear path to embed custom form fields.

Summarized with AI on November 5. AI used: claude-sonnet-4-5-20250929.

I’m having issue on how to correctly add form and text fields to Shopify AppBridge Modal. Here is how I create a modal:

let Button = window.AppBridge.actions.Button;
let okButton = Button.create(window.app, {label: "Submit"});

okButton.subscribe(Button.Action.CLICK, () => {
  reportModal.dispatch(Modal.Action.CLOSE)
});

let cancelButton = Button.create(window.app, {label: "Cancel"});
cancelButton.subscribe(Button.Action.CLICK, () => {
  reportModal.dispatch(Modal.Action.CLOSE);
});

const modalOptions = {
  title: "Title",
  message: "Message body",
  footer: {
    buttons: {
      primary: okButton,
      secondary: [cancelButton],
    },
  },
  size: "medium"
};

let Modal = window.AppBridge.actions.Modal;    
let reportModal = Modal.create(window.app, modalOptions);
reportModal.dispatch(Modal.Action.OPEN);

Applying this in Rails application with Stimulus JS.

Hi @aldrien ,

Here’s how you can approach it:

import { Controller } from "stimulus";

export default class extends Controller {
  static targets = ["content"];

  connect() {
    this.createModal();
  }

  createModal() {
    let Button = window.AppBridge.actions.Button;
    let Modal = window.AppBridge.actions.Modal;

    let okButton = Button.create(window.app, { label: "Submit" });
    let cancelButton = Button.create(window.app, { label: "Cancel" });

    okButton.subscribe(Button.Action.CLICK, () => {
      this.submitForm();
    });

    cancelButton.subscribe(Button.Action.CLICK, () => {
      this.closeModal();
    });

    const modalOptions = {
      title: "Form Modal",
      message: this.contentTarget.innerHTML, // Use innerHTML to inject form
      footer: {
        buttons: {
          primary: okButton,
          secondary: [cancelButton],
        },
      },
      size: "medium",
    };

    this.reportModal = Modal.create(window.app, modalOptions);
    this.reportModal.dispatch(Modal.Action.OPEN);
  }

  closeModal() {
    this.reportModal.dispatch(Modal.Action.CLOSE);
  }

  submitForm() {
    const form = document.getElementById('report-form');
    const formData = new FormData(form);

    // Handle form submission, e.g., send via Ajax or Rails controller
    console.log(Object.fromEntries(formData.entries()));

    this.closeModal();
  }
}

Hi @rajweb - thanks for your reply. I tried your suggestion - but this `this.contentTarget.innerHTML renders as text not HTML. Am i missing something?

Hi @aldrien ,

Got it!

createModal() {
    let Button = window.AppBridge.actions.Button;
    let Modal = window.AppBridge.actions.Modal;

    let okButton = Button.create(window.app, { label: "Submit" });
    let cancelButton = Button.create(window.app, { label: "Cancel" });

    okButton.subscribe(Button.Action.CLICK, () => {
        this.submitForm();
    });

    cancelButton.subscribe(Button.Action.CLICK, () => {
        this.closeModal();
    });

    // Create a modal without using the 'message' option to inject HTML directly later
    const modalOptions = {
        title: "Form Modal",
        footer: {
            buttons: {
                primary: okButton,
                secondary: [cancelButton],
            },
        },
        size: "medium",
    };

    this.reportModal = Modal.create(window.app, modalOptions);

    // Get the modal content container after creating the modal
    const modalContent = document.createElement('div');
    modalContent.innerHTML = this.contentTarget.innerHTML; // Inject HTML form
    document.querySelector('.Polaris-Modal-Section').appendChild(modalContent); // Append HTML to modal

    this.reportModal.dispatch(Modal.Action.OPEN);
}

This should resolve the issue of the content rendering as text instead of HTML. Let me know how it goes

Hi @rajweb - thanks for rechecking, however there is issue because the AppBridge Modal is in Shopify Admin while my Rails app is being iframed (embedded in Shopify), thus we cannot manually select the modal body and append/insert the target form :disappointed_face:
Shopify made the polaris_modal deprecated (which I’m currently using)
https://shopify.dev/docs/api/app-bridge/using-modals-in-your-app#why-app-bridge-modals-instead-of-polaris-modals
But make it hard to customize their modal :face_with_crossed_out_eyes::dizzy: