1"use strict";(self.webpackChunkembedded_workflow_builder_docs=self.webpackChunkembedded_workflow_builder_docs||[]).push([["5417"],{61463(e,n,s){s.r(n),s.d(n,{metadata:()=>t,default:()=>p,frontMatter:()=>c,contentTitle:()=>i,toc:()=>d,assets:()=>a});var t=JSON.parse('{"id":"custom-code","title":"Custom Code","description":"Write custom JavaScript code snippets to be used in your Workflows","source":"@site/docs/custom-code.md","sourceDirName":".","slug":"/custom-code","permalink":"/custom-code","draft":false,"unlisted":false,"tags":[],"version":"current","frontMatter":{"title":"Custom Code","description":"Write custom JavaScript code snippets to be used in your Workflows"},"sidebar":"docs","previous":{"title":"HTTP Requests","permalink":"/http-requests"},"next":{"title":"Runner Environment and Limits","permalink":"/runner-environment"}}'),r=s(74848),o=s(28453);let c={title:"Custom Code",description:"Write custom JavaScript code snippets to be used in your Workflows"},i,a={},d=[{value:"Code block structure",id:"code-block-structure",level:2},{value:"Context properties",id:"context-properties",level:2},{value:"Logging",id:"logging",level:2},{value:"Referencing previous steps' results",id:"referencing-previous-steps-results",level:2},{value:"Code step return values",id:"code-step-return-values",level:2},{value:"Making HTTP calls from a code step",id:"making-http-calls-from-a-code-step",level:2},{value:"Adding dependencies to a code step",id:"adding-dependencies-to-a-code-step",level:2},{value:"Requiring built-in Node.js modules",id:"requiring-built-in-nodejs-modules",level:3}];function l(e){let n={a:"a",blockquote:"blockquote",code:"code",em:"em",h2:"h2",h3:"h3",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.a,{href:"/connectors/code",children:"code"})," step allows you to write short custom JavaScript functions within your Workflow.\nThis is helpful if you have some coding experience and want to manipulate data with code."]}),"\n",(0,r.jsx)(n.h2,{id:"code-block-structure",children:"Code block structure"}),"\n",(0,r.jsx)(n.p,{children:"The code component provides a stub function by default.\nLet's examine the structure of the code:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"module.exports = async (context, stepResults) => {\n return { data: null };\n};\n"})}),"\n",(0,r.jsxs)(n.p,{children:["The code component requires you to export an asynchronous function.\nThe default code uses ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions",children:"arrow function notation"})," to create an ",(0,r.jsx)(n.code,{children:"async"})," function to export.\nIf you're used to standard JavaScript function notation, this is the same as:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"async function myFunction(context, stepResults) {\n return { data: null };\n}\n\nmodule.exports = myFunction;\n"})}),"\n",(0,r.jsx)(n.h2,{id:"context-properties",children:"Context properties"}),"\n",(0,r.jsxs)(n.p,{children:["The first parameter of your function (",(0,r.jsx)(n.code,{children:"context"}),") contains metadata about the currently running execution, along with helpful functions for logging:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"logger"})," allows you to write log lines.\nSee ",(0,r.jsx)(n.a,{href:"#logging",children:"logging"})," below."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"stepId"})," is the ID of the current step being executed."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"executionId"})," is the ID of the current execution."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"invokeUrl"})," is the URL used to invoke this Workflow."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"instance"}
1)," is an object containing the ",(0,r.jsx)(n.code,{children:"id"})," and ",(0,r.jsx)(n.code,{children:"name"})," of your Workflow."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"startedAt"})," represents the time at which the execution started, in ISO 8601 format (e.g., ",(0,r.jsx)(n.code,{children:"2023-10-01T12:00:00Z"}),")."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"You can reference any of these context properties using JavaScript dot notation:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"module.exports = async (context, stepResults) => {\n context.logger.info(\n `This step is part of the ${context.instance.name} Workflow. ${context.executionId} started at ${context.startedAt}.`,\n );\n return { data: null };\n};\n"})}),"\n",(0,r.jsxs)(n.p,{children:["To make code more readable, you can ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring",children:"destructure"})," the ",(0,r.jsx)(n.code,{children:"context"})," parameter into its component parts:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"module.exports = async ({ instance, executionId, startedAt }, stepResults) => {\n context.logger.info(\n `This step is part of the ${instance.name} Workflow. ${executionId} started at ${startedAt}.`,\n );\n return { data: null };\n};\n"})}),"\n",(0,r.jsx)(n.h2,{id:"logging",children:"Logging"}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"context.logger"})," object can be used for logging and debugging.\n",(0,r.jsx)(n.code,{children:"context.logger"})," has four functions: ",(0,r.jsx)(n.code,{children:"debug"}),", ",(0,r.jsx)(n.code,{children:"info"}),", ",(0,r.jsx)(n.code,{children:"warn"}),", and ",(0,r.jsx)(n.code,{children:"error"}),".\nFor example:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'module.exports = async (context, stepResults) => {\n context.logger.info("Things are going great");\n context.logger.warn("Now less great...");\n};\n\n// or\n\nmodule.exports = async ({ logger }, stepResults) => {\n logger.info("Hello World");\n};\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Note"}),": Log lines are truncated after 4096 characters."]}),"\n",(0,r.jsx)(n.h2,{id:"referencing-previous-steps-results",children:"Referencing previous steps' results"}),"\n",(0,r.jsxs)(n.p,{children:["The second parameter of your function, ",(0,r.jsx)(n.code,{children:"stepResults"}),", contains the results of all previous steps in your Workflow."]}),"\n",(0,r.jsxs)(n.p,{children:["For example, if you have an ",(0,r.jsx)(n.strong,{children:"HTTP - GET"})," step named ",(0,r.jsx)(n.strong,{children:"Fetch Users List"})," that pulls down an array of users from ",(0,r.jsx)(n.code,{children:"https://jsonplaceholder.typicode.com/users"}),", you can generate an array of email addresses with this code:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"module.exports = async (context, stepResults) => {\n const userArray = stepResults.fetchUsersList.results;\n const emailArray = userArray.map((user) => user.email);\n return { data: emailArray };\n};\n"})}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Tip: Step results are usually objects"})}),"\n",(0,r.jsxs)(n.p,{children:["Many components return objects that have multiple nested keys.\nSo, you can reference ",(0,r.jsx)(n.code,{children:"stepResults.myStepName.results.someKey"}),".\nIt's rare for a component to return serialized JSON, so there's rarely a need to ",(0,r.jsx)(n.code,{children:"JSON.parse()"})," results from a previous step."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Step names are ",(0,r.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/Camel_case",children:"camelCased"}),", which means spaces and other special characters are removed from the step name, and all words except the first are capitalized.\nHere are a few examples of step names and their corresponding step result reference:"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Step name"}),(0,r.jsx)(n.th,{children:"Code reference"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"My Step Name"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"stepResults.myStepName.results"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"my step name"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"stepResults.myStepName.results"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"HTTP - GET"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"stepResults.httpGet.results"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"Fetch \u{1F680} rocket launches"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"stepResults.fetchRocketLaunches.results"})})]})]})]}),"\n",(0,r.jsx)(n.h2,{id:"code-step-return-values",children:"Code step return values"}),"\n",(0,r.jsx)(n.p,{children:"The code component can optionally return a value for use by a subsequent step.\nThe return value can be an object, string, integer, etc., and will retain its type as the value is passed to the next step."}),"\n",(0,r.jsxs)(n.p,{children:["The return value is specified using the ",(0,r.jsx)(n.code,{children:"data"})," key in the return object."]}),"\n",(0,r.jsxs)(n.p,{children:["In this example, suppose a previous step ",(0,r.jsx)(n.code,{children:"Get Users"})," returned an array of user objects.\nThis step will return an array of the users' names, in all caps:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"module.exports = async (context, stepResults) => {\n const users = stepResults.getUsers.results;\n const usersNames = users.map((user) => user.name.toUpperCase());\n return { data: usersNames };\n};\n"})}),"\n",(0,r.jsxs)(n.p,{children:["The code step's result can be used as input for the next step by referencing ",(0,r.jsx)(n.code,{children:"theCodeStepsName.results"}),"."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.img,{alt:"Code step result",src:s(89898).A+"",width:"800",height:"341"})}
1),"\n",(0,r.jsx)(n.h2,{id:"making-http-calls-from-a-code-step",children:"Making HTTP calls from a code step"}),"\n",(0,r.jsxs)(n.p,{children:["The Node.js ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/API/fetch",children:"fetch"})," API is built into the code component.\nTo make an HTTP call to an API, you can use the ",(0,r.jsx)(n.code,{children:"fetch"})," function:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",metastring:'title="Make an HTTP POST request from a code step"',children:'module.exports = async (context, stepResults) => {\n const response = await fetch("https://postman-echo.com/post", {\n method: "POST",\n headers: {\n Accept: "application/json",\n "Content-Type": "application/json",\n Authorization: "Bearer abc-123",\n },\n body: JSON.stringify({ foo: "bar", baz: 123 }),\n });\n const parsedData = await response.json();\n return { data: parsedData };\n};\n'})}),"\n",(0,r.jsx)(n.h2,{id:"adding-dependencies-to-a-code-step",children:"Adding dependencies to a code step"}),"\n",(0,r.jsxs)(n.p,{children:["If your code component depends on node modules from ",(0,r.jsx)(n.code,{children:"npm"}),", dependencies will be dynamically imported from the ",(0,r.jsx)(n.a,{href:"https://unpkg.com/",children:"UNPKG"})," and ",(0,r.jsx)(n.a,{href:"https://www.jsdelivr.com/",children:"jsDelivr"})," CDNs.\nFor example, if your code component reads:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",metastring:'title="Import lodash as a dependency"',children:'const lodash = require("[email protected]/lodash.js");\n\nmodule.exports = async (context, stepResults) => {\n const mergedData = lodash.merge(\n { cpp: "12" },\n { cpp: "23" },\n { java: "23" },\n { python: "35" },\n );\n return { data: mergedData };\n};\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Then ",(0,r.jsx)(n.a,{href:"https://unpkg.com/browse/[email protected]/lodash.js",children:"lodash"})," version 4.17.21 will be imported as a dependency."]}),"\n",(0,r.jsxs)(n.p,{children:["You should specify specific known working versions of ",(0,r.jsx)(n.code,{children:"npm"})," packages for your code component:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const lodash = require("[email protected]");\nconst { PDFDocument } = require("[email protected]/dist/pdf-lib.js");\n'})}),"\n",(0,r.jsxs)(n.p,{children:["You can require any file from ",(0,r.jsx)(n.code,{children:"npm"})," using ",(0,r.jsx)(n.code,{children:"package[@version][/file]"})," syntax.\nNote that with the ",(0,r.jsx)(n.code,{children:"lodash"})," import above, no file was specified.\nIf no file is specified, the ",(0,r.jsx)(n.code,{children:"main"})," file defined in the ",(0,r.jsx)(n.code,{children:"npm"})," package's ",(0,r.jsx)(n.code,{children:"package.json"})," is imported.\nAn explicit path was called out for the ",(0,r.jsx)(n.code,{children:"pdf-lib"})," import because the ",(0,r.jsx)(n.code,{children:"pdf-lib"})," package defaults to importing an index file that itself requires other files, and ",(0,r.jsx)(n.code,{children:"dist/pdf-lib.js"})," is a completely independent file that can be imported on its own."]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Warning: Downstream dependencies"})}),"\n",(0,r.jsx)(n.p,{children:"In order for an external dependency to be compatible with a code step, all JavaScript code must be compiled into a single file."}),"\n",(0,r.jsxs)(n.p,{children:["For example, ",(0,r.jsx)(n.a,{href:"https://unpkg.com/[email protected]/lodash.js",children:"https://unpkg.com/[email protected]/lodash.js"})," contains all of the code necessary to run in a single file.\n",(0,r.jsx)(n.a,{href:"https://app.unpkg.com/[email protected]/files/flatten.js",children:"https://app.unpkg.com/[email protected]/files/flatten.js"})," does not - it has its own ",(0,r.jsx)(n.code,{children:"require()"})," statement and depends on other files.\nThe former would work in the code step, the latter would not."]}),"\n",(0,r.jsxs)(n.p,{children:["If the external package has its own dependencies that are not compiled in, or if the file that you loaded has its own ",(0,r.jsx)(n.code,{children:"require()"})," statements, you will see errors."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"requiring-built-in-nodejs-modules",children:"Requiring built-in Node.js modules"}),"\n",(0,r.jsxs)(n.p,{children:["You can also require built-in Node.js modules, like ",(0,r.jsx)(n.code,{children:"crypto"})," or ",(0,r.jsx)(n.code,{children:"path"}),".\nIf the library you specify is built in to Node.js, the client will ",(0,r.jsx)(n.em,{children:"not"})," reach out to a CDN, and will instead use the built-in module."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const crypto = require("crypto");\n\nmodule.exports = async () => {\n const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {\n modulusLength: 4096,\n publicKeyEncoding: {\n type: "spki",\n format: "pem",\n },\n privateKeyEncoding: {\n type: "pkcs8",\n format: "pem",\n cipher: "ae
1s-256-cbc",\n passphrase: "top secret",\n },\n });\n\n return {\n data: {\n publicKey,\n privateKey,\n },\n };\n};\n'})})]})}function p(e={}){let{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},89898(e,n,s){s.d(n,{A:()=>t});let t=s.p+"assets/images/code-step-result-fd00b1c7febb5bcda7211cbb3c6cef6a.png"},28453(e,n,s){s.d(n,{R:()=>c,x:()=>i});var t=s(96540);let r={},o=t.createContext(r);function c(e){let n=t.useContext(o);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function i(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:c(e.components),t.createElement(o.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.