Skip to main content
If you’re building UI extensions, such as app cards, an app home, or app settings page, you can leverage serverless functions to retrieve or write data when an end-user triggers an action from your UI extension. When the serverless function executes, HubSpot runs the function server-side using JavaScript, and mitigates the need to manage your own server. This guide will walk you through how to add an app card with a serverless function that creates a contact when the user clicks a button within your app card.

Prerequisites

Before proceeding with the steps below, confirm that you’re on the latest version of the HubSpot CLI. You’ll need to be using v8.3.0 or above.

Create a new app with a serverless function

To get started, run the command below to create a new project with scaffolding for an app card and support for serverless functions.
hs project create --project-base app --features app-function card --auth static --distribution private
The command above configures a project with static auth. 2026.03 apps with serverless functions do not currently support using OAuth authentication.
Follow the prompts to provide a name and location for your new project. Your new project will have the following structure:
my-project-folder/
└── hsproject.json
└── src
    └── app/
        └── app-hsmeta.json/
        └── cards/
            └── MyCard.jsx
            └── card-hsmeta.json
            └── package.json
        └── functions/
            └── NewFunction.js
            └── private-function-hsmeta.json
            └── package.json

Wire up your app card and serverless function

Next, edit the following files to include the contents of the respective code blocks below:
my-project-folder/
└── hsproject.json
└── src
    └── app/
        └── app-hsmeta.json/
        └── cards/
            └── MyCard.jsx
            └── card-hsmeta.json
            └── package.json
        └── functions/
            └── NewFunction.js
            └── private-function-hsmeta.json
            └── package.json
import React, { useState } from 'react';
import { Button, Input, hubspot } from '@hubspot/ui-extensions';

// Define the extension to be run within HubSpot
hubspot.extend(({ context, actions }) => (
  <CreateContactForm
    context={context}
    addAlert={actions.addAlert}
  />
));

const CreateContactForm = ({ context, addAlert }) => {
  const [email, setEmail] = useState('');
  const [firstname, setFirstname] = useState('');
  const [lastname, setLastname] = useState('');
  const [loading, setLoading] = useState(false);

  const handleSubmit = async () => {
    setLoading(true);

    try {
      const result = await hubspot.serverless('app_function_private', {
        parameters: { email, firstname, lastname }
      });

      if (result.body.success) {
        addAlert({
          title: "Contact successfully created",
          message: `New contact ID: ${result.body.contactId}`,
          type: "success"
        });
      } else {
        addAlert({
          title: "Error creating contact",
          message: result.body.error,
          type: "danger"
        });
      }
    } catch (error) {
      addAlert({
        title: "Error creating contact",
        message: error.message,
        type: "danger"
      });
    } finally {
      setLoading(false);
    }
  };

  return (
    <>
      <Input value={email} onChange={setEmail} placeholder="Email" />
      <Input value={firstname} onChange={setFirstname} placeholder="First Name" />
      <Input value={lastname} onChange={setLastname} placeholder="Last Name" />
      <Button onClick={handleSubmit} disabled={loading}>
        {loading ? 'Creating...' : 'Create Contact'}
      </Button>
    </>
  );
};
After updating the files above, save your changes. Since the example code in NewFunction.js uses the third-party axios dependency, you’ll also need to install the package locally by navigating to the functions/ directory, then running npm install axios. For example, in the terminal, if you’re already in the root directory of your project, you’d run the following two commands:
cd src/app/functions
npm install axios
After axios is installed, run the following command to upload your project to your HubSpot account:
hs project upload

Install your app and locate your access token

With your project uploaded, you can install your app in either your standard HubSpot account, or a developer test account:
If you have the required user permissions, you can install your app directly in your standard account:
  • In your HubSpot account, navigate to Development.
  • In the left sidebar menu, navigate to Projects, click the name of your new project, then click the UID of your app in the component list.
  • On the Distribution tab, under Standard install, click Install now.
Screenshot showing where to initiate installation for a standard HubSpot account
After initiating the install, you’ll be prompted to review the app permissions.
  • Select the checkbox to authorize installing an unverified app, then click Connect app.
  • Once successful, click View installed app details to navigate to the Connected Apps page of the account where you installed your app.
  • Navigate back to Development.
  • In the left sidebar menu, navigate to Projects, click the name of your new project, then click the UID of your app in the component list.
  • If you need to use your app’s access token, you can click the Distribution tab, then click Show under Standard install to reveal your static auth access token. Note that your serverless function will already be able to access a built-in secret called PRIVATE_APP_ACCESS_TOKEN by default, and you don’t need to add it manually as a secret.

Test out your app card

You can now test out the serverless function on a contact record. First, you’ll need to add the app card to the default contact record view:
  • In your HubSpot account, navigate to CRM > Contacts.
  • Click the name of an existing contact.
  • In the middle column of the record, to the right of the existing tabs, click Customize.
Location of customize link on contact record
  • On the Record Customization tab, in the view table, click Default view.
  • In the middle column, under the default tabs, hover over where you want your app card to appear, then click Add card.
Add new card to middle column of contact record
  • In the right panel, click the Card library tab.
  • Search for the name of your app card by the uid specified in your project’s /src/app/cards/card-hsmeta.json file. You can also click the All card types dropdown menu and select Apps to filter by app cards.
  • Locate your app card then click Add card.
Locate and add app card to contact record
  • Click the X in the top right to close the right panel.
  • In the top right, click Save and exit.
You’ll be taken back to the contact record, where your app card will now appear in the location you chose. To test out your card, enter a test email address, first name, and last name, then click Create Contact. After a short delay, you should see a success notification appear at the top of the page.
Testing out 2026.03 serverless function in app card

Local development

As you continue to develop and test your serverless function and app card, you can run hs project dev to help you preview changes and debug errors directly in your terminal. You’ll be prompted to select the account to test on, which you should choose based on where you installed your app in the step above Once your deployed build and dependencies are validated, a new browser tab or window will open to a Local dev panel where you can review the status of each of your app’s components.
Local dev panel with 2026.03 serverless function
  • In the Actions column, you can click Preview next to your app card component to navigate to the contacts index page.
  • You can then navigate to a specific contact and test out your app card, and any console logging statements (e.g., console.log() or console.error()) will be logged to your terminal to help you debug.
If you edit and save any changes to your frontend app card code (e.g., a change to /src/app/cards/MyCard.tsx) while running hs project dev, the changes should automatically be detected and reflected when previewing the changes.
Note that changes to your serverless functions in /src/app/functions will not automatically update your app and you’ll need to run hs project upload to ensure your changes are deployed to the account where you installed your app.

Next steps

Check out the following resources as you develop your app:
Last modified on March 30, 2026