Join our Discord community — connect, share, and grow with us! Join Now

Workflows

Workflows enable users to build their own automations inside their workspaces.
"Workflows" consist of "Nodes," which can be seen as serverless functions that can be connected with each other.

Trigger

Manual (click button)

This is the default option. It means that the workflow only triggers when you manually click on "execute" in the workflow UI.

Cron (AWS cron)

Cron allows you to time your workflow execution very precisely (to the minute). We use the AWS Cron format to ensure a reliable scheduling experience.

Webhook (URL + payload)

OrbitType generates a webhook URL, which serves as your entry point. If a third-party app or your bot from a different client sends a POST request with a JSON body to this endpoint, it will serve as the input JSON for the first node. This input will also be saved in your workspace as an example to simplify testing. You can load it with "Load Input" in the first node’s code editor.

Database (table events and OrbitType API triggers)

As OrbitType is an integrated environment, you can listen for triggers from database tables. We support listening to creation, updates, and deletion events. The SQL request triggering the workflow must be executed using the OrbitType API and include a defined return value. For testing, the input is saved so it can be retrieved later.
If a row is updated, you will receive an array containing two objects as input—one with the old row and one with the new row. You can then use JavaScript within the node to precisely evaluate whether the changes should trigger the flow or whether to return early.

Versions/restore

Each workflow is version-controlled; with every save of a node, the complete workflow is saved. You can revert these versions using the Versions button, which then restores the version. It keeps 50 versions as backups.

Node types

Code Nodes

This is the default node. Inside this node, you can write code and set config values to use within the code.

Decorative Nodes

These are visual elements which help you document and visually organize the workflow plan. They can also provide useful context to Orbitype Intelligence.

Node settings

Entry Node

Each workflow has one specific entry node. It is the first one placed on the node canvas by convention or the one you set manually in the node settings.

Hard Limit

Nodes can run for up to 60 minutes. As you can chain nodes together, this can result in very long-running tasks. To prevent nodes from running too long in situations where they clearly should not exceed a few minutes, you can set a hard limit to cut off the runtime. This is not a graceful process, meaning you will receive no output if the node times out.
This can be very useful when experimenting with AI agents and prompts that are not optimized, which may lead to long-running tasks and wasted credits if no hard limit is set.

Naming

Proper naming of nodes helps you, your coworkers, and Orbitpye Intelligence better understand the purpose of each node. It is important to clearly name each node based on its function.

Node function structure + logging

Orbitype Nodes must use the following syntax, with only one function allowed. Inside this function, you can use normal Node.js. Please keep in mind that this is a serverless environment, so some OS-related functionality will not work.
In Orbitype functions, you never need to require or import anything. Instead, you can provide the allowed libraries and API products via dependency injection into the function and then use them.

async ({input}) => {
  return {output: input}
}


If you want to log information, it is recommended to create a debug array and push messages to it. At the end, you can return it alongside other things. This way, the next node can analyze errors or you can process them further.

Node Output / Next node param

Output einer Node

If you just return something in the function, this will be set as the output.
If you want to have more control and also use features like next, you need to return an object with the key output. Then you can put it in there, and it will become clean, not boxed input for the next node.

async () => {
  const output = Math.random() 
  return {output, 
next: output > 0.5 ? "idOfTheNextNode" : "idOfTheOtherNextNode"}
}

Next Attribute

The next attribute allows you to set the next node dynamically. This works only when two or more nodes are connected with the output handle of the node.

async () => {
  const output = Math.random() 
  return {output, next: output > 0.5 ? "idOfTheNextNode" : "idOfTheOtherNextNode"}
}

Flow types

Linear

This is an easy workflow, moving from a -> b -> c. It's the easiest and most common way.

Branching

If you have a workflow path which, at some point, needs to decide how to proceed with path a.a or path a.b, you can set the next parameter to test this.

Multi Input

Every workflow has only one entry node, but you can also add another node and connect it to the second node if this helps with testing an input payload. Later in the flow, it can also happen that you have multiple inputs. Keep in mind that the payload can vary in the input, as two or more different nodes may fill it. If you don't work with the same format in the previous nodes, then it can lead to errors in the node with the multiple inputs.

Looping

This is great to have conditions within a workflow, but keep in mind, wrong conditions can lead to endless loops, which can again burn tokens. Use looping only in very controlled settings with clear exit rules.

Sub Workflow Architecture

If a workflow consists of clearly separable sub-workflows, it mostly makes sense to build them into their own workflows and trigger them via webhooks. This way, you get a far more modular workspace structure and can use and test sub-parts in isolation.

Orbitype Enviroment Insertions

Orbi Object

This is an object you can import using dependency injection. It includes a lot of information about the current workspace, allowing you to access it programmatically.

async ({ orbi, linkedin }) => {
  // start
  const id = orbi.project.credentials.find((x) => x.name === "linkedin").value
  const self = await linkedin.connect(id)
}

Credentials

This is a more direct way to access credentials. However, it may be deprecated in favor of the Orbi object.

async ({ credentials }) => {
  // 
let apiKey = credentials.myApiKey
}

Agent

orbitype workflow functions have access to an injected agent object. it is a preconfigured wrapper around langchain with two methods: addTool() registers a tool run() runs the agent and returns the final message

example:

const func = async ({agent, zod}) => {
  const { z } = zod
  
  agent.addTool(
    (id) => {
      const url = `https://jsonplaceholder.typicode.com/todos/${id}`
      return fetch(url).then((x) => x.json())
    },
    {
      name: "fetch_todo",
      description: "Fetches a todo by ID from JSONPlaceholder",
      schema: z.string()
    },
  )
  
  agent.addTool(
    (text) => text.trim().split(/\s+/).length,
    {
      name: "count_words",
      description: "Counts the number of words in a given string",
      schema: z.string(),
      returnDirect: true,
    },
  )
  
  const data = await agent.run("number of words in todo with id 3")
  return data
}

Browser

orbitype workflow functions have access to an injected browser object. its run method executes puppeteer code remotely on a different server. the function passed as the first parameter is stringified and sent via http. the second parameter is json serialized and allows injecting variables from outside. finally, the result is returned.

example:

async ({browser}) => {
  const url = "https://vuejs.org/guide/introduction.html"
  const clicks = 3
  const html = await browser.run(
    // puppeteer code
    async ({page, data}) => {
      await page.goto(data.url)
      const button = page.locator(".demo > button")
      for (let i = 0; i < data.clicks; i++)
        await button.click()
      return button.map((x) => x.textContent).wait()
    },
    // injected variables
    {url, clicks}
  )
  return html
}

AI

Use AI Features easely in your nodes:

async ({ai}) => {
  const text = await ai.chat("This is a test")
  // -> "this is an answer..."
  const json = await ai.chat("JSON for red rgb color", true)
  // -> {r: 255, g: 0, b: 0}
  return {text , json}
}

async ({ai}) => {
  return await ai.image("a red apple")
  // -> { base64: "iVBORw..." }
}

async ({ai}) => {
  return await ai.embedding("lorem ipsum")
  // -> [-0.0014445713,-0.021729672, ... ]
}

OCR

orbitype workflow functions have access to an injected ocr object. fileToText() returns the plain text of a file. fileToTable() returns the tabular data extracted from file. fileToForm() returns fields from an PDF Formular

examples:

async ({ocr}) => {
  const url = "https://cdn.orbitype.com/sXbDwXihqsp0/pdfs/sample.pdf"
  const resp = await fetch(url)
  const blob = await resp.blob()
  const file = new File([blob], "dummmy.pdf", {type: "application/pdf"})
  const text = ocr.fileToText(file)
  return text
}

async ({ocr}) => {
  const url = "https://cdn.orbitype.com/sXbDwXihqsp0/pdfs/table.png"
  const resp = await fetch(url)
  const blob = await resp.blob()
  const file = new File([blob], "table.png", {type: "image/png"})
  const table = ocr.fileToTable(file)
  return table
}

async ({ocr}) => {
  const url = "https://cdn.orbitype.com/sXbDwXihqsp0/pdfs/table.png"
  const resp = await fetch(url)
  const blob = await resp.blob()
  const file = new File([blob], "table.png", {type: "image/png"})
  const table = ocr.fileToForm(file)
  return table
}

// tabular data is returned as a 2d array, e.g.
[
  ["name", "hex", "temperature"],
  ["red", "#FF0000", "warm"],
  ["blue", "#0000FF", "cold"],
  ["yellow", "#FFFF00", "warm"],
  ["cyan", "#00FFFF", "cold"]
]

LinkedIn (advanced)

async ({ orbi, linkedin }) => {
  // start
  const id = orbi.project.credentials.find((x) => x.name === "linkedin").value
  const self = await linkedin.connect(id)
  
  // get other profiles
  const other = await linkedin.getProfile("julianvorraro")
  
  // chatting
  await linkedin.startChat("intro message", [other.provider_id])
  // await linkedin.resumeChat(chat_id)
  await linkedin.sendMessage("second meesage")
  const messages = await linkedin.getMessages()
  console.log(linkedin.chat_id, messages.items.map((x) => x.text))
  
  // posting and commenting
  await linkedin.createPost("test post")
  const posts = await linkedin.getPosts(self.provider_id)
  await linkedin.sendComment(posts.items[0].id, "test comment")
}

Libraries

orbitype workflow functions have access to a few injected npm libraries.

  • cheerio

  • imapflow

  • lodash

  • nodemailer

  • pdf-lib (available as PDFLib)

  • pdfkit (available as PDFDocument)

  • unpdf

  • swissqrbill

  • zod

manually importing/requiring additional libraries is NOT SUPPORTED

examples:

const func = async ({lodash}) => {
  await new Promise((res) => setTimeout(res, 100))
  const output = lodash.random(0,9)
  return {output}
}

const func = async ({config, input, nodemailer}) => { 
  const transporter = nodemailer.createTransport({
    host: "smtp.ethereal.email",
    port: 587,
    auth: {
      user: config.user,
      pass: config.pass,
    },
  })
  const mail = {
    from: config.user,
    to: input.email,
    subject: config.subject,
    text: config.text,
  }
  await transporter.sendMail(mail)
  return mail
}

const func = async ({ImapFlow}) => {
  const client = new ImapFlow({
    host: 'smtp.ethereal.email',
    port: 993,
    secure: true,
    auth: {
      user: "nathanial.oreilly@ethereal.email",
      pass: "BnK2wAJtg4P1Efu5zp",
    }
  })
  await client.connect()
  await client.mailboxOpen('INBOX')
  const message = await client.fetchOne('*', { source: true })
  await client.logout()
  return message.source.toString()
}

const func = async ({unpdf}) => {
  const url = 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf'
  const res = await fetch(url)
  const buffer = await res.arrayBuffer()
  const pdf = await unpdf.getDocumentProxy(new Uint8Array(buffer))
  const { text } = await unpdf.extractText(pdf, { mergePages: true })
  return text
}

const func = async ({PDFLib}) => {
  const pdfDoc = await PDFLib.PDFDocument.create()
  const page = pdfDoc.addPage()
  page.drawText('lorem ipsum')
  const base64 = await pdfDoc.saveAsBase64({ dataUri: true })
  return base64
}

const func = async ({SwissQRBill, PDFDocument}) => {
  return new Promise((resolve, reject) => {
    const doc = new PDFDocument({ size: 'A4' });
    const chunks = [];
    doc.on('data', (chunk) => chunks.push(chunk));
    doc.on('end', () => resolve(Buffer.concat(chunks).toString('base64')));
    doc.on('error', reject);

    doc.fontSize(20).text('Invoice #12345', 50, 50);
    doc.fontSize(12).text('Lorem ipsum...', 50, 100);

    const data = {
      amount: 1994.75,
      creditor: {
        account: "CH44 3199 9123 0008 8901 2",
        address: "Musterstrasse",
        buildingNumber: 7,
        city: "Musterstadt",
        country: "CH",
        name: "SwissQRBill",
        zip: 1234
      },
      currency: "CHF",
      debtor: {
        address: "Musterstrasse",
        buildingNumber: 1,
        city: "Musterstadt",
        country: "CH",
        name: "Peter Muster",
        zip: 1234
      },
      reference: "21 00000 00003 13947 14300 09017"
    };

    const qrBill = new SwissQRBill(data);
    qrBill.attachTo(doc);
    doc.end();
  });
}

Learn more

Cookbook - Sections

Modern webpages are often composed of many different sections.

Things like hero banners, testimonials, FAQs or simple text blocks.

Developers implement these as reusable components with props.

But how do we allow non-technical people to use them in Orbitype?

Cancel Account

To ensure you’re comfortable using Orbitype, we want to make sure everything is clear. If you have any questions or encounter issues understanding how something works, please don’t hesitate to reach out. We’re here to assist you—just send us a message at support@orbitype.com, and we’ll be happy to help you with any questions you have.