# Quick actions (/docs/build/quick-actions)



Long-press the app icon and up to four shortcuts appear — Wishlist, Cart, Account, or anything else you point at a screen. They are config-driven, so a brand changes them in Studio without an app release.

Arrived in [0.28.0](/docs/changelog/0-28-0). It ships a **native module**, so picking it up needs a [store release](/docs/ship/app-releases), not a config publish.

## What you have to build [#what-you-have-to-build]

Nothing. `useQuickActions` runs inside brewery's shell, so any app on the shell already registers the shortcuts and routes taps. There is no block to place and no hook to call.

The one requirement is the dependency:

```jsonc
// package.json
"@app-brew/quick-actions": "0.28.0"   // workspace:* inside the monorepo
```

It autolinks — `pod install` for iOS, a Gradle sync for Android. Both app templates already include it, and so does anything ejected from 0.28.0 onward. Apps ejected before September 2026 need it added by hand.

## Configuring the actions [#configuring-the-actions]

Actions live at `store.quickActions` in app config. Leave it unset and the app falls back to Wishlist, Cart and Account — each dropped if the app has no such screen, so a default never points somewhere that does not exist.

```jsonc
{
  "store": {
    "quickActions": [
      {
        "id": "track-order",
        "title": "Track order",
        "subtitle": "See where it is",
        "icon": "https://cdn.example.com/icons/truck.png",
        "iosSymbol": "shippingbox",
        "link": { "kind": "screen", "value": "orders" },
        "active": true
      }
    ]
  }
}
```

| Field       | Purpose                                                                                  |
| ----------- | ---------------------------------------------------------------------------------------- |
| `id`        | Stable identity, and what a tap reports back                                             |
| `title`     | Label on the shortcut                                                                    |
| `subtitle`  | Second line. iOS renders it; the Android module ignores it                               |
| `icon`      | **Android** — an image URL, downloaded to a bitmap on device, or a bundled drawable name |
| `iosSymbol` | **iOS** — an [SF Symbol](https://developer.apple.com/sf-symbols/) name                   |
| `link`      | Where a tap goes. Same `Link` shape as everywhere else                                   |
| `active`    | `false` hides it without deleting it. Absent means shown                                 |

The schema and defaults live in `@gauntlet/types`, shared by Studio validation and device registration so the two cannot drift.

## Four things that will bite you [#four-things-that-will-bite-you]

<Callout type="warn" title="Four actions, maximum">
  `MAX_QUICK_ACTIONS` is 4 — an iOS limit, applied on both platforms. Studio stops you adding a fifth; the app silently drops extras beyond the fourth.
</Callout>

**Icons are per-platform and not interchangeable.** iOS cannot render a runtime image on a home-screen action, so it draws `iosSymbol` and ignores `icon`. Android does the opposite. Give an action only one of the two and it renders on only one platform.

**`mailto:` links do not open the mail client.** They route to the in-app compose screen with the recipient parsed out. If the shopper is not signed in they go through sign-in first and land on compose afterwards.

**Order is already handled.** Android stacks dynamic shortcuts in reverse of add order, so the hook registers them reversed. Configured order is what shoppers see on both platforms — do not compensate for it yourself.

## When shortcuts register [#when-shortcuts-register]

The hook waits for config to load, then re-registers whenever the resolved list changes. The comparison is by value, so an equal-but-new array from a re-render does not churn the OS.

A tap that arrives before navigation is ready is held until it is, which is what makes cold-start taps land on the right screen instead of the home tab.

## Debugging [#debugging]

| Symptom                                 | Usual cause                                                                                                     |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| No shortcuts at all                     | Config has not loaded, or `isShortcutSupported()` is false — older OS, or a launcher that does not support them |
| Android has them, iOS does not          | The pod was never installed after adding the dep, or the actions have no `iosSymbol`                            |
| Tap opens the app but does not navigate | The `link` target does not resolve — check the screen id exists in config                                       |
| A stale shortcut lingers                | The hook clears all shortcuts before re-adding, so registration threw partway. Check the native log             |

## Where the code lives [#where-the-code-lives]

Native module: `native-libs/native-quick-actions` — a vendored fork of `@rn-org/react-native-shortcuts` carrying two Appbrew deltas, Android remote-URL icon loading and an iOS self-installing delegate swizzle that means apps need no `AppDelegate` changes. JS wiring: `useQuickActions` in `@gauntlet/brewery`. Schema and defaults: `@gauntlet/types`.
