Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt

Use this file to discover all available pages before exploring further.

Admin Action Extensions

Admin actions add a button to an admin resource page (e.g. the order detail page). When the merchant clicks it, your app opens in a modal iframe with the current resource id in the URL — perfect for “Refund this order”, “Issue gift card from order”, “Resync inventory”, or any other one-shot operation that needs your app’s UI but does not belong as an always-visible block.
If you need a panel that is always visible on a resource page, use an Admin Block instead. Use an Admin Action when the merchant only needs the UI on demand.

How Admin Actions Work

┌──────────────────────────────────────────────────────────────┐
│  LaunchMyStore Admin · Order #1042                           │
├──────────────────────────────────────────────────────────────┤
│  [Refund]  [Send invoice]  [Refund helper ← your action]     │
│                                                              │
│  Order details ...                                           │
└──────────────────────────────────────────────────────────────┘
                            ↓ click
┌──────────────────────────────────────────────────────────────┐
│  Modal · Refund helper                                       │
│  ┌────────────────────────────────────────────────────────┐  │
│  │ <iframe src="https://app.example.com/refund?orderId..>│  │
│  │  [Your UI: pick line items, partial amount, reason]   │  │
│  │                                                       │  │
│  │  [Cancel]                              [Issue refund] │  │
│  └────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────┘
The host renders the button in the resource page’s action toolbar. On click, it opens a modal with an iframe pointing at your appUrl plus query parameters describing the resource. Your app drives the UX inside the modal and uses App Bridge to close it, show a toast, or trigger a host redirect when work is done.

Available Targets

Admin Actions are wired at specific resource-detail action slots. The initial release ships with one target — more will be added in subsequent releases.
TargetWhere it renders
admin.order-details.action.renderAction toolbar on the order detail page
Additional targets for product, customer, draft order, and collection detail pages are planned next. Track the changelog for new admin.{resource}-details.action.render targets as they ship.

Extension Manifest

Declare admin actions in your app.json under extensions.adminActions, or send them inline to the install pipeline. Each entry corresponds to one button.

Inline in app.json

{
  "handle": "refund-helper",
  "name": "Refund Helper",
  "version": "1.0.0",
  "extensions": {
    "adminActions": [
      {
        "handle": "issue-partial-refund",
        "title": "Refund helper",
        "target": "admin.order-details.action.render",
        "appUrl": "https://refund-helper.example.com/admin/refund",
        "icon": "https://refund-helper.example.com/icon.svg"
      }
    ]
  }
}

Schema file form

When installed through the install pipeline, each admin action is persisted as a single schema file at:
public/extensions/{domainSlug}/{appHandle}/admin-actions/{handle}.schema.json
{
  "target": "admin.order-details.action.render",
  "title": "Refund helper",
  "appUrl": "https://refund-helper.example.com/admin/refund",
  "icon": "https://refund-helper.example.com/icon.svg"
}

Fields

FieldRequiredDescription
handleyesURL-safe identifier — unique per app. Also used as the schema filename.
targetyesAction slot the button is rendered into. See Available targets.
titleyesButton label shown to the merchant.
appUrlyesHTTPS URL loaded into the modal iframe. Receives resource context as query params.
iconnoURL of an SVG/PNG icon shown next to the button label.

Installing Admin Actions

Send the action manifests when your app completes OAuth, the same way you upload theme blocks or snippets. The install endpoint accepts an adminActions array on the extensions payload:
curl -X POST https://store.launchmystore.io/api/apps/install-extensions \
  -H "Content-Type: application/json" \
  -d '{
    "domainSlug": "acme-store",
    "appHandle": "refund-helper",
    "extensions": {
      "adminActions": [
        {
          "handle": "issue-partial-refund",
          "title": "Refund helper",
          "target": "admin.order-details.action.render",
          "appUrl": "https://refund-helper.example.com/admin/refund",
          "icon": "https://refund-helper.example.com/icon.svg"
        }
      ]
    }
  }'
Each entry produces a {handle}.schema.json file under the app’s admin-actions/ directory. The host’s admin extension API picks it up on the next request — /api/apps/admin-extensions?target=... returns entries with type: "admin_action" for actions, as opposed to type: "admin_block" for blocks.

Iframe Context

When the merchant clicks the action button, your appUrl is loaded into the modal iframe with these query parameters appended (see AdminActionModal.jsxbuildActionIframeSrc):
ParameterAlways setDescription
targetyesThe extension’s manifest target.
domainSlugyesThe merchant’s domain slug.
hostyesbtoa(window.location.origin) — base64-encoded admin origin.
resourceIdoptionalThe id of the resource currently being viewed (e.g. ord_8a7f...).
resourceTypeoptionalThe type of the resource (e.g. order).
const params = new URLSearchParams(location.search);
const orderId = params.get('resourceId');
const resourceType = params.get('resourceType');
const domainSlug = params.get('domainSlug');
Unlike admin blocks, the action modal does not append an extensionId query param — the modal’s resize listener filters on the manifest id it already holds in its React state, so you don’t need to echo one. Resize clamp for the modal is Math.min(height, 800), with a 400px default.
// Auto-resize the action modal
window.parent.postMessage({
  type: 'APP_BRIDGE_RESIZE',
  extensionId: '<your-manifest-handle>',  // matched against host's state
  height: document.body.scrollHeight,
}, '*');

Communicating with the Host

Admin actions use the standard App Bridge SDK. app.dispatch(action, payload) and app.dispatchAndWait(action, payload) take an action string plus payload — not a {type, payload} envelope.

Close the modal when done

After your action completes, dismiss the modal so the merchant returns to the resource page:
import { createApp } from '@launchmystore/app-bridge';

const app = createApp({
  apiKey: process.env.NEXT_PUBLIC_APP_CLIENT_ID,
  host: new URLSearchParams(location.search).get('host'),
});

// Refund completed → close the modal
app.dispatch('MODAL_CLOSE');

Show a toast on success

app.dispatch('TOAST_SHOW', {
  message: 'Refund issued for $24.00',
  duration: 3000,
  type: 'success',          // 'success' | 'error' | 'warning' | 'info'
});
app.dispatch('MODAL_CLOSE');

Block close while a request is in flight

import { LeaveConfirmation } from '@launchmystore/app-bridge';

const leave = LeaveConfirmation.create(app, {
  message: 'Refund in progress — leave anyway?',
});

leave.enable();
await issueRefund();
leave.disable();

app.dispatch('MODAL_CLOSE');
The LeaveConfirmation helper also dispatches the underlying LEAVE_CONFIRMATION_ENABLE / LEAVE_CONFIRMATION_DISABLE actions for you, and wires the browser’s beforeunload event so closing the tab triggers the confirmation too.

Complete Example

A minimal refund helper that loads the order, takes a partial amount, and issues the refund via your app’s backend before closing the modal.
import { createApp } from '@launchmystore/app-bridge';
import { useEffect, useState } from 'react';

export default function RefundHelper() {
  const [order, setOrder] = useState(null);
  const [amount, setAmount] = useState('');
  const [submitting, setSubmitting] = useState(false);

  const app = createApp({
    apiKey: process.env.NEXT_PUBLIC_APP_CLIENT_ID,
    host: new URLSearchParams(location.search).get('host'),
  });

  useEffect(() => {
    const params = new URLSearchParams(location.search);
    const orderId = params.get('resourceId');
    if (orderId) loadOrder(orderId);
  }, []);

  const loadOrder = async (orderId) => {
    const token = await app.getSessionToken();
    const response = await fetch(`/api/orders/${orderId}`, {
      headers: { Authorization: `Bearer ${token}` },
    });
    setOrder(await response.json());
  };

  const submit = async () => {
    setSubmitting(true);
    try {
      const token = await app.getSessionToken();
      await fetch(`/api/orders/${order.id}/refunds`, {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ amount: parseFloat(amount) }),
      });
      app.dispatch('TOAST_SHOW', {
        message: `Refunded $${amount}`,
        type: 'success',
      });
      app.dispatch('MODAL_CLOSE');
    } catch (err) {
      app.dispatch('TOAST_SHOW', {
        message: 'Refund failed — please try again',
        type: 'error',
      });
    } finally {
      setSubmitting(false);
    }
  };

  if (!order) return <p>Loading order...</p>;

  return (
    <div className="refund-helper">
      <h3>Refund order {order.name}</h3>
      <p>Total paid: ${order.total}</p>

      <label>
        Refund amount
        <input
          type="number"
          step="0.01"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
        />
      </label>

      <div className="actions">
        <button onClick={() => app.dispatch('MODAL_CLOSE')}>
          Cancel
        </button>
        <button onClick={submit} disabled={submitting || !amount}>
          {submitting ? 'Refunding...' : 'Issue refund'}
        </button>
      </div>
    </div>
  );
}

Admin Actions vs Admin Blocks

Admin ActionAdmin Block
TriggerClick a buttonAlways rendered with the page
LayoutModal iframe on top of the pageInline iframe in the page layout
Use casesRefund, resync, issue gift card, one-shot opsReviews summary, warehouse status, KPIs
type fieldadmin_actionadmin_block
File locationadmin-actions/{handle}.schema.jsonapp.jsonextensions.adminExtensions[]
Discovery API/api/apps/admin-extensions?type=admin_action/api/apps/admin-extensions?type=admin_block

See Also