Aangepaste triggers

De ingebouwde triggers dekken de wijzigingen die voor de meeste winkels van belang zijn. Een aangepaste trigger dekt de rest: je kiest een gebeurtenis uit Shopify, schrijft een paar regels JavaScript om te bepalen of deze van belang is en wat er moet worden verzonden, en zo ontstaat een trigger die je kunt gebruiken in Shopify Flow.

Drie zaken die het oplost en die een ingebouwde trigger niet kan:

  • Schiet alleen op jouw voorwaarde. "Alleen wanneer een bestelling van meer dan 500 EUR het label vip krijgt" is één regel code, in plaats van een workflow die bij elke bestelling wordt uitgevoerd en vervolgens filtert.
  • Pas de for-loop toe op een overgang, niet op een toestand. Je code ziet de waarde vóór en na de verandering, dus "de status is veranderd van concept naar actief" of "de voorraad is onder de 5 gedaald" is mogelijk. Een voorwaarde op de huidige waarde kan dat niet uitdrukken.
  • Stuur precies die velden mee die je nodig hebt. Pas de gebeurtenis aan naar de waarden die je workflow daadwerkelijk gebruikt, in plaats van ze één voor één weer op te halen.

Een aangepaste trigger heeft niet eens een Shopify-gebeurtenis nodig: deze kan ook volgens een schema worden uitgevoerd en zelf bepalen wat er is veranderd.

Hoe het werkt

  1. Kies hoe het wordt uitgevoerd: eenmalig Shopify of volgens een schema.
  2. Kies voor een gebeurtenis de gebeurtenis waarop deze reageert - elke Shopify-gebeurtenis die de app al ontvangt - of begin met een sjabloon, waarbij de gebeurtenis automatisch wordt geselecteerd en de code wordt ingevuld.
  3. Leg een echte payload vast. Breng die wijziging aan in je winkel en de app haalt de exacte inhoud van de gebeurtenis op.
  4. Schrijf een transformatie: retourneer een object om de actie uit te voeren, en retourneer null om deze over te slaan.
  5. Test het aan de hand van de vastgelegde gebeurtenis en kijk precies wat je workflow zou ontvangen.
  6. Zet het aan.
De editor voor aangepaste triggers met naam, handle, de keuze tussen een Shopify-gebeurtenis en een schema, de gebeurtenis en een sjabloon
Een nieuwe aangepaste trigger: kies wat ervoor zorgt dat deze wordt geactiveerd, selecteer de gebeurtenis en begin eventueel met een sjabloon.

De transformatie schrijven

Je trigger is een JavaScript-module die een functie met de naam transform exporteert. Deze functie krijgt vier argumenten:

  • payload - de onbewerkte inhoud van de gebeurtenis Shopify, precies zoals deze binnenkomt.
  • topic - welke gebeurtenis is geactiveerd, bijvoorbeeld PRODUCTS_UPDATE.
  • shop - je MyShopify-domein.
  • ctx - ctx.log(...) geeft informatie weer in het logpaneel naast de editor, ctx.shopify(...) voert een Admin GraphQL-query uit en ctx.fetch(...) roept een URL op het openbare internet op.

Wat je teruggeeft, bepaalt wat er gebeurt:

  • Er wordt een object geretourneerd en de trigger wordt geactiveerd, waarbij dat object wordt meegenomen.
  • Geef null terug en het evenement wordt overgeslagen. Zo werkt het filteren - je hoeft geen aparte filtertaal te leren.
  • Laat het veld leeg en de actie wordt bij elke gebeurtenis van dat type geactiveerd.
flow-triggers/high-value-vip-order.jsjavascript
/**
 * Only fire for high-value orders carrying the vip tag.
 */
export async function transform(payload, topic, shop, ctx) {
  const total = parseFloat(payload.total_price || "0");
  const tags = (payload.tags || "").split(",").map(t => t.trim());

  if (total < 500) return null;
  if (!tags.includes("vip")) return null;

  ctx.log("firing for", payload.name, total);

  return {
    orderId: payload.admin_graphql_api_id,
    orderNumber: payload.name,
    total,
    currency: payload.currency,
    customerEmail: payload.email,
  };
}

De waarde vóór de wijziging

Shopify

Bij ‘Productupdate’, ‘Bestelupdate’ en ‘Klantupdate’ geeft payload._changes een overzicht van alle bijgehouden velden die door deze update zijn gewijzigd, met daarbij telkens oldValue en newValue:

  • Producten: title, handle, description, status, vendor, productType, tags
  • Bestellingen: financialStatus, fulfillmentStatus, tags, note, lineItems en customAttributes.<name>
  • Klanten: tags, note, state (ENABLED, DISABLED, INVITED, DECLINED)

De lijst is leeg wanneer de wijziging buiten die velden plaatsvond - bijvoorbeeld een voorraadwijziging - of wanneer de app het record voor het eerst te zien krijgt. Het voorbeeldgebeurtenis dat je in de editor vastlegt, toont het veld, zodat je de werkelijke indeling ervan kunt zien voordat je er gegevens in invoert.

flow-triggers/product-went-live.jsjavascript
/**
 * Fire only when a product BECOMES active - not on every later edit of an
 * active product, which is what a condition on the current status would do.
 */
export async function transform(payload, topic, shop, ctx) {
  const change = (payload._changes || []).find((c) => c.field === "status");
  if (!change) return null;
  if (change.oldValue !== "draft" || change.newValue !== "active") return null;

  return {
    productId: payload.admin_graphql_api_id,
    title: payload.title,
    oldStatus: change.oldValue,
    newStatus: change.newValue,
  };
}

De meer specifieke gebeurtenissen hebben hun eigen oude en nieuwe waarden, dus je hebt daar geen _changes nodig: bij een prijswijziging heb je oldPrice, newPrice en percentChange; bij een voorraadwijziging heb je _oldAvailable, _newAvailable en _delta; bij een wijziging van een metaveld heb je previousValue naast metafield.value.

Begin met een sjabloon

Als in de editor de optie ‘Fires on’ is ingeschakeld, kiest ‘Start from a template’ de gebeurtenis en vult deze met werkende code. Bewerk de constanten bovenaan, test het resultaat en sla het vervolgens op. Elk sjabloon leest een waarde voor en na de wijziging:

  • Een veld van een product, bestelling of klant is gewijzigd van de ene waarde naar de andere - de status is veranderd van ‘concept’ naar ‘actief’, een bestelling is betaald, een account is geactiveerd.
  • De prijs is met meer dan N procent gedaald - een echte prijsverlaging, niet zomaar een prijsaanpassing.
  • De voorraad is onder een drempelwaarde gedaald - dit wordt één keer geregistreerd, op het moment dat de voorraad de drempel overschrijdt, en niet bij elke verkoop terwijl de voorraad al laag is.
  • Het getal van een productmetaveld heeft een drempelwaarde overschreden - een beoordeling is onder de 3 gedaald, een marge is boven de 40 gekomen.
  • Bestelling betaald door een hoogwaardige klant - leest de totale uitgaven van de klant in uw winkel en wordt alleen geactiveerd boven het door u ingestelde bedrag.
De Transform-kaart van de editor met de code van een sjabloon met de tekst payload._changes
Een sjabloon vult werkende code in. Pas de constanten bovenaan aan, test het en sla het vervolgens op.

Extra gegevens ophalen met ctx.shopify

De inhoud van webhooks bevat alleen de velden die de Shopify verstuurt. Als je andere gegevens nodig hebt - zoals het aantal bestellingen van een klant, de voorraad van een variant of een metaveld - voer dan rechtstreeks vanuit je transformatie een query uit op de Admin API:

Enrich the event before decidingjavascript
export async function transform(payload, topic, shop, ctx) {
  const data = await ctx.shopify(`
    query($id: ID!) {
      customer(id: $id) { numberOfOrders tags }
    }
  `, { id: payload.customer.admin_graphql_api_id });

  // Only fire for repeat customers
  if (data.customer.numberOfOrders < 5) return null;

  return {
    orderId: payload.admin_graphql_api_id,
    orderCount: data.customer.numberOfOrders,
  };
}

Het geeft de data van de query terug en genereert een uitzondering als de query fouten bevat, zodat er in je test een foutmelding verschijnt in plaats van dat er stilletjes niets wordt geretourneerd.

Drie limieten die je moet kennen:

  • Maximaal 10 oproepen per uitvoering. Voor verrijking is een handvol nodig; meer dan dat is meestal een lus. Haal wat je nodig hebt in één query, als dat mogelijk is.
  • De app leest alleen**, ze schrijft niet.** Ze maakt gebruik van de rechten die je hebt toegekend, en de app vraagt uitsluitend om leestoegang. Een verzoek om bestelgegevens mislukt tenzij ‘Toegang tot bestelgegevens’ is ingeschakeld op de pagina ‘Rechten’. Als je iets in je winkel wilt wijzigen, doe dat dan via de Flow-acties die op de trigger volgen.
  • De inloggegevens van je winkel komen nooit in je code terecht. De query wordt door de app namens jou uitgevoerd, dus er is geen toegangstoken in de sandbox dat zou kunnen uitlekken.

ctx.fetch(url, options) werkt net als de fetch van de browser voor alles op het openbare internet: de voorraadfeed van een leverancier, een wisselkoers, je eigen API. Interne en privé-netwerkadressen worden geweigerd. Als je triggercode op GitHub is opgeslagen, vermeld daar dan geen API-sleutel in.

Triggers die volgens een schema worden uitgevoerd

Shopify Flow reageert wanneer er iets gebeurt. Het kan niet reageren wanneer er niets gebeurt, en het kan niets buiten je winkel waarnemen. Kies daarvoor de optie ‘Volgens een schema’ onder ‘Wat zorgt ervoor dat dit wordt uitgevoerd’. Achter zo’n trigger zit geen Shopify-gebeurtenis: je code wordt met een bepaalde interval uitgevoerd, van elke 30 seconden tot één keer per dag, en bepaalt zelf wat als een wijziging geldt.

Het is dezelfde functie transform, maar met een andere payload en een andere retourwaarde:

  • payload.state - wat je code ook als state heeft geretourneerd bij de vorige uitvoering. null bij de eerste uitvoering.
  • payload.now en payload.lastRunAt - tijdstempels.
  • Retour{ state, events }eer: de nieuwe status die moet worden onthouden (maximaal 32 KB) en een lijst met gebeurtenissen die moeten worden geactiveerd (maximaal 100 per uitvoering). Elke gebeurtenis activeert Custom Trigger één keer; de resourceId van een gebeurtenis, indien ingesteld, wordt het record-id in Flow. Retournulleer wanneer er niets te melden is.

Zorg ervoor dat de code bij de eerste uitvoering alleen onthoudt, maar nooit iets activeert. Bij de eerste uitvoering heeft je code nog niets om mee te vergelijken, dus alles ziet er nieuw uit. Sla op wat je ziet en geef geen gebeurtenissen terug; door de trigger in te schakelen, worden je workflows nooit overspoeld. Alle sjablonen werken op deze manier.

De editor voor aangepaste triggers in de modus ‘Volgens een schema’, met het interval en het aantal keren dat de trigger in 30 dagen wordt uitgevoerd
Volgens een schema: kies de interval - de editor geeft aan op hoeveel herhalingen dit neerkomt - en begin met een sjabloon voor een schema.
flow-triggers/supplier-stock-changed.jsjavascript
// Fires when a value at a JSON URL changes, with the value before and after.
const URL = "https://api.example.com/stock/1042"

export async function transform(payload, topic, shop, ctx) {
  const res = await ctx.fetch(URL, { headers: { accept: "application/json" } })
  if (!res.ok) throw new Error("The URL answered with HTTP " + res.status)
  const value = String((await res.json()).stock)

  // The first run only remembers the value.
  const previous = payload.state ? payload.state.value : undefined
  if (previous === undefined || previous === value) return { state: { value } }

  return {
    state: { value },
    events: [{ oldValue: previous, newValue: value }],
  }
}

Sjablonen voor geplande triggers, onder ‘Beginnen met een sjabloon’ op de kaart ‘Planning’:

  • **Product, bestelling of klant al N dagen niet bijgewerkt **- verouderde catalogusbeoordelingen, vastgelopen bestellingen, terugwinnen van klanten. Elk record wordt één keer per rustperiode geactiveerd, en items die al achterstallig waren op het moment dat je de functie inschakelt, worden niet allemaal tegelijk geactiveerd.
  • Een waarde buiten Shopify is gewijzigd - een voorraadfeed van een leverancier, een wisselkoers, een prijslijst, met het verschil en het percentage voor getallen.
  • Nieuw item in een RSS- of Atom-feed - nieuws van leveranciers, een productlijstfeed, een statuspagina.
  • Geen bestellingen gedurende N uur - een hartslag voor je winkel. Wordt één keer geactiveerd wanneer er geen bestellingen meer binnenkomen en één keer wanneer ze weer binnenkomen.

Met ‘Test’ wordt je code één keer uitgevoerd zonder iets te activeren en zonder de status op te slaan, zodat je deze zo vaak kunt uitvoeren als je wilt.

Het gebruiken in Shopify Flow

Elke aangepaste trigger wordt in Flow weergegeven als dezelfde trigger: Aangepaste trigger. Voeg deze toe aan een workflow en voeg vervolgens een voorwaarde toe: ‘Trigger-handle is gelijk aan de handle van je trigger’.

Die naam wordt weergegeven op de pagina van de trigger en verandert nooit, zelfs niet als je de trigger een andere naam geeft - zodat je workflow gewoon blijft werken.

Elke sleutel die je transformatie retourneert, wordt een veld in de trigger, en het volledige object is ook beschikbaar als JSON als je het liever zelf wilt parseren.

Bewaar de code op GitHub

Je kunt een GitHub-repository koppelen, zodat je triggercode onder versiebeheer valt. Wijzigingen kunnen vervolgens in een pull-request worden beoordeeld, je kunt zien wie wat heeft gewijzigd en je kunt een transformatie terugdraaien die niet meer werkt.

Koppel het op de pagina ‘Ontwikkelaar’, onder ‘Verbindingen’. Je kiest zelf welke repositories de app kan zien, en je kunt die toegang op elk moment via GitHub intrekken.

Zodra een opslagplaats is geselecteerd, worden alle bestaande triggers onmiddellijk daarheen geschreven, en de synchronisatie verloopt in beide richtingen:

Wat je doet Wat gebeurt er?
Een trigger aanmaken, bewerken of dupliceren in de app Het bestand is opgeslagen in je repository
Een trigger in de app verwijderen Het bestand is uit je repository verwijderd
Een wijziging doorvoeren naar de gekoppelde branch De code van de trigger wordt in de app bijgewerkt

Elke trigger is één bestand dat naar zijn handle is vernoemd en dat precies de module bevat die je in de editor ziet - zonder enige extra omhulling. Dat betekent dat je het in je eigen editor kunt openen, uitvoeren en op fouten controleren, net als elk ander JavaScript-bestand.

Teruggaan naar een eerdere versie

Je hoeft geen kennis van Git te hebben om een wijziging ongedaan te maken. Zodra er verbinding is gemaakt met een repository, verschijnt er in de editor een vervolgkeuzemenu ‘Versie’ met daarin alle eerdere versies van dat bestand, inclusief de datum en de auteur. Kies er een uit en deze wordt in de editor geladen als een niet-opgeslagen wijziging, zodat je deze eerst kunt bekijken - pas als je opslaat, wordt de wijziging daadwerkelijk teruggezet.

Test terwijl je lokaal bewerkt

Als je het bestand in je eigen editor bewerkt, kun je het uitvoeren op basis van het vastgelegde voorbeeldgebeurtenis zonder het eerst in de app op te slaan. Gebruik een API-sleutel met het uitvoeringsniveau van de ontwikkelaarspagina:

Test the file you are editingbash
curl -X POST https://shopify.workflow-trigger-extensions.app/api/v1/triggers/custom/high-value-vip-order/test \
  -H "Authorization: Bearer ftk_your_key_here" \
  -H "Content-Type: application/json" \
  -d "$(jq -Rn --rawfile c flow-triggers/high-value-vip-order.js '{code:$c}')"

Je krijgt hetzelfde resultaat terug als wat de knop ‘Test’ van de app laat zien: of de actie zou worden geactiveerd, het uitvoerobject, je ctx.log-regels en de uitvoertijd.

Dit eindpunt activeert niets en slaat niets op, en het verbruikt geen deel van je abonnementskvote - je kunt het dus veilig uitvoeren bij elke opslag door een bestandsbewaker. (De knop ‘Test uitvoeren’ in de app activeert wel je Flow-workflow, zodat je deze van begin tot eind kunt volgen; dat is ook gratis.)

Je kunt de voorbeeld-payload ook afzonderlijk ophalen met een leessleutel, deze lokaal opslaan en het bestand volledig offline uitvoeren:

Get the captured sample payloadbash
curl https://shopify.workflow-trigger-extensions.app/api/v1/triggers/custom/high-value-vip-order/test \
  -H "Authorization: Bearer ftk_your_key_here"
Wordt mijn datalimiet verbruikt als ik een aangepaste trigger gebruik?

Ja, en het telt elke gebeurtenis die het controleert - niet alleen degene waarop je trigger reageert. Als je trigger luistert naar ‘Product Update’ en je winkel 60.000 productupdates per maand heeft, zijn dat 60.000 gebeurtenissen, zelfs als je code slechts op 100 daarvan reageert.

We ontvangen al deze gebeurtenissen, verwijderen dubbele exemplaren en plaatsen ze in de wachtrij, waarna we je code uitvoeren in een geïsoleerde sandbox - en dat alles voordat je code beslist of deze wordt geactiveerd. Het aantal geeft die werkzaamheden weer.

Met andere woorden: het kost evenveel als de ingebouwde trigger voor dezelfde gebeurtenis zou kosten. Er worden geen extra kosten in rekening gebracht voor het filteren, en het testen is altijd gratis. Een geplande trigger telt één keer per uitvoering.

Is het testen gratis?

Ja. Zowel de knop ‘Run test’ in de app als het API-eindpunt /test vallen hieronder, ook al activeert ‘Run test’ daadwerkelijk je Flow-workflow zodat je kunt zien hoe deze wordt uitgevoerd. Alleen live-uitvoeringen verbruiken je quotum, dus je kunt een transformatie zo vaak aanpassen als je wilt.

Waarom is payload._changes leeg?

Ofwel betrof de wijziging een veld dat niet wordt bijgehouden - bijvoorbeeld een voorraadwijziging of een variantwijziging bij een product - ofwel beschikt de app nog niet over een basiswaarde voor dat record. De basiswaarde wordt opgeslagen op het moment dat de app een record voor het eerst tegenkomt; de allereerste wijziging van een record dat de app nog nooit heeft gezien, bevat dus geen oude waarde. Elke volgende wijziging bevat dat wel.

Kan mijn code gegevens in mijn winkel wijzigen?

Nee. De ctx.shopify draait met de leesrechten die je hebt toegekend, en de app vraagt nooit om schrijftoegang. Dat is bewust zo ontworpen: een trigger die het record bewerkt waarnaar hij luistert, start zichzelf opnieuw, en dat is precies de lus waarvoor de Shopify de workflows uitschakelt. Wijzig de gegevens in de Flow-acties die op de trigger volgen.

Kan ik het handvat later nog verwisselen?

Nee, en dat is bewust zo gedaan. Je Flow-workflow filtert op de handle, dus als je die zou wijzigen, zou de workflow zonder waarschuwing stoppen met draaien. Je kunt de trigger naar believen hernoemen - de handle blijft ongewijzigd.

De handle is tevens de bestandsnaam in je GitHub-repository, dus die verandert ook nooit.

Wat gebeurt er als mijn code een fout bevat?

De gebeurtenis wordt overgeslagen en de fout wordt bij de trigger geregistreerd, zodat je kunt zien wat er mis is gegaan. Een mislukte transformatie blokkeert nooit iets anders - je andere triggers, of ze nu aangepast of ingebouwd zijn, gaan gewoon door zonder dat ze hierdoor worden beïnvloed.

Waar wordt mijn code uitgevoerd?

In een geïsoleerde sandbox, los van de rest van de app, met een korte tijdslimiet en zonder toegang tot de inloggegevens van je winkel. Het krijgt alleen de door jou vastgelegde gebeurtenisgegevens te zien, plus wat je ophaalt via ctx.shopify of ctx.fetch.

Wat gebeurt er als ik het bestand tegelijkertijd in GitHub en in de app bewerk?

Wat je als laatste opslaat, is doorslaggevend. Als je in de app opslaat, wordt dit bovenop het bestand vastgelegd, en als je naar de gekoppelde branch pusht, wordt de code in de app overschreven. Als je voornamelijk in je repository werkt, beschouw de editor van de app dan als alleen-lezen om verrassingen te voorkomen.

Kan een bestand in mijn repository een nieuwe trigger aanmaken?

Nee. Een trigger moet ook weten naar welke gebeurtenis van Shopify hij luistert, en het bestand bevat alleen code - als je de gebeurtenis zou raden, zou de trigger aan de verkeerde gebeurtenis worden gekoppeld. Maak de trigger eerst in de app aan en bewerk daarna het bestand naar believen.

Kan het reageren op gebeurtenissen die de app nog niet ontvangt?

Nee. Een aangepaste trigger reageert op de gebeurtenissen waarop de app al is geabonneerd voor jouw winkel, afhankelijk van de machtigingen die je hebt verleend. Als je de machtiging voor een bron verleent, worden de bijbehorende gebeurtenissen ook beschikbaar voor aangepaste triggers. Voor al het andere kun je het volgens een schema laten uitvoeren.

Volgende stappen