Back to FormTechnical Documentation

SAFD Collection Submission Platform

Technical Documentation

A complete reference for setting up the Shopify integration, understanding the data flow, deploying the platform, and resolving common issues.

1. Architecture Overview

The platform is a full-stack web application built on React 19, tRPC, and Express. When a student submits a collection, the following pipeline executes:

  1. Images are encoded as base64 in the browser and sent to the server via tRPC.
  2. The server decodes them and stores the raw buffers in S3 (Manus built-in storage) as a reliable staging area.
  3. The server calls Shopify's stagedUploadsCreate mutation to obtain secure, temporary upload URLs.
  4. Each image buffer is posted directly to its staged target URL using multipart form data.
  5. The server calls fileCreate with the resourceUrl values returned by the staged targets.
  6. The server polls nodes(ids:) until all files reach READY status, collecting their MediaImage GIDs.
  7. Finally, metaobjectCreate is called with the designer name, description, and a JSON array of image GIDs for the images field.

This two-stage approach (S3 → Shopify) ensures that large or multiple images are reliably transferred even under slow network conditions, and that the browser never communicates directly with the Shopify Admin API (keeping the access token server-side only).

2. Shopify App Setup

The integration requires a Shopify Custom App with Admin API access. Follow these steps to create one:

  1. In your Shopify Admin, navigate to Settings → Apps and sales channels.
  2. Click Develop apps, then Create an app.
  3. Give the app a name (e.g., SAFD Collection Form) and click Create app.
  4. Go to Configuration → Admin API integration and click Configure.
  5. Enable the following access scopes:
ScopePurpose
write_metaobjectsCreate new entries in the Collections metaobject
read_metaobjectsVerify metaobject definition exists
write_filesUpload images via stagedUploadsCreate and fileCreate
read_filesPoll file processing status after upload

After saving, go to API credentials and click Install app. Copy the Admin API access token — it is only shown once. Store it as the SHOPIFY_ACCESS_TOKEN environment variable.

To find your metaobject type handle, go to Content → Metaobjects in Shopify Admin, click on your Collections definition, and look at the URL — the last segment is the type handle (e.g., collections or safd_collections). Set this as SHOPIFY_METAOBJECT_TYPE.

Required environment variables

SHOPIFY_STORE_DOMAIN=your-store.myshopify.com
SHOPIFY_ACCESS_TOKEN=shpat_xxxxxxxxxxxxxxxxxxxx
SHOPIFY_METAOBJECT_TYPE=collections

3. Field Mapping

The form fields map directly to the Shopify metaobject field keys. The table below shows the exact mapping used in the metaobjectCreate mutation:

Form FieldMetaobject KeyField TypeValue Format
Designer Namedesignersingle_line_text_fieldPlain string
Descriptiondescriptionmulti_line_text_fieldPlain string (newlines preserved)
Imagesimageslist.file_referenceJSON array of MediaImage GID strings

The images field expects a JSON-serialised array of Shopify Global IDs (GIDs) in the format gid://shopify/MediaImage/<id>. These GIDs are obtained after uploading images through the staged upload pipeline and calling fileCreate.

If your metaobject uses different field keys (e.g., designer_name instead of designer), update the keys in server/shopify.ts → createCollectionMetaobject().

mutation metaobjectCreate($metaobject: MetaobjectCreateInput!) {
  metaobjectCreate(metaobject: $metaobject) {
    metaobject { id handle }
    userErrors { field message code }
  }
}

# Variables:
{
  "metaobject": {
    "type": "collections",
    "fields": [
      { "key": "designer",    "value": "Jane Doe" },
      { "key": "description", "value": "A collection inspired by…" },
      { "key": "images",      "value": "["gid://shopify/MediaImage/123"]" }
    ]
  }
}

4. API Integration Flow

The complete server-side flow is implemented across two tRPC procedures in server/routers/collection.ts and the Shopify helper in server/shopify.ts:

Procedure 1 — collection.uploadImagesToS3

Accepts base64-encoded file payloads, decodes them to buffers, and stores each one in S3 with a unique key. Returns the array of S3 keys for use in the next step.

Procedure 2 — collection.submitToShopify

  1. Fetches each file buffer from S3 using the keys returned by step 1.
  2. Calls stagedUploadsCreate to get temporary Shopify upload targets.
  3. POSTs each buffer to its staged target using multipart form data (Shopify's required format).
  4. Calls fileCreate with the resourceUrl from each staged target.
  5. Polls nodes(ids:) every 1.5 s until all files are READY (max 20 attempts = 30 s).
  6. Calls metaobjectCreate with the collected MediaImage GIDs.
// Staged upload input (one entry per image)
{
  filename: "collection-01.jpg",
  mimeType: "image/jpeg",
  resource: "FILE",
  httpMethod: "POST",
  fileSize: "2048000"   // bytes as string
}

// fileCreate input
{
  originalSource: "https://shopify-staged-uploads.s3.amazonaws.com/…",
  contentType: "IMAGE"
}

// metaobjectCreate field values
{ key: "images", value: "["gid://shopify/MediaImage/987654321"]" }

5. Deployment Instructions

The platform is deployed via the Manus publishing workflow. No external hosting configuration is required.

  1. Ensure all three Shopify environment variables are set in the Manus project Secrets panel (SHOPIFY_STORE_DOMAIN, SHOPIFY_ACCESS_TOKEN, SHOPIFY_METAOBJECT_TYPE).
  2. Create a checkpoint via the Manus interface (required before publishing).
  3. Click the Publish button in the Manus Management UI header. The platform will be live at your assigned *.manus.space domain.
  4. Optionally, bind a custom domain under Settings → Domains.

To embed the form in your Shopify store, you can link to the published URL from a custom page, a navigation menu item, or a Shopify theme button. The form operates as a standalone web application and does not require Shopify theme integration.

Embedding in Shopify (optional)

In your Shopify theme, add a button or link pointing to the published Manus URL. Students click the link, complete the form, and the submission is created directly in your metaobject store — no Shopify theme code changes are required.

6. Troubleshooting

Error: Value must be a file reference string

This occurs when the images field receives a raw URL or an incorrect GID format. Ensure you are using GIDs in the form gid://shopify/MediaImage/<numeric-id> and that the value is a JSON-serialised array (e.g., ["gid://shopify/MediaImage/123"]).

Error: metaobjectCreate errors: Field 'images' is invalid

The metaobject field key does not match. Open Shopify Admin → Content → Metaobjects → your Collections definition and verify the exact field key for the image field. Update the key in server/shopify.ts → createCollectionMetaobject() accordingly.

Error: Shopify API HTTP 401

The access token is invalid or has expired. Re-install the custom app in Shopify Admin to generate a new token, then update the SHOPIFY_ACCESS_TOKEN secret in the Manus Secrets panel.

Error: Timed out waiting for Shopify to process uploaded files

Shopify's file processing pipeline occasionally takes longer than expected for large images. The platform polls for up to 30 seconds (20 × 1.5 s). If this error persists, try reducing image file sizes to under 5 MB each, or increase maxAttempts in server/shopify.ts → pollFilesUntilReady().

Error: write_files access scope required

The custom app is missing the write_files scope. Go to Shopify Admin → Settings → Apps → your app → Configuration → Admin API integration and enable write_files and read_files, then reinstall the app.

Images upload but the metaobject has no images

This usually means the metaobject's images field type is set to file_reference (single) rather than list.file_reference (multiple). In Shopify Admin, edit the metaobject definition and change the field type to support multiple files. No code changes are required.