Formats & platforms

The one page worth reading before you write any code. Nearly every integration bug traces back to it, and the failure mode is a blank canvas with nothing in the console.

There is no format both platforms read

  • SceneKit and AR Quick Look read USDZ. They cannot open glTF.
  • Filament and Scene Viewer read GLB. They cannot open USDZ.

So every model is stored as both. The GLB is produced when the restaurant uploads; the USDZ is derived from it by a background worker and arrives a minute or two later.

iOS

Inline previewSceneKit USDZ
ARQuick Look USDZ
No USDZ yetPoster image
Camera permissionNot required

Android

Inline previewFilament GLB
ARScene Viewer GLB
No USDZ yetUnaffected
Camera permissionNot required

Web

Inline previewWebGL GLB
AR on AndroidWebXR / Scene Viewer GLB
AR on iOSQuick Look USDZ
Desktop3D only, no AR

The consequence people miss

You never have to reason about this per call site. resolvePresentation() makes the decision and the components call it for you. The rule is simply: never reach for model.glbUrl yourself on iOS.

what the SDK does for you
const { inline, ar } = resolvePresentation({
  platform: "ios",
  model: item.model,
});

// inline.kind is "usdz" when the conversion finished,
// and "poster" when it has not — never "glb" on iOS.
if (inline.kind === "poster") showPhoto(inline.url);

Telling “not yet” from “never”

ar.blockedOnConversion is true only while the USDZ is still being made — a state that resolves by itself. Every other unsupported reason (a desktop browser, a handset without ARCore, a failed conversion) is permanent for that session, and showing a hopeful message there is a lie the interface repeats forever.

usdzStatus carries the underlying detail: pending, processing, ready or failed.