The features described in this guide are in Early Access beta, separate from the CRM development tools to build UI extensions with React as frontend beta for private app UI extension development. 

Request access to the UI extensions for public apps beta

Fetching data for public apps (BETA)

While private apps can use serverless functions to fetch data, when you build UI extensions inside public apps, you need to bring your own REST-based back-end and use the hubspot.fetch() API to fetch data. 

Requests made with hubspot.fetch() are subject to the following limits:

  • Each app is allowed up to 20 concurrent requests per account that it's installed in. Additional requests will be rejected with the status code 429, and can be retried after a delay.
  • Each request has a maximum timeout of 15 seconds. Both request and response payloads are limited to 1MB. You can specify a lower timeout per request using the timeout field. Request duration time includes the time required to establish an HTTP connection. HubSpot will automatically retry a request once if there are issues establishing a connection, or if the request fails with a 5XX status code within the 15 second window.

Below, learn more about using the hubspot.fetch() API along with examples.

Method

The method contract for hubspot.fetch() is as follows:
import { hubspot } from '@hubspot/ui-extensions'; interface Options { method?: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'PATCH'; timeout?: number; body?: { [key: string | number]: unknown; } } hubspot.fetch(resource: string | URL): Promise<Response> hubspot.fetch(resource: string, options?: Options): Promise<Response>
Use this table to describe parameters / fields
ParameterTypeDescription
method
String

The method to use.

timeout
Number

Time in milliseconds to allow for the request to complete before timing out. Timeout will occur when the back-end request duration exceeds this value or 15 secondswhichever is smaller. Request duration time includes the time required to establish an HTTP connection.

HubSpot will retry a request once if there are issues establishing a connection, or if the request fails with a 5XX status code within the 15 second window.

body
Object

The request body.

Please note: hubspot.fetch() does not support request and response headers. The HubSpot signature must be used to perform authentication/authorization in your back-end. For security reasons, you should not store secrets in your React code to communicate with third-party back-ends. Instead, use your own back-end to fetch data from other third-party APIs after validating the HubSpot signature on the request.

Specify URLs to fetch from

To enable your extension to request data from a URL, you'll first need to add it to the allowedUrls array in the public-app.json file. URLs not included in this array cannot be requested by the extension.

// Example public-app.json { "name": "My public app", "uid": "my-public-app", "description": "This is my public app.", "logo": "./my-company.png" "allowedUrls": [ "https://app.example.com", "https://app.example2.com:8080", "https://api.example.com/user/", "https://api.example2.com/user" ] ... }

Please note: when running the local development server, any hubspot.fetch() request will still go to your hosted back-end via a HubSpot-managed data fetch service, and the request URL must be on the allowlist in public-app.json. If you need to update the allowlist, you'll need to run hs project upload for the change to take effect. 

Headers and metadata

To ensure that the requests hitting your back-end are coming from HubSpot, several headers are included in the request. You can use these headers, along with the incoming request fields, to verify the signature of the request. Learn more about validating HubSpot requests

HubSpot will always populate headers related to request signing and also allow you to pass an Authorization header from hubspot.fetch(). See the example hubspot.fetch() request with Authorization header for more information.

Please note: while you can use hubspot.fetch() to pass an Authorization request header, hubspot.fetch() does not pass other client-supplied request headers or return response headers set by your back-end server.

HubSpot also automatically adds the following query parameters to each request to supply metadata: userId, portalId, userEmail, and appId. As the request URL is hashed as part of the signature header, this will help you securely retrieve the identity of the user making requests to your back-end.

If you're not seeing appended metadata passed with hubspot.fetch() requests, check whether you have a local.json file that's currently rerouting requests via a proxy. If so, disable this local data fetch feature by renaming the file to local.json.bak and restarting the development server.

Proxying requests to a locally running back-end

If you have a locally running back-end, you can set up a proxy to remap hubspot.fetch() requests made during local development. This proxy is configured through a local.json file in your project, and will prevent requests from being routed through HubSpot's data fetch service.

To proxy requests to a locally running back-end:

  • Create a local.json file in the same directory as your public-app.json file. In this file, define a proxy that remaps requests made using hubspot.fetch(). This mapping will only happen for the locally running extension. You can include multiple proxy key-value pairs in the proxy object.
// Example local.json file { "proxy": { "https://example.com": "http://localhost:8080" } }

Please note: 

  • Each proxy URL must be a valid URL and use HTTPS.
  • Path-based routing is not supported. For example, the following proxy won't work:

    "https://example.com/a": "http://localhost:8080"
  • Upload your project by running hs project upload.
  • With your project uploaded, run hs project dev to start the development server. The CLI should confirm that it has detected your proxy.
    public-app-local-proxy-detected
  • When you request the mapped URL using hubspot.fetch(), the CLI will confirm the remapping.
    public-app-local-proxy-remapping

Request signatures during proxied local development

By default, when proxying requests during local development, requests will not be signed or include metadata in query parameters. However, if you want to introduce request signing into the local development process, you can inject the CLIENT_SECRET environment variable into the local development process.

Injecting CLIENT_SECRET

After setting up your local.json file to proxy specific domains, you can inject the CLIENT_SECRET variable when starting the local development server by prepending the hs project dev command with the variable:

CLIENT_SECRET="abc123" hs project dev

Note that this doesn't have to be a real CLIENT_SECRET value, as long as you inject the same variable in your locally running back-end that you're using for hs project dev. For example, your back-end might include the following Node or Python code:

CLIENT_SECRET="abc123" node my-app.js # and also CLIENT_SECRET="abc123" npm run dev CLIENT_SECRET="abc123" python my-app.py

And to access the CLIENT_SECRET variable:

console.log(process.env.CLIENT_SECRET) import os print(os.environ['CLIENT_SECRET'])

Once you've finalized your request signing logic and have permanently added it to your back-end code, you'll need to inject the CLIENT_SECRET variable from your app into the hs project dev command permanently. For ease of use, consider baking the variable into your start scripts for local development.

To validate HubSpot request signatures in your custom back-end, check out the request validation guide. You can also use HubSpot's @hubspot/api-client npm module to verify requests and sign them yourself. For example:

import { Signature } = from '@hubspot/api-client' const url = `${req.protocol}://${req.header('host')}${req.url}` const method = req.method; const clientSecret = process.env.CLIENT_SECRET const signatureV3 = req.header('X-HubSpot-Signature-v3'); const timestamp = req.header('X-HubSpot-Request-Timestamp'); // Reject the request if the timestamp is older than 5 minutes. if (parseInt(timestamp, 10) < (Date.now() - 5 * 60 * 1000)) { throw Error('Bad request. Timestamp too old.') } const requestBody = req.body === undefined || req.body === null ? '' : req.body; const validV3 = Signature.isValid({ signatureVersion: 'v3', signature: signatureV3, method, clientSecret, requestBody, url, timestamp, }); if (!validV3) { throw Error('Bad request. Invalid signature.') }

hubspot.fetch examples

Below are examples of hubspot.fetch requests to illustrate promise chaining, async/await, and authorization header usage.

Promise chaining

import React, { useEffect } from 'react'; import { hubspot, Text } from '@hubspot/ui-extensions'; hubspot.extend(({context}) => (<Hello context={context}/>)); const Hello = ({ context }) => { useEffect(() => { let url = 'https://run.mocky.io/v3/98c56581'; // replace with your own hubspot .fetch(url, { timeout: 2_000, method: 'GET', }) .then(response => { console.log("Server response:", response.status); response.json() .then(data => console.log(data)) .catch(err => console.error("Failed to parse as json", err)); }) .catch(err => { console.error("Something went wrong", err); }); }, []); return ( <Text>Hello world</Text> ); };

Async/await

import React, { useEffect } from 'react'; import { hubspot, Text } from '@hubspot/ui-extensions'; hubspot.extend(({ context }) => <Hello context={context} />); const Hello = ({ context }) => { useEffect(() => { const fetchData = async () => { let url = 'https://run.mocky.io/v3/98c56581'; // replace with your own const response = await hubspot.fetch(url, { timeout: 2_000, method: 'GET', }); console.log('Server response:', response.status); try { const data = await response.json(); console.log(data); } catch (err) { console.error('Failed to parse as json', err); } }; fetchData().catch((err) => console.error('Something went wrong', err)); }, []); return <Text>Hello world</Text>; };

Authorization header

You may return a short-lived authorization token from your back-end service after validating the HubSpot signature. You can then use this token to access other resources.

To get the access token from your back-end server in the UI extension:

hubspot.fetch(`${BACKEND_ENDPOINT}/get-access-token`, { timeout: 3000, method: 'GET', }) .then((response: Response) => { response.json().then((data) => setAccessToken(data.accessToken)); })

To return a short-lived access token from your back-end server:

app.get('/get-access-token', (req, res) => { validateHubspotSignatureOrThrow(req); res.json({ 'accessToken': generateShortLivedAccessToken(req.query.userEmail), 'expiryTime': addMinutes(currentTime, 10) }); });

To attach access tokens to other UI extension requests:

hubspot.fetch('https://www.oauth-enabled-api.com/', { timeout: 3000, method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}` } }).then((response: Response) => { ... })

Was this article helpful?
This form is used for documentation feedback only. Learn how to get help with HubSpot.