Skip to content
← Back to blog
Expo native builds

How to create Android and iOS app icons in Expo without Android cropping them

9 min read

Written by: Jonathan Reis on

An app icon can look correct in an editor and on iOS, then lose part of its artwork in an Android launcher. The fix is to treat the Android adaptive foreground as a separate asset, not as a copy of the iOS icon.

OpenGraph preview image for this article. How to create Android and iOS app icons in Expo without Android cropping them

An app icon is often shipped as one 1024-pixel PNG because that is the most convenient file to export. It works well enough for iOS, store listings, and a browser favicon. It is the wrong mental model for Android adaptive icons.

Android launchers are allowed to apply different masks: circles, rounded squares, squircles, and manufacturer-specific shapes. If an Expo app supplies the full opaque icon as android.adaptiveIcon.foregroundImage, the launcher treats every pixel as foreground and masks it. Artwork placed close to the canvas edge gets cut off. The same image can look good on iOS because iOS and Android are not consuming the same kind of asset.

The reliable pattern is simple:

  1. Keep one editable vector source.
  2. Export a full, opaque icon for iOS.
  3. Export a separate, transparent foreground for Android.
  4. Let Android draw the adaptive icon background.
  5. Regenerate native resources after changing the configuration or assets.

This article shows how to make that split reproducible in an Expo project instead of relying on a designer or developer remembering it during each icon refresh.

Start with one source, not one output file

The source of truth should be the artwork someone edits, usually an SVG. The files consumed by each platform are derivatives with different contracts.

assets/app-icon/
├── source/
│   └── icon.svg
├── ios/
│   └── icon.png
├── android/
│   └── adaptive-foreground.png
└── web/
    └── favicon.png

This is not an argument for making three independently designed icons. The same visual identity can still drive every output. The separation makes platform behavior visible in the repository: ios/icon.png is a complete icon, while android/adaptive-foreground.png is explicitly a layer.

That distinction matters during maintenance. A file named adaptive-icon.png next to icon.png is easy to replace with a copy operation. A file in an android/ directory named adaptive-foreground.png communicates that it has a different alpha and safe-area requirement.

For Expo, the top-level icon is normally a 1024 by 1024 PNG. Expo documents icon as the general app-icon input and generates the platform sizes from it. Its configuration reference also documents android.adaptiveIcon.foregroundImage and backgroundColor as separate adaptive-icon inputs. See the Expo app configuration reference for the current fields.

Treat the Android foreground as a layer

An Android adaptive icon is composed from at least two layers:

adaptive icon
├── background: a color or background image
└── foreground: transparent PNG with the recognizable artwork

The launcher applies its mask to the composed result. It may also animate or parallax the layers on supported devices. The foreground therefore needs empty space around the artwork. Its canvas is not a promise that all 1024 pixels will remain visible.

Android’s design guidance uses a 108 by 108 logical-layer canvas, with a central 66 by 66 safe zone for the content that must survive every mask. The practical equivalent for a 1024 by 1024 export is a centered 676 by 676 artwork area. The remaining pixels are transparent padding, not a background color.

Use that rule for content that must not be clipped: a logo mark, a pencil tip, a board corner, a shadow that defines the shape, or any text if the icon unfortunately includes text. A diffuse shadow can fade beyond the safe area, but a hard edge should not depend on it.

The official Android adaptive icon guidance explains why this extra space exists and why different launcher masks make edge-to-edge foreground art unsafe.

Configure Expo with separate iOS and Android inputs

The Expo configuration should express the same split as the directory tree:

{
  "expo": {
    "icon": "./assets/app-icon/ios/icon.png",
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/app-icon/android/adaptive-foreground.png",
        "backgroundColor": "#F6F2EA"
      }
    },
    "web": {
      "favicon": "./assets/app-icon/web/favicon.png"
    }
  }
}

The iOS file is intentionally opaque. iOS receives the full icon and applies its own rounded treatment when presenting it.

The Android foreground is intentionally transparent. Do not paint the app background into this PNG. Setting the background in both places makes the image look like a flat square foreground layer and defeats the composition model. It also makes it harder to see whether the safe padding is actually present.

The common failed setup

This configuration looks plausible but is the source of many cropped Android icons:

{
  "android": {
    "adaptiveIcon": {
      "foregroundImage": "./assets/app-icon/ios/icon.png",
      "backgroundColor": "#F6F2EA"
    }
  }
}

The backgroundColor is correct, but the foreground file is not. A full opaque iOS icon contains both the background and the artwork at full size. Android masks that complete square as the foreground. The visible result depends on the launcher, which is why it can look acceptable on one device and clipped on another.

Generate both derivatives from the SVG

Manual image editing is fine while exploring visual directions. It is fragile once the icon is approved. Replace the handoff with one generator that always applies the platform rules.

The essential operations are:

render source/icon.svg at 1024x1024
compose the render over the iOS background -> ios/icon.png
scale the transparent render to 676x676 and center it -> android/adaptive-foreground.png
scale the iOS output -> web/favicon.png
run Expo prebuild to refresh native resources

The iOS composition needs an explicit background layer. Rendering an SVG that happens to contain a background shape is not always enough: the shape may stop short of the corners, and the PNG can retain alpha there. Compositing the raster over the chosen color guarantees an opaque output.

The Android operation starts from the transparent render, not from the already composed iOS PNG. In image-processing terms, it is:

transparent canvas 1024x1024
  + centered transparent source render scaled to 676x676
  = adaptive foreground

If the source SVG itself has a full opaque background, remove or isolate that background before producing the Android foreground. Otherwise scaling the source simply produces a smaller opaque square, which is the same mistake with more padding around it.

Add a check that catches the regression

The useful validation is not “the generator exited successfully.” A generator can create a valid PNG that is still the wrong type of PNG for Android.

Check at least these invariants:

iOS icon:
  - 1024x1024
  - alpha is opaque at every pixel

Android foreground:
  - 1024x1024
  - alpha exists
  - alpha is zero at all four corners
  - essential artwork is no larger than the centered 676x676 safe area

Web favicon:
  - matches the configured dimensions

The four-corner alpha check is deliberately boring. It prevents the most damaging regression: copying the opaque iOS image into the Android foreground path. It does not prove a designer kept every important detail in the safe zone, but it turns an easy-to-miss visual contract into a failed build step.

For a shell-based pipeline, a single-pixel alpha extraction at coordinates (0,0), (1023,0), (0,1023), and (1023,1023) is enough to test the corners. A Node generator can run the equivalent check through an image library. Keep the test in the generator rather than in a document that someone has to remember to read.

Refresh generated native resources

app.json does not modify an app already installed on a phone. The adaptive icon is native build input. After changing the asset or its path, regenerate the native projects and build a fresh binary:

pnpm <your-app>:icon:generate
pnpm --filter <your-app> exec expo prebuild --platform all --no-install

The first command is a project script in this example; it renders the assets and performs the alpha check. The second command is shown separately to make the native boundary clear. A production script can run both in sequence.

Then install a new Android build and inspect the launcher, not just an image preview. The launcher is the system component that chooses the mask. Test at least one physical device or emulator configuration that uses an adaptive icon. If the icon is used in a native splash screen, rebuild that path as well. The Expo splash-screen migration guide covers the related rule: configuration that writes native resources needs a new native artifact before it can be judged.

A repeatable icon checklist

  • Edit only the source SVG.
  • Generate an opaque iOS icon from that source.
  • Generate an Android foreground separately from a transparent render.
  • Keep essential Android artwork within the centered safe area.
  • Configure Android’s background as backgroundColor or a separate background image.
  • Fail generation when the Android foreground corners are opaque.
  • Run prebuild after changing assets or configuration paths.
  • Install a new Android binary and inspect the launcher mask.
  • Keep the site favicon and web favicon as named derivatives, not alternate sources of truth.

The important change is not a particular export command. It is modeling the icon as a source plus platform-specific derivatives. iOS needs a complete icon. Android needs a composition. Once the file layout, Expo configuration, generator, and validation all say that explicitly, changing the artwork stops reintroducing the same cropped launcher icon.

Related postWhen an Expo upgrade turns your splash screen into a white screen8 min readWritten by: Jonathan Reis on