Server functions let you make HTTP requests from your app, whether it’s to a third-party API, or to
the Attio REST API using ATTIO_API_TOKEN. The App SDK does not allow
client-side fetch requests, so all HTTP requests must go through server functions which run inside
a secure sandbox within the Attio infrastructure. The server function runtime is not Node.js
compatible, meaning some libraries that work in Node.js might not work in our environment.
While they do have some similarities, Attio’s server functions are not to be confused with React Server
Functions. Attio server functions have different capabilities and therefore do not follow the React Server Components protocol.
How to use server functions in your app
To use a server function in your app, follow these steps:
- Create a new
.server.ts file anywhere in your folder structure.
- Create an async function within the file. Implement any data fetching and logic required for your app.
- Expose the function as the default export of the file.
- Import and use the server function from client-side code.
Example: Add a prospect to an external mail sequencing API
This example shows a server function being used to call an external mail sequencing API and add a prospect to a sequence.
First, we create a file with the .server.ts suffix. The file can be placed anywhere in your app, but it must export default a function which returns a promise. This function acts as the entry point for the server function.
add-to-sequence.server.ts
// You likely want to store API keys as a user or workspace connection instead of hardcoding them
const API_KEY = "your-api-key"
export default async function addToSequence({
email,
sequenceId,
mailboxId,
}: {
email: string
sequenceId: number
mailboxId: number
}): Promise<Record<string, string>> {
const response = await fetch(`https://emailsequencingtool.com/api/v1/sequences`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`,
},
body: JSON.stringify({email, sequenceId, mailboxId}),
})
if (!response.ok) {
throw new Error(`Failed to add prospect to sequence: ${await response.text()}`)
}
// In production apps, using a tool like Zod to parse the response offers better type safety and
// error handling than the cast shown here
const body: {data: Record<string, string>} = await response.json()
return body.data
}
Then, this addToSequence() function can be called from your client by simply importing it and calling it with the required arguments.
add-to-sequence-action.ts
import type {App} from "attio"
import {showToast, runQuery} from "attio/client"
import addToSequence from "./add-to-sequence.server"
import getPersonContactDetails from "./get-person-contact-details.graphql"
export const addToSequenceAction: App.Record.Action = {
id: "add-to-sequence",
label: "Add to Sequence",
objects: "people",
onTrigger: async ({recordId}) => {
const {person} = await runQuery(getPersonContactDetails, {recordId})
if (!person) {
showToast({
variant: "error",
title: "Person not found",
})
return
}
const result = await addToSequence({
email: person.email_addresses[0],
sequenceId: 42,
mailboxId: 42,
}).catch((error) => {
console.error(error)
showToast({
variant: "error",
title: "Failed to add person to sequence",
})
})
},
}
As you can see, it’s possible to pass arguments to a server function and access its return value. Each call to a server function sends a request to Attio’s infrastructure, where server function runs in a sandboxed environment. Because the functioncall crosses a network boundary, only JSON-serializable data can be passed between client and server functions.
Whatever you pass to or return from a server function will go through
JSON.stringify(JSON.parse(...)).
As you can see in the example code, if a server function throws an error, it is re-thrown on the client side and can be
handled with try/catch or .catch
promise chaining.
Debugging server functions
To check output of console.log() placed inside the server functions, open the Logs tab for your app in the developer dashboard.
Accessing secrets from server functions
By enforcing the use of server functions, the App SDK keeps secrets hidden from the client and ensures they are managed securely.
In the example above, we used a hardcoded API token. This may be sufficient for testing or simple use cases, but most of the time you’ll want to perform actions on behalf of the Attio user.
For these cases, Attio offers a built-in ‘connections’ system for dynamically managing authentication and storing secrets
Read more about how to use connections in Authenticating to external services.