A Square item and a Shopify product are the same idea. Each is the named thing you sell, a latte or a t-shirt, and each holds the sellable units underneath it: Square calls them item variations, Shopify calls them product variants. In both APIs the price and SKU live on the unit, not on the parent.
The differences are in what else each catalog holds and in the word itself. Square's catalog also stores taxes, discounts, and modifier lists, because it was built for a point-of-sale counter. Shopify's stores products, collections, and inventory items, because it was built for an online store. And "item" means something different in nearly every commerce API, which is why Flint, which I started after four years at Square, calls the sellable thing a product.
This post maps the two models object by object, shows the same order row in each API, and explains the naming choice behind Flint's catalog.
The short answer: how the objects map#
| What it is | Square | Shopify | Flint |
|---|---|---|---|
| The thing you sell, by name | SquareCatalogItem (ITEM) | ShopifyProduct | FlintProduct (prod_) |
| The unit with a price and SKU | SquareCatalogItemVariation (ITEM_VARIATION) | ShopifyProductVariant | FlintVariant (var_) |
| A dimension such as Size | SquareCatalogItemOption (ITEM_OPTION) | ShopifyProductOption | FlintOption (opt_) |
| One value, such as Large | SquareCatalogItemOptionValue (ITEM_OPTION_VAL) | ShopifyOption value | FlintOption value (optv_) |
| A group for browsing | SquareCatalogCategory | ShopifyCollection | FlintCategory (ctg_) |
| A choice made at purchase, such as oat milk | SquareCatalogModifierList and CatalogModifier | ShopifyNo catalog object | FlintModifier group, modifier, and modifier set |
| Several units sold as one | SquareNo bundle type | ShopifyA product with components | FlintBundle (bun_) |
| The stock record | SquareInventory count per variation and location | ShopifyInventoryItem and InventoryLevel | FlintInventory item (invi_) and level |
| Tax | SquareCatalogTax, a rate the seller enters | ShopifyShop tax settings | Flinttaxable and a tax category on the variant |
| A row on an order | SquareOrderLineItem, with a uid unique within the order | ShopifyLineItem | FlintOrder line item (li_) |
Every row lines up except two. Shopify has no catalog object for a purchase-time choice like a milk or a side, and Square's catalog has no bundle type. Flint has both, which matters if you're moving a menu or a store onto one API.
How Square models a catalog#
Everything in Square's Catalog API is a CatalogObject with a type and a matching *_data field. There are 19 types, from ITEM and ITEM_VARIATION to TAX, DISCOUNT, MODIFIER_LIST, PRICING_RULE, and SUBSCRIPTION_PLAN. Square's documentation calls the whole set the seller's item library.
The rules for items and variations:
- An item has no price. Square's reference says "An item must have at least one variation," and the price sits on the variation in
price_money, withpricing_typeset toFIXED_PRICINGorVARIABLE_PRICINGfor a price set at the time of sale. - Variations carry the SKU and the UPC, plus per-location price and stock overrides.
- One item takes up to 250 variations, and up to 6 item options to organize them.
- An item has a
product_type. Values includeREGULAR,FOOD_AND_BEV,EVENT,DIGITAL,DONATION, andAPPOINTMENTS_SERVICE, though the API only lets you createREGULARandAPPOINTMENTS_SERVICEitems.
Here's a latte with one size, in the shape UpsertCatalogObject takes. The # IDs are temporary ones Square replaces on write.
{
"type": "ITEM",
"id": "#latte",
"item_data": {
"name": "Latte",
"variations": [
{
"type": "ITEM_VARIATION",
"id": "#latte-12",
"item_variation_data": {
"item_id": "#latte",
"name": "12 oz",
"sku": "ESP-LATTE-12",
"pricing_type": "FIXED_PRICING",
"price_money": { "amount": 475, "currency": "USD" }
}
}
]
}
}
Stock lives in a separate API. Square's Inventory API keeps an InventoryCount per catalog object and location, almost always an ITEM_VARIATION, with the quantity as a decimal string. And on an order, a line item points at the variation through catalog_object_id, not at the item.
How Shopify models a catalog#
Shopify's Admin API describes products as "the goods and services that merchants offer to customers." A Product has options, such as Size and Color, and ProductVariant records, each with its own price, sku, and selectedOptions.
- Up to 3 options and 2,048 variants per product, according to Shopify's help center.
- Stock lives on an
InventoryItem. Shopify says an inventory item "represents the inventory for a product variant" and holds the SKU, whether quantities are tracked, and shipping and customs details. AnInventoryLevelholds its quantity at one location. - Collections group products, either by hand (
custom) or by rules (smart). - Bundles are products.
productBundleCreategroups other products as components, and the bundle appears as a single product in the store. - There's no modifier object. For choices beyond its option limits, Shopify's help center points to apps or line item properties, which arrive on the order as
customAttributes: text, with no price of its own.
A Shopify LineItem "represents a single product or service that a customer purchased in an order." It links to the variant and the product, and keeps the product and variant titles as they were when the order was placed.
The same order row in three APIs#
Two 16 oz lattes, as each API returns the order line. Square:
{
"uid": "g3Lk7Q2nR8",
"name": "Latte",
"variation_name": "16 oz",
"quantity": "2",
"catalog_object_id": "W62UWFY35CWMYGVWK6TWJDNI",
"item_type": "ITEM",
"base_price_money": { "amount": 525, "currency": "USD" }
}
Shopify, through GraphQL:
{
"id": "gid://shopify/LineItem/15283746592",
"name": "Latte - 16 oz",
"sku": "ESP-LATTE-16",
"quantity": 2,
"variant": { "id": "gid://shopify/ProductVariant/48211203947" },
"product": { "id": "gid://shopify/Product/9123374501" }
}
Flint:
{
"order_line_item_id": "li_1kmn0aExample",
"type": "variant",
"product_id": "prod_1kmn0bExample",
"variant_id": "var_1kmn0cExample",
"name": "Latte - 16 oz",
"sku": "ESP-LATTE-16",
"quantity": 2,
"unit_price_money": { "amount": 525, "currency": "USD" },
"selected_options": [
{ "option_id": "opt_1kmn0aExample", "option_name": "Size", "option_value_id": "optv_1kmn0bExample", "value": "16 oz" }
]
}
Read them as a developer seeing each API for the first time.
In Square's row, catalog_object_id is an opaque ID, and only the docs tell you it's a variation and not an item. item_type: "ITEM" doesn't mean "this row is a catalog item" either. It means an itemized sale, as opposed to CUSTOM_AMOUNT or GIFT_CARD. The row's own uid is unique only within the order.
Shopify's global IDs name their type, and the row separates the line from the variant from the product. That's most of why the Shopify model is easy to learn.
Flint's row names every reference by what it points to: the row is an order_line_item_id, the unit is a variant_id, and the parent is a product_id. The ID prefixes say the same thing, so a stray var_ in a log tells you what it is without a lookup.
Why the word matters#
Square's choice of "item" makes sense for where it started. A coffee, a haircut, a shirt, and a custom amount all fit the word, and a point-of-sale counter has to ring up all of them. "Item" isn't wrong. It's broad, and broad names get harder to use as an API grows.
"Item" collides with "line item"#
Every commerce API needs a name for a row on an order, an invoice, or a subscription, and the industry's name is line item. When the catalog object is also an "item," the two compete for the same word.
Stripe shows how far this goes. Stripe's catalog uses Products and Prices, but its billing objects reuse "item" three ways:
- An invoice item (
ii_) is a charge or credit for a customer, attached to an invoice or created ahead of one. - An invoice line item (
il_, with"object": "line_item") is a row on the invoice, and points back to the invoice item it came from. - A subscription item (
si_) is one price on a subscription.
None of the three is the thing being sold. Each is well defined, and you still have to learn the difference before you can read an invoice. Google's API design guidance, AIP-190, names the problem directly: "Avoid name overloading. Use different names for different concepts."
Square's own docs reach for "product"#
When the documentation for a noun keeps defining it with another noun, the other noun is usually the clearer one. Square's docs do this:
- "An item library entry for a product or service is called a catalog item."
- The
ITEMtype "represents a product for sale or a service for hire." buyer_facing_nameon an item is "The override to a product name to display to users."- A
PRODUCT_SET, used to target pricing rules, holdsproduct_ids_any, and those IDs can be items, variations, or categories.
Sellers say "product"#
At Square, I heard sellers say "Square products" about as often as they confused Square with Squarespace. When the people using your system reach for a different word than the one in your API, the API is the one translating. Nielsen Norman Group's usability heuristic says to "Use words, phrases, and concepts familiar to the user, rather than internal jargon," and developers are users too.
"Product" means "offered for sale"#
"Item" can mean a row, a unit in a box, a menu entry, a list entry, or a stock record. "Product" means one thing: something offered for sale. That's not limited to physical goods. A haircut, a membership, a digital download, a delivery fee, and a gift card are all products. Flint's product_type reflects that: physical, service, fee, digital, or gift_card.
How Flint names its catalog#
Flint keeps "product" for the sellable definition and "line item" for every transactional row. "Item" still appears, but never alone, so it's always clear what kind you have.
| Name | ID prefix | What it is |
|---|---|---|
product | prod_ | What you sell, by name. It has no price or SKU. |
variant | var_ | The unit you sell, with its price, SKU, tax category, and optional stock link |
bundle | bun_ | Several variants sold as one line at their own price |
inventory_item | invi_ | The stock record a variant points at. Two variants can share one. |
order_line_item | li_ | A row on an order |
invoice_line_item | invli_ | A row on an invoice, linked to its order line by order_line_item_id |
subscription_line_item | sli_ | A row on a subscription |
payment_link_line_item | plli_ | A row on a payment link |
return_line_item | retli_ | A row on a return, linked to its order line |
Refunds, returns, invoices, and shipment packages all point back to the order line by order_line_item_id, so one ID follows a latte from the order to its refund. Flint's inventory item is the same idea as Shopify's: stock you count, kept separate from the thing you sell. Inventory covers locations and holds.
What Flint's catalog does with the model#
The naming is the visible part. The model under it takes the piece each platform got right and puts them in one API.
Options and variants, as in Shopify. A product lists its options and every variant it sells. Each variant selects one value per option, and Flint rejects two variants with the same combination. Flint never generates variants from options, so you sell exactly the units you list.
curl -X POST https://api.withflintpay.com/v1/products \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: product-latte" \
-H "Content-Type: application/json" \
-d '{
"variant_mode": "optioned",
"name": "Latte",
"product_type": "physical",
"category_handles": ["espresso-drinks"],
"options": [{
"name": "Size",
"values": [{ "value": "12 oz" }, { "value": "16 oz" }]
}],
"variants": [
{
"name": "12 oz",
"sku": "ESP-LATTE-12",
"unit_price_money": { "amount": 475, "currency": "USD" },
"line_item_tax_category": "prepared_food",
"selected_options": [{ "option_name": "Size", "value": "12 oz" }]
},
{
"name": "16 oz",
"sku": "ESP-LATTE-16",
"unit_price_money": { "amount": 525, "currency": "USD" },
"line_item_tax_category": "prepared_food",
"selected_options": [{ "option_name": "Size", "value": "16 oz" }]
}
]
}'
Putting a price or SKU on the product itself returns 400 INVALID_REQUEST with UNKNOWN_FIELD, so the product-versus-unit split is enforced, not just documented.
Modifiers, as in Square. A modifier group is one prompt, such as Milk, with selection limits. A modifier is one choice with an optional unit_price_delta_money, such as oat milk for 75 cents. A modifier set orders the groups and attaches to a product, a variant, or a bundle. Flint enforces the rules on the order: leaving out a required milk returns REQUIRED, and picking two returns INVALID_VALUE.
Bundles with their own price. A breakfast combo sells a croissant and a latte for $7.50 as one line, with its own SKU and tax settings, and Flint holds each tracked component's stock.
The catalog prices the order. An order line names a variant_id or a bundle_id, never a product_id, and Flint copies the name, price, SKU, and tax settings onto the line. A catalog line that sends its own unit_price_money is rejected with NOT_ALLOWED, so a buyer who edits the request can't set the price. For something that isn't in the catalog, such as Square's custom amount, send an ad hoc line with a name, a unit_price_money, and a fulfillment requirement.
Tax by category. Instead of a rate typed into the catalog, as with Square's CatalogTax, each variant carries taxable and a line_item_tax_category, and Flint's automatic sales tax calculates US tax from the buyer's location. Each line records its own tax_money, and refunds reverse it.
Refunds by line. Because the order keeps what each line cost, a refund names the line and a quantity, and Flint works out its share of discounts and tax:
curl -X POST https://api.withflintpay.com/v1/refunds \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: refund-ord-1kmn0a-latte" \
-d '{
"refund_type": "targets",
"order_id": "ord_1kmn0aExample",
"reason": "requested_by_customer",
"line_items": [
{ "order_line_item_id": "li_1kmn0aExample", "quantity": 1,
"tax_refund_mode": "automatic" }
]
}'
Square's Refunds API takes an amount, so refunding one latte there means computing that amount yourself. Can the Square API refund one item? walks through the difference.
Catalog setup builds this whole café menu step by step: categories, a sized latte, a milk modifier set, a combo bundle, and the order that sells them. The Products API reference has every field.
Moving a Square or Shopify catalog to Flint#
The objects map one to one, so a migration script is mostly renaming.
| From Square | From Shopify | To Flint |
|---|---|---|
| Item | Product | Product |
| Item variation | Product variant | Variant, with unit_price_money and sku |
| Item option and values | Product option and values | Option and values; each variant sends selected_options |
| Category | Collection | Category, assigned by category_handles |
| Modifier list and modifiers | Line item properties or an app | Modifier group, modifiers, and a modifier set |
| No bundle type | Bundle product | Bundle with components |
| Inventory count per location | Inventory item and levels | Inventory item linked from the variant, with a level per location |
| Custom amount line | Custom line item | Ad hoc line with name, unit_price_money, and fulfillment |
Two things to plan for. Square measurement units allow decimal quantities such as 2.25 pounds, while Flint order quantities are whole numbers, so sell by-weight goods in a fixed unit, such as a quarter pound. And a SKU must be unique across every variant and bundle in a Flint environment, so clean up duplicate SKUs before you import.
To keep another system in step after the move, subscribe to Flint's 24 catalog events, such as product.updated and product_variant.created. Catalog setup covers ordering, backfills, and archived records.
For the rest of a migration:
- Migrating from Square maps Square payments, orders, subscriptions, invoices, and webhooks to Flint.
- Flint vs Square and Flint vs Shopify compare the platforms beyond the catalog.
- Headless Shopify checkout covers keeping a Shopify storefront while orders and payments run on Flint.
- Payments for restaurant tech covers menus, modifiers, and tabs for software that replaces a Square counter.
Where Square and Shopify fit#
Square's catalog is built for a counter. It handles variable prices set at the register, decimal quantities sold by weight, kitchen names on tickets, and per-location price overrides, and it runs Square's own point-of-sale hardware.
Shopify's catalog is built for a storefront. It handles thousands of variants per product, rule-based collections, and an app ecosystem that fills gaps such as modifiers.
If you're building your own storefront, app, or point-of-sale software and want the catalog, orders, tax, and refunds behind one API, that's what Flint's commerce API is for.
FAQ#
Is a Square item the same as a Shopify product?#
Yes, for most purposes. Both are the named thing you sell, both hold their sellable units as children (item variations in Square, product variants in Shopify), and both keep the price and SKU on those children. When you sync the two, map item to product and item variation to variant.
What's the difference between an item and a line item?#
An item, or product, is the catalog definition of something you sell. A line item is one row on a specific order, invoice, or subscription: that product, at a quantity and price, with its discounts and tax. One product appears on many line items. In Flint, a product is prod_ and an order line item is li_, so the two never share a name.
What's the difference between a Square item variation and a Shopify variant?#
Very little. Both carry the price, SKU, and option values, and both are what an order line points at. Square allows up to 250 variations per item and 6 item options. Shopify allows up to 2,048 variants and 3 options per product. Square variations can also use variable pricing, set at the time of sale.
Does Shopify have modifiers like Square?#
Not as a catalog object. Shopify's help center points to apps or line item properties for choices beyond its option limits, and line item properties are text on the order with no price of their own. Square and Flint both model priced, purchase-time choices, such as oat milk for 75 cents, in the catalog.
How do I sync Square items to Shopify products?#
Map each Square item to a Shopify product, each item variation to a variant, item options to product options, and categories to collections. Watch for three gaps: Square items can use up to 6 options while Shopify allows 3, Square modifier lists have no Shopify equivalent, and Square's decimal quantities don't fit Shopify's whole-number line item quantities.
Why does Stripe have both invoice items and invoice line items?#
An invoice item (ii_) is a charge or credit for a customer. You can create one ahead of the invoice, such as an extra charge for the next subscription bill, or attach it to an invoice directly. On the invoice, it appears as an invoice line item (il_), a row that points back to its invoice item. Subscription items (si_) are separate again: one price on a subscription. Stripe's catalog object, the thing being sold, is a Product.
What does Flint call a catalog item?#
A product. Its sellable units are variants, its purchase-time choices are modifiers, and several variants sold together are a bundle. Rows on orders, invoices, subscriptions, and payment links are all line items, and "item" never appears on its own.
Sources#
Square, Shopify, and Stripe details come from their documentation, checked on 2026-10-24.
- Square: What It Does (item library, catalog items, the 250-variation limit), CatalogObjectType, CatalogItem (at least one variation, up to 6 item options,
buyer_facing_name), CatalogItemProductType, CatalogItemVariation, CatalogProductSet, InventoryCount, OrderLineItem, and OrderLineItemItemType. - Shopify: Product, ProductVariant, InventoryItem, Collection, productBundleCreate, LineItem, and the help center's Adding variants (2,048 variants, 3 options).
- Stripe: How products and prices work, The Invoice Item object, The Invoice Line Item object, and The Subscription Item object.
- AIP-190: Naming conventions, Google's API design guidance on familiar terms and name overloading.
- 10 Usability Heuristics for User Interface Design, Nielsen Norman Group.
