Functions

Function to function invocation

Learn how to invoke one function from another.

Sanity Functions can now invoke other Sanity Functions directly from within your code. Combined with Runtime resource discovery, this lets you chain, fan out, and compose function logic without routing everything back through document change events.

Previously, if one function's work needed to trigger another function, the only way was through document change events: a function would modify a document, that mutation would raise a new change event, and Sanity would invoke the next function in response. This worked, but it meant every step in a chain had to be modeled as a document mutation, even when no document change was actually the point.

Sanity Functions now support invoking a function directly from your code, and exposes the other resources in your Blueprint (functions, CORS origins, datasets, etc.) so you can reference them by name at runtime.

Prerequisites:

  • Complete the Functions quick start, or be comfortable creating and deploying a function.
  • Use the latest version of the sanity CLI (sanity@latest) to interact with Blueprints and Functions as shown in this guide. You can always run the latest CLI commands with npx sanity@latest.

Chaining function invocations

Without invoke: chaining through document events

  • Modifying a document raises a document change event.
  • Sanity invokes your code.
  • Your code modifies the document.
  • Another document change event is raised.
  • Sanity invokes the next function.
  • Repeat for each step in the chain.

Every link in the chain depended on a document mutation to trigger the next one, even for steps that had nothing to do with the document itself, such as posting a Slack message or calling an external API.

With invoke: chaining through invoke

  • Modifying a document raises a document change event.
  • Sanity invokes your code.
  • Your code invokes a Sanity PubSub Function directly.

The intermediate document mutation is no longer required. A function can call the next step in the pipeline as a normal function call.

Why use invoke?

Function composition. Break logic into small, focused functions and call them from one another, instead of duplicating logic across functions.

Fan-out processing. A single function can invoke many others in parallel, distributing heavy workloads across multiple invocations rather than processing everything serially in one function.

Privilege separation. A broadly-permissioned function can hand off sensitive operations to a narrowly-scoped function, rather than holding every permission itself.

Resource isolation. Different workloads can run with their own memory and timeout configurations, invoked dynamically from a coordinating function instead of being forced to share one configuration.

How to invoke a function

Step 1: Create a PubSub function

Create a pubsub function, a Sanity Function you can trigger from other functions.

Step 2: Add it to your Blueprint

Add this new function to your Blueprint.

Step 3: Import invoke

Import invoke from the standard functions library in any function that you want to invoke other functions.

Step 4: Call invoke

Call invoke with the name of the target function, passing along your context and the event payload you want it to receive.

The target function (slack-post in this example) runs as its own function invocation, receiving whatever context and event you pass it - it doesn't need to know it was invoked by another function rather than by a document event.

Understanding invoke's execution modes

invoke takes an optional third parameter, { sync: boolean }. If you omit it, sync defaults to false. This is the existing async behavior, unchanged. If you pass { sync: true }, invoke waits for the target function to finish and gives you back its response.

Async (default): fire-and-forget

invoke resolves as soon as the target function's invocation request is accepted. It does not wait for that function to finish running. If the request itself is rejected (bad name, bad payload, etc.), invoke throws. A resolved async invoke call tells you "the function was triggered," not "the function is done."

❌ Wrong: assuming async invoke waits for a result

Or…

✅ Correct: treat async invoke as fire-and-forget

Or…

Sync: waiting for a response

Passing { sync: true } as the third argument tells invoke to wait for the target function to finish executing, and to return its response.

Use this only when your function's next step genuinely depends on the invoked function's output or on it having definitely finished. For example, a validation function that needs a yes/no answer before deciding whether to proceed. Sync invocation should be the exception, not the default. It ties up your caller's execution (and its timeout/memory budget) for as long as the callee takes to run, and it forces steps to execute one at a time instead of in parallel. This is the opposite of what fan-out and privilege separation are meant to achieve. If you find yourself reaching for { sync: true } in most of your invoke calls, that's usually a sign the logic belongs in one function rather than two.

❌ Wrong: sync invocation as the default habit

Or…

✅ Correct: sync invocation for a genuine dependency

Deciding which mode to use

Async (default / sync: false)

Sync (sync: true)

Waits for completion?

No - only for acceptance

Yes

Returns callee's response?

No

Yes

Best for

Fan-out, chaining steps, privilege separation

Steps that genuinely can't proceed without the callee's result

Use liberally?

Yes, this is the default pattern

No - reserve for cases async can't solve

Example: fan out

In this example our function receives a document mutation event when a blog post goes from draft to published. The act of publishing means we want to blast out this new blog post to all of our socials.

At a high level, the pipeline looks like:

  • Our document change function is invoked when the post becomes published.
  • The function invokes many functions in parallel to post to social media sites.

This mirrors the fan-out pattern above: the document change logic and the social-posting logic stay in separate, independently-configured functions, connected by invoke instead of a document mutation.

Was this page helpful?