An app adds to the storefront in three ways, all declared in tringify.app.json and frozen into each release. Changing a declaration reaches a store only when it installs or updates to a release that contains it.
Extension |
What it is |
Who places it |
|---|---|---|
App block |
A Vascula block the merchant adds to a theme section, which opens one of your hosted surfaces. |
The merchant, in the theme editor, in sections that accept app blocks. |
Hosted surface |
A page on your server that the storefront shows in an isolated frame, inline, as a drawer, a modal or a sticky bar. It can read and change the cart only through commands you declare. |
Opened by an app block. |
App embed |
Vascula markup on every page, or a tracking pixel. |
The merchant turns it on under App embeds in the theme editor. |
Storefront extensions never appear in checkout, and nothing an app does on the storefront can stop a shopper checking out.
App blocks#
"storefront_blocks": [
{
"key": "size-guide",
"name": "Size guide",
"surface_key": "size-guide",
"source": "<p class=\"size-guide__label\">{{ settings.label }}</p>\n{% schema %}\n{\"name\": \"size-guide\", \"kind\": \"block\", \"settings\": [{\"id\": \"label\", \"type\": \"text\", \"default\": \"Find your size\"}]}\n{% endschema %}"
}
]
Field |
Meaning |
|---|---|
|
Lowercase letters and digits joined by |
|
What merchants see in the theme editor. Up to 80 characters. |
|
The hosted surface this block opens. It must be declared in the same release. |
|
A complete Vascula block, up to 128 KB, with |
An app can declare up to 10 blocks. Merchants can add them to any section whose schema sets accept_app_blocks. The storefront renders the block's markup and then mounts its surface directly after it.
Hosted surfaces#
"storefront_surfaces": [
{
"key": "size-guide",
"name": "Size guide",
"url": "https://example.app/surfaces/size-guide@1.4.0",
"bridge_major": 1,
"presentation": "inline",
"launch_transport": "form_post",
"height": 320,
"actions": ["cart.add_lines"],
"context": ["page.type", "product.id", "product.selected_variant_id"]
}
]
Field |
Meaning |
|---|---|
|
As for blocks. Up to 10 surfaces per app. |
|
An HTTPS page on your server with a version or content hash in its path, such as |
|
|
|
How it first appears: |
|
|
|
For inline surfaces, the height in pixels to reserve before your page loads, up to 2400, so the page doesn't jump. |
|
The commands the surface may send. See below. |
|
The page facts the surface receives. See below. |
|
Optional first paint shown while your page loads: |
|
Up to 4 static files from your origin the storefront may fetch early: |
Commands#
Action |
What it does |
|---|---|
|
Returns the shopper's cart. |
|
Adds lines: |
|
Changes quantities of lines your app added: |
|
Removes lines your app added, with the same shape. |
|
Takes the shopper straight to checkout with the lines you send, same payload as |
|
Takes the shopper to checkout with their cart. |
cart.update_lines and cart.remove_lines refuse lines the shopper added themselves and lines another app added. Surfaces cannot apply discount codes or set a gift card recipient. Prices, stock and line property rules are checked by Tringify, and each response carries any user_errors to show the shopper.
Context#
Context |
Field in the launch |
Value |
|---|---|---|
|
|
The kind of page, such as |
|
|
The shopper's language, country and currency. |
|
|
On product pages. |
|
|
The variant selected when the surface opened. |
|
|
The product's options and variants with prices in the shopper's currency and availability, so your surface can offer its own variant picker. |
|
|
Whether the product is a gift card. |
|
|
Whether the shopper is signed in. Never who they are. |
|
|
The number of items in the cart. |
Declare only what the surface uses. When an update adds commands or context to a surface, or moves it to another origin, the store's owner approves the update before it takes effect.
Open a surface#
When a surface opens, the storefront posts a form to its url with a launch_token field: a signed token that names the store, your app, the surface and the context you declared, and expires after two minutes. Exchange it from your server before you return the page:
curl -X POST https://api-store.tringify.com/api/2025-01/runtime/surface-launches/exchange \
-H "Authorization: Bearer LAUNCH_TOKEN"
{
"success": true,
"data": {
"store_id": "74e634ea-84ec-43b8-b66b-986214c2c5a3",
"storefront_id": "1b2f6a43-0c8e-4d2b-9a7e-3f5d8c1e6b20",
"app_id": "d6a26466-7903-4e2f-8fd7-7c59f3c8cd9a",
"audience": ["YOUR_CLIENT_ID"],
"surface_key": "size-guide",
"surface_group_id": "5f0c2b1e-7a4d-4c9e-8b3f-2d6e1a9c4b70",
"subject": "...",
"customer_subject": "...",
"bridge_major": 1,
"actions": ["cart.add_lines"],
"context": { "page_type": "product", "product_id": 1042, "variant_id": 2210 },
"ancestor_origins": ["https://example-store.com"],
"expires_at": "2026-10-05T10:02:00Z"
}
}
A token can be exchanged once; a second exchange returns
401.Check that
app_idis your app andaudienceis your client ID.subjectidentifies the shopper's browser session for your app.customer_subjectis present when the shopper is signed in: an identifier for that customer that only your app receives, not their account ID or contact details.Send
Content-Security-Policy: frame-ancestorswith theancestor_originsso only that storefront can frame your page.
The exchange needs no Store API token. If your surface needs store data the launch doesn't carry, fetch it from your server with the store's token.
Talk to the storefront#
<script src="https://bridge.tringify.com/surface-bridge@1.js"></script>
<script>
const surface = TringifySurface.init({
onInit: (init) => {
// init.actions, init.context, init.expires_at
surface.ready();
},
});
async function addToCart(variantId) {
const result = await surface.command("cart.add_lines", {
lines: [{ product_id: 1042, variant_id: variantId, quantity: 1 }],
});
if (result.success) surface.toast("Added to cart");
}
</script>
Method |
Use |
|---|---|
|
Shows the surface and reports its height. |
|
Keeps reporting the element's height as it changes. |
|
Tells the storefront there is nothing to show, so it removes the space. |
|
Switches between |
|
Sends one of the surface's declared commands. Commands run one at a time, in order, and time out after 15 seconds. |
|
Shows a short message on the page. |
|
Scrolls the page to the surface. |
App embeds#
"storefront_embeds": [
{
"key": "store-badge",
"name": "Trust badge",
"kind": "vascula",
"surface": "global_body_end",
"consent_category": "essential",
"snippet": "<div class=\"trust-badge\">{{ settings.text }}</div>",
"settings_schema": [{ "id": "text", "type": "text", "default": "Secure checkout" }]
}
]
Field |
Meaning |
|---|---|
|
As for blocks. Up to 5 embeds per app. |
|
|
|
|
|
The consent the shopper must give before the embed is added to the page: |
|
For |
|
Optional settings the merchant fills in when turning the embed on, with the same setting types as theme sections. The snippet reads them as |
Merchants turn embeds on per storefront, under App embeds in the theme editor. An embed is left out of the page until the shopper has given the consent it declares.
Personalized products#
To collect text from the shopper, such as an engraving, declare line properties; for uploaded artwork and print files, use line artifacts. See Line properties and App-owned line artifacts. Check what the shopper supplies in your surface before you add it to the cart: once a line is in the cart, checkout never waits on your app.
Test#
Install the app on a development store and add your block to a product page in the theme editor.
Under the app's Installs in the Developer Portal, check which release each store runs.
Under Webhooks, Download lifecycle examples gives sample line artifact events to test your handlers without real orders.