Skip to content

Plugin system: what's missing to build a Checkout Links-style plugin (discounts, server carts, cart attributes, storefront slots) #223

Closed

Description

@NagariaHussain

Why

We looked at whether Checkout Links (Shopify app, $25/month, 2025 Build Award) could be rebuilt as a Commera plugin.

Checkout Links turns an offer into a URL such as yourstore.com/summer-sale. The link loads a cart with chosen items, quantities and discounts, can prefill the shopper's details, and lands the shopper in one of three places:

  • the checkout,
  • a branded popup with upsells, or
  • the store, with the discount active while they browse.

Every order carries the link code, UTMs and tags. Links can also be scheduled, limited (total or once per customer) and passcode-protected, and they come with QR codes and per-link analytics.

About half of it can be built today. The core flow can't, because the plugin system has five gaps. The gaps aren't specific to this app: coupon, loyalty, bundle, upsell and affiliate plugins hit the same ones.

What a plugin can already do

Feature How
Link records, schedule, passcode, draft/active The plugin's own DocType
Short URL on the store domain website_route_rules / www/ in the plugin app
QR codes, clicks, revenue per link Plugin page, plus order_placed / order_paid events
Create a link from a product, customer or order Product, customer and order actions
Show which link an order came from Order card
Total-uses limit Count uses in order_placed
Once per customer validate_cart
Order tags Tag the Sales Order in order_placed
Free shipping delivery_options repriced to 0 (shipping-provider options only)

What's missing

1. Server-side cart creation

The storefront cart lives in the browser, in Alpine.store('cart') saved to localStorage. It only becomes a Quotation at checkout, in generate_quotation_for_cart / save_guest_cart (commera/api/payments.py). Server code therefore can't fill a visitor's cart, and the SDK only has cart.get_cart and cart.set_cart_fields.

Shopify exposes:

  • Cart permalinks: /cart/{variant}:{qty},… with discount, attributes[…], note, ref, and storefront=true to land on the cart instead of checkout.
  • Storefront API cartCreate: lines, attributes, note, discount codes and buyer identity. It returns a checkoutUrl.

Needed:

  • An SDK call such as cart.create_cart(items, attributes=None, buyer=None) that returns a cart token and a checkout URL.
  • A storefront contract (e.g. /cart?load=<token>) that fills Alpine.store('cart') from that cart.

2. A discount hook

The hooks reference says: "No hook changes an item price, gives a discount or sets the cart total directly."

ERPNext coupons don't fill the gap. get_quotation_for_cart calls _remove_coupon_code on every cart rebuild, so a coupon applied by a plugin is wiped.

Shopify exposes: the Discount Function API. It has product, order and shipping discount targets, reads lines, attributes, metafields and buyer identity, and is combined by each discount's combination settings. Free gifts are an added line plus a 100% product discount. Cart Transform handles price overrides and bundles.

Needed: a cart_discounts hook, run on every pricing pass like cart_fees.

  • Input: the cart.
  • Output: line discounts (percentage or amount), order discounts and shipping discounts. The shopper sees each discount's label.
  • Stacking: Commera defines it once in the hook. Shopify functions can't see each other, which is why Checkout Links writes its own "biggest discount wins" logic.
  • Persistence: the discounts carry into the Sales Order. Tax is computed on the discounted amounts.

3. Checkout prefill and a checkout URL

There is no SDK call to set a cart's email, shipping address or delivery option, or to get a URL that opens checkout for that cart. Guest checkout exists (save_guest_cart), but plugins can't call it.

Shopify exposes: the permalink parameters checkout[email] and checkout[shipping_address][first_name|last_name|address1|address2|city|province|country|zip], and cartCreate.buyerIdentity.

Needed: SDK calls to set a cart's buyer (email, phone, shipping address) and delivery option, and cart.get_checkout_url(cart).

4. Cart attributes for guests

A plugin's own Quotation fields already copy to the Sales Order. But set_cart_fields needs an existing cart, and a guest has none until they type an email at checkout. So a link's code and UTMs can't be attached when the shopper lands.

Commera already handles this for its own UTMs: set_attribution_fields (commera/analytics/events.py) reads a cookie at checkout. Plugins have no equivalent, and only source, medium and campaign are captured (no term or content).

Shopify exposes: cart attributes and a cart note, settable from permalinks, the AJAX cart and the Storefront API, and copied onto the order. Permalink ref and source_name set the order source.

Needed:

  • A way for a plugin to set key/value cart attributes for a session, cart or no cart, which Commera stamps onto the Quotation and the Sales Order.
  • Ideally, also an order-source value a plugin can set.

5. Storefront extension points

The storefront templates/layout.html is a standalone template, so a plugin's web_include_js never loads there. Plugins can't put anything on the storefront: no popup, no banner, no discount badge on a cart line, no "spend ₹X more for a free gift" message.

Shopify exposes:

  • Theme app extensions: app embed blocks (a whole-page script or overlay, turned on once in the theme editor) and app blocks (sections the merchant places). Each carries Liquid, JS and CSS assets.
  • App proxies: app pages rendered inside the store's theme.
  • The AJAX cart API: a public JavaScript cart.

Needed:

  • A plugin script/style include in the storefront layout (the "app embed"), with an on/off switch in Settings → Plugins.
  • Named slots plugins can render into, such as cart_line_badge, cart_banner, checkout_banner and page_overlay (the "app blocks").
  • A documented storefront JavaScript cart API (add, update, set attributes, read discounts and events), so plugins don't depend on the internal Alpine.store('cart') shape.
  • A way for a plugin page to render inside the store layout (header, footer, theme), not as a bare page.

Not needed

  • Sales channels / publications: Commera is a single storefront.
  • Subscriptions and B2B catalogs: Commera doesn't have them yet.

Suggested order

  1. Gap 2, the cart_discounts hook. It unblocks the most plugins and is the riskiest because it touches money, so it needs the most review. The cart_fees row handling is the template.
  2. Gaps 1 and 3, server-side cart creation and checkout prefill.
  3. Gap 4, cart attributes for guests.
  4. Gap 5, storefront placements.

With 1–3 done, a Checkout Links-style MVP is doable: links with a fixed or dynamic cart, a discount or free shipping, limits, schedule, passcode, QR codes, attribution and analytics. Item 4 adds the popup and storefront mode.

Rough estimate (not designed yet):

Work Estimate
cart_discounts hook 1–1.5 weeks
Cart creation SDK 3–4 days
Cart attributes for guests 2–3 days
Checkout prefill and storefront placements about 2 weeks
The plugin itself 1.5–2 weeks

🤖 Generated with Claude Code

Activity

  1. NagariaHussain commented on Oct 4, 2026

    @NagariaHussain
    ContributorAuthor

    Checkout Links: admin interface draft

    This is a follow-up to the gap list above: what the Checkout Links admin looks like, what it allows, and where each screen would live in a Commera plugin. It's reconstructed from the public sources below. Only the first two images show the real admin UI. The rest of the layout comes from the help-center text, and the places where I inferred the layout are marked (inferred).

    Sources:

    • Current App Store listing (apps.shopify.com/checkoutlinks): marketing slides of the shopper-side screens.
    • April–December 2024 App Store listings via the Wayback Machine: older shopper-side slides and an editor/analytics collage.
    • An earlier checkoutlinks.com homepage hero, embedded in their QR code blog post: the only full admin screenshot.
    • 88 help-center articles at checkoutlinks.com/help.

    1. Where it lives

    Create link editor with the product picker open

    It's an embedded Shopify admin app with Polaris UI. It is registered as a sales channel, so it sits under Sales channels → Checkout Links with four sub-pages:

    • Links: the home page.
    • Analytics.
    • Settings.
    • Support.

    Save and Discard sit in Shopify's contextual save bar ("Unsaved changes") at the top. LinkShop, a separate link-in-bio app from the same company, appears beside it.

    2. Links page (home)

    • Header buttons: Create link and Create template.
    • Tabs:
      • Built-in tabs that can't be deleted: All, Dynamic, Active, Draft.
      • Templates.
      • Saved custom views, plus a + to add one.
      • A custom view stores the search, the Status and Type filters, and the sort. Custom tabs have a dropdown with Rename view and Delete view.
    • Table columns: Name, Shortcode, Status, Type, Clicks, Orders, Conversion rate, Sales and Updated. Every column is sortable.
      • The Templates tab hides the four performance columns.
    • Search: by name or shortcode. Filters: Status (Active, Draft) and Type (e.g. Dynamic).
    • Row ⋯ actions: Duplicate link, Save as template, and Create link from template (on the Templates tab).
      • Duplicate creates "Copy of …" with a new shortcode and the same status.
      • Templates are starting points only. A link made from one doesn't stay connected to it.

    3. Link editor

    This is a two-column Polaris page with a breadcrumb Links › Create link and a ⋯ menu.

    ┌─ Links › Summer sale ───────────────────────────────────────────── ⋯ ─┐
    │ LEFT (main)                              │ RIGHT (settings cards)     │
    │ ┌ Link ────────────────────────────────┐ │ ┌ Link preview ─── Edit ─┐ │
    │ │ Name  [Summer Sale 20% Off        ]  │ │ │ [web][X][fb][msg] tabs │ │
    │ │ URL   yourstore.com/[summer-sale] ✎ ⧉│ │ │ og image, title, desc  │ │
    │ │ Status (Active|Draft)                │ │ └────────────────────────┘ │
    │ │ Destination (Checkout|Popup|Storefr.)│ │ ┌ QR code ────────────── ▾┐ │
    │ │ Path  [/collections/sale] ✓          │ │ └────────────────────────┘ │
    │ └──────────────────────────────────────┘ │ ┌ Dynamic & B2B ────── ◯ ┐ │
    │ ┌ Cart ────────────────────────────────┐ │ ┌ Attribution ──────── ◯ ┐ │
    │ │ [Search products…] [Browse]          │ │ ┌ Tag orders ───────── ◯ ┐ │
    │ │ ⠿ Aquatic Myth 50ml  ×1   Edit  ✕    │ │ ┌ Schedule ─────────── ◯ ┐ │
    │ │ ⠿ Boot laces         ×2   Edit  ✕    │ │ ┌ Usage limit ──────── ◯ ┐ │
    │ │ ☐ Reorder items (Dynamic only)       │ │ ┌ Passcode / login ─── ◯ ┐ │
    │ └──────────────────────────────────────┘ │ ┌ Scripts ──────────── ◯ ┐ │
    │ ┌ Promotions ─────────────────────── ◯ ┐ │                            │
    │ │ Promotion #1  Conditions: Always     │ │                            │
    │ │   Discount 15% · Free shipping       │ │                            │
    │ │ Promotion #2  Cart qty ≥ 5           │ │                            │
    │ │   Free gift: Mushroom gummies        │ │                            │
    │ │ [+ Add promotion]   Scope ▾          │ │                            │
    │ └──────────────────────────────────────┘ │                            │
    │ ┌ Popup ──────────────────── ◯  Edit → ┐ │                            │
    │ ┌ Storefront banner (storefront only) ┐  │                            │
    └──────────────────────────────────────────┴────────────────────────────┘
    

    The screenshot confirms the right-column order Link preview → QR Code → Dynamic → Attribution → Schedule, with on/off toggles, and that Name, Checkout URL, Cart and Discounts are on the left. The rest of the order is (inferred).

    Order discount card, free-gift lines and A/B analytics (2024 listing)

    What each card allows

    Link

    • Name (internal only).
    • Shortcode: auto-generated and fixed, e.g. FCcJ83.
    • URL: yourstore.com/<slug> with a custom slug, copy-to-clipboard and edit. The slug redirects to /a/link/<shortcode>.
    • Status: Active or Draft.
    • Destination: Checkout, Popup or Storefront. Storefront adds a Path field, checked live against the store (✓ exists / ✗ missing).

    Cart

    • Shopify's product picker, with search and Browse. It shows stock per variant (see the first screenshot).
    • Rows can be dragged to reorder, and each has Edit and remove.
    • Edit sets the variant, quantity, selling plan, a per-unit line discount (% or amount) and line attributes (key/value).
    • Reorder items checkbox (Dynamic links only): prefills the shopper's last order.
    • Inline warnings for products not on the sales channel.

    Promotions

    • Toggle on. Promotions are listed as Promotion frappe.exceptions.DoesNotExistError: Item Group Men not found and IndexError: list index out of range #1, fix: IndexError: list index out of range during install #2 and so on, with a duplicate icon that raises the threshold so tiers are quick to build.
    • Conditions: cart quantity and/or cart total, each "is at least" or "is between". No condition means "Always".
    • Discounts:
      • Percentage or fixed amount, with an optional checkout label.
      • Free shipping.
      • Free gift (a product picker).
      • An existing discount code.
    • Scope (set per link): whole cart, link's items, collection or specific products.
    • Applies to (set per discount): what gets discounted.
    • Per-currency amounts: shown as chips such as EUR 45.
    • Warnings: not on the channel, partially available, more than 500 products, nothing chosen.
    • A read-only list of discounts that were set inside the popup's product blocks.
    • Stacking rules:
      • Discounts don't add up; the biggest matching one wins.
      • Free gifts stack.
      • Free shipping applies if any matching promotion grants it.

    Popup

    • Toggle on, then Edit opens the block editor (§4).

    Storefront banner (Storefront destination only)

    • Message.
    • Style, background, text and border colours, border (including dotted), corner radius, full or contained width, and dismissible.

    Link preview

    • Platform tabs (web, X, Facebook, messages) with a live card.
    • Edit sets the title (60 characters or fewer), description (160 or fewer) and image (1200×630, from Shopify's image picker).

    QR code (collapsible)

    • Style: Square, Rounded, Dots, Classy, Classy rounded or Extra rounded.
    • Dot colour.
    • Download as PNG, SVG or PDF.
    • The code encodes the permanent /a/link/<shortcode>, so changing the slug doesn't break prints.

    Dynamic & B2B

    • Dynamic toggle:
      • Turns on the URL parameters ?li= (lines), ?c= (customer), ?attr= (cart attributes) and ?note=.
      • Adds a Select customer picker that prefills checkout.
      • Adds the Reorder items checkbox to the Cart card.
    • B2B toggle: forces a customer-account sign-in and applies the company's catalog prices and payment terms.

    Attribution

    • UTM source, medium, campaign, term and content, plus a referral field.
    • Add parameter for custom key/values.
    • Values are saved to order metafields cl_utm_*, cl_ref, cl_custom_* and cl_shortcode.

    Tag orders

    • A tag input with chips: Enter, a comma or + adds a tag. Up to 40 characters per tag and 250 tags per link.
    • Tags are added to the order when checkout completes.

    Schedule

    • Start date and time, and an optional end, in the store's timezone.
    • Shoppers see "not yet available" before the start and "no longer available" after the end.

    Usage limit

    • Total uses (blank means once), with a counter such as "42 of 50 used · 8 left" and a Reset count button. When used up, the link is set to Draft.
    • Once per customer, with a Clear customers button. It needs the app's Checkout Rule enabled in Shopify settings.

    Passcode / Require login

    • A 4-digit passcode screen.
    • Or require a customer-account login.

    Scripts

    • Custom HTML/JS injected before </body> on the popup, e.g. GA tags or translations through a MutationObserver.

    4. Popup block editor

    This is a full-screen editor with the block list on the left and a live preview on the right.

    Blocks

    Block Required? Notes
    Logo Yes
    Cart items Yes
    Cart summary Yes
    Checkout button Yes
    Upsells No Hand-picked products, a collection, or Shopify recommendations; can carry discounts; multiple allowed
    Media No Multiple allowed
    Callout No Multiple allowed
    Progress bar No Shows the link's promotion tiers
    Countdown No
    Consent checkbox No
    Skip button No
    Kaching Bundles No

    Editing

    • Add block from a menu.
    • Drag to reorder. Logo and Cart items stay pinned at the top, and Consent sits just before Checkout.
    • Hover a block to show hide and delete controls.

    Templates

    • A header icon opens a wizard with two templates, Influencer gifting and Build a box.
    • A template replaces the blocks and can also set promotions.

    Document settings: colour scheme. Prices show in the shopper's currency through Shopify Markets.

    5. Analytics page

    • Per link: Clicks, Orders, Sales (revenue), Conversion rate.
    • Date presets: today, 7 days and 30 days.
    • A/B comparison (see the second screenshot): "Link A vs Link B" columns for Views, Orders and Revenue, a Conversion rate row, and a Summary line such as "Link A is the better performer with a conversion rate of 68%".
    • UTM data is also queryable in Shopify Reports with ShopifyQL.

    6. Settings page (inferred; no article covers the page itself)

    • Enable the discount function. Promotions need it, though discount codes don't.
    • Shortcuts to turn on the theme app embed and to add the Checkout Rule for per-customer limits.
    • Billing: one $25/month plan.

    7. Entry points outside the app

    • More actions → Create Checkout Link on Product, Products list (with or without products selected), Customer, and Collection pages. Collection pages create a storefront link.
    • More actions → Create Reorder Link on Order and Customer pages.
    • These open a quick-create modal: Name, Link, Destination, Path, Cart and Promotions. It ends on "Your link is created!" with Edit link and Done.
    • POS tiles: Scan Link (applies a link's cart and promotions to the POS cart) and Create Link (turns the POS cart into a link the shopper can pay for on their phone).
    • Theme block: a "Checkout Links Button" with link picker, text, width, colours, padding and radius. It opens the popup as an overlay, or a bottom sheet on mobile.
    • Sidekick:
      • Find a link and describe it.
      • Report 30-day stats and UTM breakdowns.
      • Explain why a link is broken (channel, draft, sold out, expired code, deleted collection).
      • Prefill the editor from a prompt.
    • Shopify Flow: actions and 13 ready templates (reorder at 30 days, VIP gift, win-back, back in stock).

    8. What the shopper sees

    Checkout link Cart page
    Landing page Collection page
    Storefront link Reorder + QR
    Popup: influencer gifting Storefront banner and progress

    Link types at a glance

    9. Where each screen would live in a Commera plugin

    Checkout Links screen Commera placement Blocked by (issue #223)
    Links list, views, templates Plugin page commera/pages/links/index.vue → /commera/plugins/<app>/links n/a
    Link editor (Link, Cart, Promotions, Preview, QR, Dynamic, Attribution, Tags, Schedule, Limits, Passcode) Second plugin page, e.g. pages/link/index.vue?name=…. Placements have no per-record routes such as pages/links/<id>, so the link to edit goes in a query parameter or in-page state. This is another small gap The Promotions card needs gap 2 (discounts) to have an effect. Cart and Dynamic need gaps 1 and 3
    Popup block editor Plugin page Gap 5 to show the popup on the storefront
    Storefront banner settings Card on the editor page Gap 5
    Analytics and A/B comparison Plugin page, fed by order_placed/order_paid and the plugin's own click log n/a
    Settings Settings tab commera/settings/index.vue (a Single DocType form) n/a
    "Create Checkout Link" on product, customer and collection Product and customer actions; Commera has no collection placement n/a
    "Create Reorder Link" on order and customer Order and customer actions n/a
    Which link an order came from Order card n/a
    Search a link by name or code Command palette commera/commands n/a
    POS tiles, Sidekick, Flow No Commera equivalent n/a

    One more gap: per-record plugin pages

    Mapping the editor above turned up a sixth gap, a small one. A plugin page is only pages/<name>/index.vue → /commera/plugins/<app>/<name>; there are no per-record routes like pages/links/<id>. A plugin with a list page and an edit page per record, such as links, coupons or bundles, has to pass the record in a query parameter or keep it in page state. A detail route would fix this, e.g. pages/<name>/[id].vue → /commera/plugins/<app>/<name>/<id>, with the parameter available to the page.

    🤖 Generated with Claude Code

  2. NagariaHussain commented on Oct 7, 2026

    @NagariaHussain
    ContributorAuthor

    What's done, what's left?

  3. NagariaHussain commented on Oct 10, 2026

    @NagariaHussain
    ContributorAuthor

    Status: Buy Links plugin

    The plugin now exists: bwhtech/commera_buy_links. It is built on what Commera has today, and its GAPS.md lists what it cannot do.

    Gap Now
    1. Server-side cart Worked around: the link redirects to the cart permalink, which fills the cart and applies ?discount=
    2. Discount hook Still open. Only an existing Coupon Code works, so no automatic discount, free shipping or free gift per link
    3. Checkout prefill Mostly there through permalink checkout[...] parameters. Phone is missing: #245
    4. Cart attributes for guests Worked around: the plugin sets a cookie at /buy/<code> and tags the Quotation in its validate doc event
    5. Storefront extension points There: commera_storefront_include_js/_css and commera_storefront_blocks. Buy Links' popup uses the include. The on/off switch moves to Settings > Plugins in #244

    New, smaller gaps found while building it:

    🤖 Generated with Claude Code

  4. moved this from Todo to In Progress in Commera Roadmapon Oct 11, 2026
  5. Rl0007 commented on Oct 20, 2026

    @Rl0007
    Collaborator

    @NagariaHussain shall I close this?

  6. moved this from In Progress to Done in Commera Roadmapon Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions