Skip to content
littleworksdocs

Explore the documentation

Meet LittleworksA little backend for what you’re building on Shopify.Connect to LittleworksChoose the CLI, a plugin, or a remote MCP connection.CLIConnect from your terminal with Shopify approval and no manual tokens.Claude pluginThe Littleworks MCP connection and agent guidance in one package.OpenAI pluginThe Littleworks MCP connection and agent guidance in one package.MCP with OAuthConnect a compatible client directly, without a marketplace plugin.Build with an agentThe operating guide for agents building on Shopify with Littleworks.Write a workA JavaScript handler, a small manifest, and an immutable version each time you deploy.Keep secretsEncrypted credentials, shared across your store’s works.Call external servicesMake public HTTPS API requests from a work using http.fetch().Store dataDocument collections with a small API, scoped automatically to the work that uses them.Call ShopifyCall Shopify’s Admin and Storefront GraphQL APIs with credentials held by Littleworks.Expose an endpointConnect an existing frontend to a work through a small JSON API.Customer access and invitationsRequire a signed-in shopper or a narrow, expiring invitation before a work runs.RecipesUseful things to build for your store, with the backend already taken care of.Product reviewsCollect customer reviews, verify purchases, and publish approved content directly into your Shopify theme.Customer wishlistsGive signed-in customers a persistent list of products they can revisit across devices.Customer quote requestsCollect a customer’s products, quantities, and requirements without turning a request into an order.Build a merchant viewCombine data and action works in a native Shopify screen, with draft and published versions.Requests inboxA searchable inbox with filters, cursor pagination, a details modal, editable requests, and bulk closing. All records stay internal to this starter.Settings formA prefilled form with typed fields, save/discard controls, and revision-checked writes.Operations overviewIndependent data works populate a metrics card and resource list in a main column, with a small informational sidebar.MCP tool referenceTools to inspect, deploy, run, and organize the backend for a connected store.Usage and allowancesSee what your works use, how much capacity remains, and when allowances reset.Inspect and troubleshootFind the relevant run, understand the failure, and make the next change deliberately.Limits and securityThe current runtime boundaries, data isolation model, and execution allowances.
Shopify admin
View examples
Markdown

Settings form

A prefilled form with typed fields, save/discard controls, and revision-checked writes.

Settings form — local preview with synthetic data
Settings form — local preview with synthetic data

A prefilled form with typed fields, save/discard controls, and revision-checked writes.

Build this example

  1. Read get_context and list_views. Choose an unused view name; if adapting the name, update every work’s trigger.view and all work bindings together. Inspect existing works before deploying.
  2. Deploy each manifest and self-contained JavaScript source below with deploy_function. No terminal, imports, dependencies, or bundler is needed. Record each returned deployment ID.
  3. Test unpublished data works with their sample input and a fresh idempotency key. Only run sample actions when requested; they write synthetic records. Do not pass placeholder IDs or stale revisions.
  4. Replace each source/action version in the view definition, including modal bindings, with the matching deployment ID. Call validate_view with the deployment object; fix any issues, then call deploy_view.
  5. Open the returned manageUrl in Shopify to inspect the draft. Test empty states, form errors and refresh behaviour. Publish only when requested, using the returned version and current publishedVersion. A published page consumes one slot; its modals do not.

Load

Work manifest
{
  "name": "starter.starter-settings.load",
  "title": "Load",
  "feature": "starter_starter_settings",
  "environment": "preview",
  "trigger": {
    "type": "view",
    "view": "starter-settings",
    "role": "data"
  },
  "permissions": {
    "shopify": false
  },
  "inputSchema": {
    "type": "object",
    "properties": {
      "filters": {
        "type": "object"
      },
      "search": {
        "type": "string"
      },
      "sort": {
        "type": [
          "object",
          "null"
        ]
      },
      "after": {
        "type": [
          "string",
          "null"
        ]
      },
      "limit": {
        "type": "integer",
        "minimum": 1,
        "maximum": 25
      }
    },
    "required": [],
    "additionalProperties": false
  }
}
Self-contained JavaScript source
export default async ({db}) => {
  const record = await db.collection("settings").get("preferences");
  return record ? {...record.data, revision: record.revision} : {label: "Request a quote", enabled: true, responseDays: 3, revision: null};
};
Sample invocation input
{
  "filters": {},
  "search": "",
  "sort": null,
  "after": null,
  "limit": 25
}

Save

Work manifest
{
  "name": "starter.starter-settings.save",
  "title": "Save",
  "feature": "starter_starter_settings",
  "environment": "preview",
  "trigger": {
    "type": "view",
    "view": "starter-settings",
    "role": "action"
  },
  "permissions": {
    "shopify": false
  },
  "inputSchema": {
    "type": "object",
    "properties": {
      "revision": {
        "type": [
          "integer",
          "null"
        ]
      },
      "label": {
        "type": "string",
        "minLength": 1,
        "maxLength": 100
      },
      "enabled": {
        "type": "boolean"
      },
      "responseDays": {
        "type": "integer",
        "minimum": 1,
        "maximum": 30
      }
    },
    "required": [
      "revision",
      "label",
      "enabled",
      "responseDays"
    ],
    "additionalProperties": false
  }
}
Self-contained JavaScript source
export default async ({input, db}) => {
  const settings = db.collection("settings");
  const current = await settings.get("preferences");
  if ((current?.revision ?? null) !== input.revision) return Response.json({error: "Settings changed. Refresh before saving."}, {status: 409});
  const label = input.label.trim();
  if (!label) return Response.json({error: "Enter a heading."}, {status: 422});
  const data = {label, enabled: input.enabled, responseDays: input.responseDays};
  const saved = current ? await settings.update(current.id, data, {revision: current.revision}) : await settings.create(data, {id: "preferences"});
  return {revision: saved.revision};
};
Sample invocation input
{
  "revision": null,
  "label": "Request a quote",
  "enabled": true,
  "responseDays": 3
}

View definition

validate_view / deploy_view arguments
{
  "baseVersion": null,
  "definition": {
    "apiVersion": 3,
    "name": "starter-settings",
    "title": "Request settings",
    "sources": [
      {
        "id": "settings",
        "work": "starter.starter-settings.load",
        "version": "00000000-0000-4000-8000-000000000001"
      }
    ],
    "actions": [
      {
        "id": "save",
        "label": "Save settings",
        "work": "starter.starter-settings.save",
        "version": "00000000-0000-4000-8000-000000000002",
        "scope": "page",
        "source": "settings",
        "input": {
          "revision": {
            "source": "data",
            "field": "revision"
          },
          "label": {
            "source": "form",
            "field": "label"
          },
          "enabled": {
            "source": "form",
            "field": "enabled"
          },
          "responseDays": {
            "source": "form",
            "field": "responseDays"
          }
        },
        "fields": [
          {
            "name": "label",
            "label": "Request heading",
            "type": "text",
            "required": true,
            "maxLength": 100,
            "initialField": "label"
          },
          {
            "name": "enabled",
            "label": "Accept requests",
            "type": "checkbox",
            "initialField": "enabled"
          },
          {
            "name": "responseDays",
            "label": "Response time (days)",
            "type": "number",
            "required": true,
            "min": 1,
            "max": 30,
            "step": 1,
            "initialField": "responseDays"
          }
        ],
        "refresh": [
          "settings"
        ]
      }
    ],
    "layout": {
      "type": "stack",
      "main": [
        "settingsCard"
      ],
      "elements": {
        "settingsCard": {
          "type": "Section",
          "props": {
            "heading": "Request preferences"
          },
          "children": [
            "form"
          ]
        },
        "form": {
          "type": "Form",
          "props": {
            "action": "save"
          }
        }
      }
    }
  }
}