> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackshift.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Create and embed media galleries

> Arrange published images, video, spins and models with locale, alternate text, approved fallbacks and stable published revisions.

## Goal

Publish a controlled media sequence and embed it without silently following draft edits or stale delivery URLs.

## Prerequisites

* Published media with valid rights and compatible channel/policy in one Assets space.
* curate authority to author galleries and publish authority for publication transitions.

## Workflow

<Steps>
  <Step>
    Create a gallery from published media and order its entries.
  </Step>

  <Step>
    Set role, locale, alternate text and optional approved image fallback.
  </Step>

  <Step>
    Preview the draft, then explicitly publish it.
  </Step>

  <Step>
    Resolve and embed the published gallery from your application backend.
  </Step>
</Steps>

## Create the gallery in the dashboard

Open Assets → Galleries → New gallery. Enter a gallery name and choose Add media. Select a published publication for each entry; use Load more published media if the selection extends beyond the first page.

Use Move up/Move down to order entries. Enter meaningful alternate text and a locale, then choose Hero, Detail or Media. Optionally select an approved image publication as the fallback. Save draft preserves changes without modifying the currently published snapshot.

Use Preview draft to inspect the sequence, then Publish draft when ready. The list shows both Draft and Published revision values so you can see whether the live snapshot differs from your edits.

## Entry identities and limits

A gallery has at most 50 entries. Its name is required and at most 160 bytes; each entry’s alternate text is required and at most 1,000 bytes. An entry records publication\_id, role, locale and optional fallback\_publication\_id. The service pins source/version/publication revision and fallback revision when saving. Locale values use a 2–8 letter primary tag with optional hyphen-separated 1–8 letter/digit subtags, for example en or en-GB.

A fallback must be an approved image publication with current authorization. It is not an arbitrary URL or permission bypass. Choose a fallback that describes the same product/content and retain its own attribution/rights.

## Draft and live behavior

Saving a draft never changes the published gallery. Publishing snapshots the validated entries. Later source, metadata or publication changes do not silently substitute content into the old snapshot. If a pinned publication becomes unavailable, that entry is unavailable or uses its separately authorized fallback.

Publish a new gallery revision deliberately after reviewing changed references. Replacement or withdrawal invalidates older gallery-bound delivery credentials. An embed that pins a revision must explicitly adopt the new published revision; it must not invent an implicit latest-version fallback.

## Create and publish with the SDK

```ts theme={null}
const gallery = await sdk.assets.dam.createGallery({
  name: 'Chair product gallery',
  entries: [{
    publication_id: approvedModelPublicationId,
    fallback_publication_id: approvedImagePublicationId,
    role: 'hero', locale: 'en', alt: 'Oak chair viewed from the front',
  }],
})
// Review the draft delivery before the authorized publishing action.
const preview = await sdk.assets.dam.resolveGallery(gallery.id, true)
const published = await sdk.assets.dam.transitionGallery(gallery.id, gallery.revision, 'publish')
// Later edits use updateGallery(id, currentRevision, { name, entries }).
```

## Backend resolver contract

Authenticate viewers when your product/content requires it. Load the permitted gallery ID from trusted application records and call resolveGallery(id). Do not expose an unrestricted endpoint that resolves any browser-supplied gallery or publication using your service key.

Return AssetGalleryDelivery directly: gallery\_id, name, revision and entries. Each entry includes its pinned reference and delivery, fallback\_delivery or unavailable flag. Responses and short-lived media grants must remain private/no-store.

For AR, check that the requested publication is part of this product’s saved gallery, then call resolveGalleryAR(galleryId, publicationId). Gallery-bound authorization must be retained; resolving an arbitrary publication separately would lose that host boundary.

```ts theme={null}
// Call only after authorizing the viewer and loading the product's saved IDs.
async function galleryResponse(savedGalleryId: string) {
  const delivery = await sdk.assets.dam.resolveGallery(savedGalleryId)
  return Response.json(delivery, { headers: { 'Cache-Control': 'private, no-store' } })
}

async function galleryARResponse(savedGalleryId: string, allowedPublicationId: string) {
  // The caller must verify that this publication belongs to the saved product gallery.
  const ar = await sdk.assets.dam.resolveGalleryAR(savedGalleryId, allowedPublicationId)
  return Response.json(ar, { headers: { 'Cache-Control': 'private, no-store' } })
}
```

## React embed

```tsx theme={null}
import { StackshiftAssetsGallery } from '@stackshift-cloud/assets-gallery/react'

async function readResponse(response: Response) {
  if (!response.ok) throw new Error('Product media is unavailable')
  return response.json()
}
export function ProductGallery() {
  return <StackshiftAssetsGallery
    getGallery={signal => fetch('/api/product-gallery', { signal, cache: 'no-store' }).then(readResponse)}
    getAR={(entry, signal) => fetch('/api/product-gallery/ar', {
      method: 'POST', signal, cache: 'no-store',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ publication_id: entry.reference.publication_id }),
    }).then(readResponse)}
  />
}
```

## JavaScript, lifecycle and styling

Plain JavaScript exports createAssetsGallery(element, options); use the same getGallery/getAR callback contract and call destroy() on removal. Mount into a fresh host element. Build and install the gallery package, including its sibling player dependency, before importing it into your application.

The viewer renders isolated styles. Set --gallery-background, --gallery-foreground, --gallery-border, --gallery-muted and --gallery-accent on the host. Callbacks should honor the abort signal so navigation can cancel old requests.

## What visitors see

* Only the active item mounts. Images use alternate text and can retry failed loads. The viewer refreshes expiring delivery grants and preserves stable selection where possible.
* Native packaged video uses the shared Assets player with approved caption tracks. Rendered turntable MP4 uses the gallery’s video presentation; a turntable is not an interactive model.
* Interactive spins use the complete ordered 24-frame image set with pointer/keyboard navigation. Do not replace them with one static image or an unrelated animated file.
* A model starts with its supplied poster. View in 3D triggers the model download and viewer import. Orbit, zoom, reset and supported animation controls are explicit; there is no automatic rotation or animation.
* View in your space requests fresh AR authorization. Quick Look requires the supplied validated USDZ; other device paths depend on browser support and the GLB. AR is not guaranteed by a visible 3D preview.

## Withdraw and troubleshoot

Select Withdraw in Galleries to stop subsequent gallery delivery. Keep the stable gallery record in your CMS so you can show a clear unavailable state rather than leaking an original URL. Files a visitor already downloaded cannot be remotely erased.

If Publish draft fails, inspect each entry’s publication status, revision, rights and fallback. On a revision conflict, reload and compare order/entries before saving. If an embed cannot load, check the host’s saved gallery ID, backend response shape, no-store behavior and current grant—not only whether the browser can download one image.

## Go, Python and PHP: create a model gallery, preview and publish

The model and fallback IDs must refer to published publications in this space, with active rights. Creation captures their revisions. The fallback should be a product image. The functions return the saved draft and its preview, allowing the editor to inspect the result before publishing.

After explicit publication approval in your application, call TransitionGallery(ctx, draft.ID, draft.Revision, "publish"), transition\_gallery(draft\["id"], draft\["revision"], "publish"), or transitionGallery($draft["id"], $draft\["revision"], "publish"). Save the gallery ID on your product; the backend resolver reads that saved ID for each visitor request.

Later changes send the complete name and entries array with the latest draft revision. Reordering means reordering that array; removal means omitting the entry. Preview the updated draft and publish it to replace the live revision. Use withdraw with the current revision to stop gallery delivery.

<CodeGroup>
  ```go Go theme={null}
  func createGallery(ctx context.Context, dam *stackshift.AssetDAMClient, modelPublicationID, imagePublicationID string) (*stackshift.AssetGallery, map[string]any, error) {
  	draft, err := dam.CreateGallery(ctx, map[string]any{
  		"name": "Chair product gallery", "entries": []map[string]any{{
  			"publication_id": modelPublicationID, "fallback_publication_id": imagePublicationID,
  			"role": "hero", "locale": "en", "alt": "Oak chair viewed from the front",
  		}},
  	})
  	if err != nil {
  		return nil, nil, err
  	}
  	preview, err := dam.PreviewGallery(ctx, draft.ID)
  	return draft, preview, err
  }
  ```

  ```python Python theme={null}
  def create_gallery(dam, model_publication_id, image_publication_id):
      draft = dam.create_gallery({
          "name": "Chair product gallery", "entries": [{
              "publication_id": model_publication_id, "fallback_publication_id": image_publication_id,
              "role": "hero", "locale": "en", "alt": "Oak chair viewed from the front",
          }],
      })
      return {"draft": draft, "preview": dam.preview_gallery(draft["id"])}
  ```

  ```php PHP theme={null}
  function createGallery(\StackShift\AssetDAMClient $dam, string $modelPublicationId, string $imagePublicationId): array {
      $draft = $dam->createGallery([
          'name' => 'Chair product gallery', 'entries' => [[
              'publication_id' => $modelPublicationId, 'fallback_publication_id' => $imagePublicationId,
              'role' => 'hero', 'locale' => 'en', 'alt' => 'Oak chair viewed from the front',
          ]],
      ]);
      return ['draft' => $draft, 'preview' => $dam->previewGallery($draft['id'])];
  }
  ```
</CodeGroup>

## HTTP operations for this workflow

Management requests use Authorization: Bearer with your server-side Assets credential and X-Asset-Space-ID for the selected space. API-key scopes and space/collection permissions both apply. Successful JSON responses use \{success: true, data: ...}; the SDK methods return the unwrapped data. Paths shown outside /api/v1 use their dedicated short-lived credential.

| Operation                                            | Authorization  | Concurrency     | Success |
| ---------------------------------------------------- | -------------- | --------------- | ------- |
| `GET /api/v1/assets/galleries`                       | assets:read    | —               | 200     |
| `POST /api/v1/assets/galleries`                      | assets:write   | —               | 200     |
| `PUT /api/v1/assets/galleries/{galleryID}`           | assets:write   | Quoted If-Match | 200     |
| `POST /api/v1/assets/galleries/{galleryID}/{action}` | assets:publish | Quoted If-Match | 200     |
| `GET /api/v1/assets/galleries/{galleryID}/resolve`   | assets:read    | —               | 200     |
| `POST /api/v1/assets/galleries/{galleryID}/ar`       | assets:read    | —               | 200     |

For If-Match, quote the revision number, for example If-Match: "7". On 412 assets.revision\_conflict, reload the relevant resource and reconcile the edit before retrying. Use the revision of the resource being changed: asset, collaborator, schema, publication or gallery. A bucket-policy save instead places its current revision in the JSON body.

## Gallery payloads and delivery responses

Create and update accept name and the complete ordered entries array. An empty draft is allowed. Each populated entry references a currently deliverable publication in the same space. The service records asset\_id, version\_id, publication\_revision and fallback\_revision; do not calculate or overwrite these snapshot fields.

Creation and update return id, name, entries, revision, optional published\_revision, created\_by and timestamps. List returns galleries and next\_cursor. The mutable draft revision and published\_revision are different: saving does not change the live gallery.

Resolve returns gallery\_id, revision, name and entries. The revision is the published revision; use the saved gallery record’s revision for editing, not this delivery field. Each entry has reference and, when available, delivery and fallback\_delivery. If neither can resolve, unavailable=true allows the UI to show an unavailable item without inventing media. AR requests send only publication\_id and must select a primary entry of the published gallery.

| Field                     | Meaning and constraints                                                                                 |
| ------------------------- | ------------------------------------------------------------------------------------------------------- |
| `name`                    | Trimmed nonblank label, at most 160 bytes.                                                              |
| `entries`                 | Ordered array of at most 50 entries.                                                                    |
| `publication_id`          | Required published publication UUID.                                                                    |
| `fallback_publication_id` | Optional separately published fallback, typically a product image.                                      |
| `role`                    | hero, detail or media; descriptive placement within the gallery.                                        |
| `locale`                  | Language tag beginning with 2–8 letters and optional hyphen-separated subtags, for example en or en-GB. |
| `alt`                     | Required nonblank accessible description, at most 1,000 bytes.                                          |

```json Example request body theme={null}
{
  "name": "Chair product gallery",
  "entries": [
    {
      "publication_id": "YOUR_MODEL_PUBLICATION_UUID",
      "fallback_publication_id": "YOUR_IMAGE_PUBLICATION_UUID",
      "role": "hero",
      "locale": "en",
      "alt": "Oak chair viewed from the front"
    }
  ]
}
```

## Expected result

<Check>
  The site displays an explicitly published gallery snapshot, with authorized media refresh, accessible entry descriptions and independently controlled AR.
</Check>

## Common failures

<Warning>
  * Expecting Save draft to change the live gallery.
  * Supplying an arbitrary fallback URL.
  * Resolving AR without checking membership in the saved gallery.
  * Caching a resolver response as permanent product data.
</Warning>

## Related guides

<CardGroup cols={2}>
  <Card title="Publications, review and usage rights" href="/assets/publications-review-and-rights">
    Select immutable renditions, capture metadata and rights, submit editorial review, publish by channel, and withdraw delivery.
  </Card>

  <Card title="Upload, validate and publish 3D models" href="/assets/models-ar-and-galleries">
    Prepare supported GLB and supplied USDZ files, inspect validation reports, add posters, review the model and launch authorized device AR.
  </Card>

  <Card title="Product rendering: images, spins and turntables" href="/assets/cloudinary-product-rendering">
    Use model-preview controls, estimate rendering units, request proofs or final media, and publish approved immutable outputs.
  </Card>

  <Card title="Use Assets in WordPress" href="/assets/wordpress-media-integration">
    Install the StackShift Assets plugin, select approved media in Gutenberg, publish stable gallery references, and maintain revocation-aware delivery.
  </Card>

  <Card title="Assets SDKs: JavaScript, Go, Python and PHP" href="/assets/sdk-media-workflows">
    Configure space-scoped clients and use native video, governed DAM, model rendering, galleries and CMS capabilities in all four SDKs.
  </Card>
</CardGroup>
