gadget-server 

The gadget-server package is an auto-generated package of utilities for implementing your Gadget app's backend.

Exports from gadget-server are only available in the backend of your application, and not on the frontend. For reading and writing data from your frontend, see the @gadgetinc/react package.

logger 

An instance of the structured logger for your Gadget app.

JavaScript
1import { logger } from "gadget-server";
2
3// log at the info level
4logger.info("hello world");
5
6// log an error
7logger.error({ error: new Error("Something went wrong") });
1import { logger } from "gadget-server";
2
3// log at the info level
4logger.info("hello world");
5
6// log an error
7logger.error({ error: new Error("Something went wrong") });

Logs are viewable in the Log Viewer within Gadget.

api 

An instance of your Gadget app's API client.

JavaScript
import { api } from "gadget-server";
await api.widget.create({ title: "new widget " });
import { api } from "gadget-server";
await api.widget.create({ title: "new widget " });

This api client has the system-admin role and can perform any action in your Gadget app.

See the API Reference for more information on your app's API and the api client object.

trigger 

An instance of the triggers associated with your Gadget app's action. When an action is executed, it receives a trigger object containing information about the event that initiated the action. This object varies depending on the trigger type and includes a type property that specifies the kind of trigger.

api/models/someModel/actions/someAction.js
JavaScript
export const run: ActionRun = async ({ logger, trigger }) => {
// This logs an object providing details about the trigger
// Output example: { "type": "api", "rootModel": "someModel", "rootAction": "someAction", ... }
logger.info(trigger);
};
export const run: ActionRun = async ({ logger, trigger }) => {
// This logs an object providing details about the trigger
// Output example: { "type": "api", "rootModel": "someModel", "rootAction": "someAction", ... }
logger.info(trigger);
};

For more information on triggers check out our guide here.

connections 

An object with utility functions for each of your app's connections to external systems. This object contains pre-configured clients for supported Gadget connections, such as Shopify, OpenAI, and Sentry.

For example, if you wanted to access your OpenAI connection in a Fastify boot plugin, you could do so like this:

api/boot/setup.js
JavaScript
1import { connections, Server } from "gadget-server";
2
3export default async function (server: Server) {
4 const openAIKey = connections.openai.apiKey;
5 // Do things with the key
6}
1import { connections, Server } from "gadget-server";
2
3export default async function (server: Server) {
4 const openAIKey = connections.openai.apiKey;
5 // Do things with the key
6}

For more information on the available connections for Gadget app check out the guide here.

connections.shopify 

If you have a Shopify connection configured, the connections object offers helpers for getting a Shopify API client to make calls to Shopify. All functions that create Shopify API clients create an instance of the client from the shopify-api-node package, pre-configured with the right credentials retrieved from the database.

For example, you can get a Shopify client instance for a given shop ID using connections.shopify.forShopId

api/routes/GET-test.js
JavaScript
1import { RouteHandler } from "gadget-server";
2
3const route: RouteHandler = async ({ request, reply, connections, logger }) => {
4 const shopify = await connections.shopify.forShopId("12345");
5 logger.info(
6 await shopify.graphql(`
7 query {
8 shop {
9 name
10 }
11 }
12 `)
13 );
14};
15export default route;
1import { RouteHandler } from "gadget-server";
2
3const route: RouteHandler = async ({ request, reply, connections, logger }) => {
4 const shopify = await connections.shopify.forShopId("12345");
5 logger.info(
6 await shopify.graphql(`
7 query {
8 shop {
9 name
10 }
11 }
12 `)
13 );
14};
15export default route;

connections.shopify.forShopId(shopId: bigint | string): Promise<ShopifyClient> 

Returns an instantiated Shopify API client from shopify-api-node ready for calling the given shop with the given shopId. Retrieves the credentials for this shop by by querying the shopifyShop model under the hood, and using the apiKey and accessToken fields to instantiate the client.

api/routes/GET-test.js
JavaScript
1import { RouteHandler } from "gadget-server";
2
3const route: RouteHandler = async ({ request, reply, connections, logger }) => {
4 const shopify = await connections.shopify.forShopId("12345");
5
6 logger.info(
7 await shopify.graphql(`
8 query {
9 shop {
10 name
11 }
12 }
13 `)
14 );
15};
16export default route;
1import { RouteHandler } from "gadget-server";
2
3const route: RouteHandler = async ({ request, reply, connections, logger }) => {
4 const shopify = await connections.shopify.forShopId("12345");
5
6 logger.info(
7 await shopify.graphql(`
8 query {
9 shop {
10 name
11 }
12 }
13 `)
14 );
15};
16export default route;

connections.shopify.forShopDomain(myshopifyDomain: string): Promise<ShopifyClient> 

Returns an instantiated Shopify API client from shopify-api-node ready for calling the shop with given myshopifyDomain. Retrieves the credentials for this shop by by querying the shopifyShop model under the hood, and using the apiKey and accessToken fields to instantiate the client.

api/routes/GET-test.js
JavaScript
1import { RouteHandler } from "gadget-server";
2
3const route: RouteHandler = async ({ request, reply, connections, logger }) => {
4 const shopify = await connections.shopify.forShopDomain("snowdevil.myshopify.com");
5
6 logger.info(
7 await shopify.graphql(`
8 query {
9 shop {
10 name
11 }
12 }
13 `)
14 );
15};
16export default route;
1import { RouteHandler } from "gadget-server";
2
3const route: RouteHandler = async ({ request, reply, connections, logger }) => {
4 const shopify = await connections.shopify.forShopDomain("snowdevil.myshopify.com");
5
6 logger.info(
7 await shopify.graphql(`
8 query {
9 shop {
10 name
11 }
12 }
13 `)
14 );
15};
16export default route;

connections.shopify.current: ShopifyClient | undefined 

Returns an instantiated Shopify API client from shopify-api-node ready for calling the shop calling the current action or route, if there is one. The current shop is derived from the trigger running the current action or route. If the current shop is unknown, connections.shopify.current will be undefined.

For more information on calling your backend from your frontend and preserving authentication information, see the Shopify plugin guides.

api/routes/GET-test.js
JavaScript
1import { RouteHandler } from "gadget-server";
2
3const route: RouteHandler = async ({ request, reply, connections, logger }) => {
4 const shopify = connections.shopify.current;
5
6 if (!shopify) {
7 throw new Error("don't know the current shop for creating a shopify client");
8 }
9
10 logger.info(
11 await shopify.graphql(`
12 query {
13 shop {
14 name
15 }
16 }
17 `)
18 );
19};
20export default route;
1import { RouteHandler } from "gadget-server";
2
3const route: RouteHandler = async ({ request, reply, connections, logger }) => {
4 const shopify = connections.shopify.current;
5
6 if (!shopify) {
7 throw new Error("don't know the current shop for creating a shopify client");
8 }
9
10 logger.info(
11 await shopify.graphql(`
12 query {
13 shop {
14 name
15 }
16 }
17 `)
18 );
19};
20export default route;

connections.shopify.setCurrentShop(shopId: bigint): Promise<void> 

Allows setting the currently authenticated shop for manually overriding Gadget's Shopify authentication.

Warning: This function is generally not used in the course of normal development with Gadget, as Gadget includes robust for automatically authenticating requests from Shopify as belonging to a particular shop. For frontends using the @gadgetinc/react-shopify-app-bridge library, or building Shopify extensions, Gadget passes headers from the frontend to the backend that securely authenticate shops and populate connections.shopify.current if possible. This function should only be used when you are building your own secure authentication mechanism, and you'd like your code to be able to use connections.shopify.current as you might otherwise.

For more information on calling your backend from your frontend and preserving authentication information, see the Shopify plugin guides.

api/routes/GET-test.js
JavaScript
1import { RouteHandler } from "gadget-server";
2
3const route: RouteHandler = async ({ request, reply, connections, logger }) => {
4 // undefined for some reason where Gadget can't authenticate the request as coming from a particular shop
5 logger.info(connections.shopify.current);
6
7 // imperatively set the current shop ID
8 await connections.shopify.setCurrentShop("12345");
9
10 // undefined for some reason where Gadget can't authenticate the request as coming from a particular shop
11 const shopify = connections.shopify.current;
12
13 if (!shopify) {
14 throw new Error("don't know the current shop for creating a shopify client");
15 }
16
17 logger.info(
18 await shopify.graphql(`
19 query {
20 shop {
21 name
22 }
23 }
24 `)
25 );
26};
27export default route;
1import { RouteHandler } from "gadget-server";
2
3const route: RouteHandler = async ({ request, reply, connections, logger }) => {
4 // undefined for some reason where Gadget can't authenticate the request as coming from a particular shop
5 logger.info(connections.shopify.current);
6
7 // imperatively set the current shop ID
8 await connections.shopify.setCurrentShop("12345");
9
10 // undefined for some reason where Gadget can't authenticate the request as coming from a particular shop
11 const shopify = connections.shopify.current;
12
13 if (!shopify) {
14 throw new Error("don't know the current shop for creating a shopify client");
15 }
16
17 logger.info(
18 await shopify.graphql(`
19 query {
20 shop {
21 name
22 }
23 }
24 `)
25 );
26};
27export default route;

connections.shopify.currentShopId: string | undefined 

Returns the id of the currently authenticated shop, if it is set. Returns undefined otherwise.

connections.shopify.currentShopDomain: string | undefined 

Returns the myshopify domain (like snowdevil.myshopify.com) of the currently authenticated shop, if it is set. Returns undefined otherwise.

connections.shopify.currentClientId: string | undefined 

Returns the Client ID of the Shopify App that has authenticated the current request, if it is known. Returns undefined otherwise.

connections.shopify.currentClientSecret: string | undefined 

Returns the Client Secret of the Shopify App that has authenticated the current request, if it is known. Returns undefined otherwise.

connections.shopify.currentSession: { token: string, userId?: string } | undefined 

Returns the contents of the Shopify Session Token passed from the frontend if one was passed. Useful for manually inspecting the session token contents, or accessing the userId for Customer Account extensions that correctly pass a session token.

See the @gadgetinc/shopify-extensions docs for more info on authenticating extensions to your Gadget backend.

connections.bigcommerce 

If you have a BigCommerce connection configured, the connections object offers helpers for getting an API client to make calls to BigCommerce. All functions that create BigCommerce API clients create an instance of the client from the @space48/bigcommerce-api package, pre-configured with the right credentials retrieved from the database.

connections.bigcommerce.current: BigCommerceClient | undefined 

Returns an instantiated BigCommerce API client from @space48/bigcommerce-api package ready for calling the store calling the current action or route, if there is one. The current store is derived from the trigger running the current action or route. If the current store is unknown, connections.bigcommerce.current will be undefined.

For more information on calling your backend from your frontend and preserving authentication information, see the BigCommerce frontend guide.

api/actions/testAction.js
JavaScript
1export const run: ActionRun = async ({ logger, connections }) => {
2 const bigcommerce = connections.bigcommerce.current;
3
4 if (!bigcommerce) {
5 throw new Error(
6 "don't know the current store for creating a bigcommerce client"
7 );
8 }
9
10 logger.info(
11 await bigcommerce.v3.get("/catalog/products", { query: { limit: 1 } }),
12 "read 1 bigcommerce product"
13 );
14};
1export const run: ActionRun = async ({ logger, connections }) => {
2 const bigcommerce = connections.bigcommerce.current;
3
4 if (!bigcommerce) {
5 throw new Error(
6 "don't know the current store for creating a bigcommerce client"
7 );
8 }
9
10 logger.info(
11 await bigcommerce.v3.get("/catalog/products", { query: { limit: 1 } }),
12 "read 1 bigcommerce product"
13 );
14};

connections.bigcommerce.forStoreHash(storeHash: string) : BigCommerceClient | undefined 

Returns an instantiated BigCommerce API client from @space48/bigcommerce-api package ready for calling the given store with the given storeHash. Retrieves the credentials for this store by by querying the bigcommerce/store model under the hood, and using the apiKey and accessToken fields to instantiate the client.

api/actions/testAction.js
JavaScript
1// init BigCommerce API client using forStoreHash
2export const run: ActionRun = async ({ logger, connections }) => {
3 const bigcommerce = await connections.bigcommerce.forStoreHash(
4 "<add-your-store-hash>"
5 );
6
7 logger.info(
8 await bigcommerce.v3.get("/catalog/products", { query: { limit: 1 } }),
9 "read 1 bigcommerce product"
10 );
11};
1// init BigCommerce API client using forStoreHash
2export const run: ActionRun = async ({ logger, connections }) => {
3 const bigcommerce = await connections.bigcommerce.forStoreHash(
4 "<add-your-store-hash>"
5 );
6
7 logger.info(
8 await bigcommerce.v3.get("/catalog/products", { query: { limit: 1 } }),
9 "read 1 bigcommerce product"
10 );
11};

connections.bigcommerce.forStore(store: GadgetRecord<{storeHash: string, accessToken: string}>) : BigCommerceClient | undefined 

Returns an instantiated BigCommerce API client from @space48/bigcommerce-api package ready for calling the given store with the given bigcommerce/store record. Retrieves the credentials for this store by by querying the bigcommerce/store model under the hood, and using the apiKey and accessToken fields to instantiate the client.

forStore is an optimized way to initialize a BigCommerce API client, used when you already have a store record loaded in context and don't want to make another round trip to the database using other client initialization methods.

api/actions/testAction.js
JavaScript
1// in init BigCommerce API client using forStore
2export const run: ActionRun = async ({ logger, connections }) => {
3 // read a store record from Gadget
4 const store = await api.internal.bigcommerce.store.findFirst({
5 filter: { storeHash: { equals: "<your-store-hash>" } },
6 });
7
8 const bigcommerce = await connections.bigcommerce.forStore(store);
9
10 logger.info(
11 await bigcommerce.v3.get("/catalog/products", { query: { limit: 1 } }),
12 "read 1 bigcommerce product"
13 );
14};
1// in init BigCommerce API client using forStore
2export const run: ActionRun = async ({ logger, connections }) => {
3 // read a store record from Gadget
4 const store = await api.internal.bigcommerce.store.findFirst({
5 filter: { storeHash: { equals: "<your-store-hash>" } },
6 });
7
8 const bigcommerce = await connections.bigcommerce.forStore(store);
9
10 logger.info(
11 await bigcommerce.v3.get("/catalog/products", { query: { limit: 1 } }),
12 "read 1 bigcommerce product"
13 );
14};

connections.bigcommerce.setCurrentStore(storeOrStoreHash: string | GadgetRecord<{storeHash: string, accessToken: string}>): Promise<void> 

Allows setting the currently authenticated store for manually overriding Gadget's BigCommerce authentication.

Warning: This function is generally not used in the course of normal development with Gadget, as Gadget includes robust for automatically authenticating requests from BigCommerce as belonging to a particular store. For frontends using the @gadgetinc/react-bigcommerce library, Gadget passes headers from the frontend to the backend that securely authenticate stores and populate connections.bigcommerce.current if possible. This function should only be used when you are building your own secure authentication mechanism, and you'd like your code to be able to use connections.bigcommerce.current as you might otherwise.

For more information on calling your backend from your frontend and preserving authentication information, see the BigCommerce plugin guides.

api/actions/testAction.js
JavaScript
1// in example setting custom authentication using setCurrentStore
2export const run: ActionRun = async ({ logger, connections }) => {
3 // undefined for some reason where Gadget can't authenticate the request as coming from a particular store
4 logger.info(connections.bigcommerce.current);
5
6 // imperatively set the current store ID
7 await connections.bigcommerce.setCurrentStore("store_hash_here");
8
9 // defined now
10 const bigcommerce = connections.bigcommerce.current;
11
12 if (!bigcommerce) {
13 throw new Error(
14 "don't know the current store for creating a BigCommerce client"
15 );
16 }
17
18 logger.info(
19 await bigcommerce.v3.get("/catalog/products", { query: { limit: 1 } }),
20 "read 1 bigcommerce product"
21 );
22};
1// in example setting custom authentication using setCurrentStore
2export const run: ActionRun = async ({ logger, connections }) => {
3 // undefined for some reason where Gadget can't authenticate the request as coming from a particular store
4 logger.info(connections.bigcommerce.current);
5
6 // imperatively set the current store ID
7 await connections.bigcommerce.setCurrentStore("store_hash_here");
8
9 // defined now
10 const bigcommerce = connections.bigcommerce.current;
11
12 if (!bigcommerce) {
13 throw new Error(
14 "don't know the current store for creating a BigCommerce client"
15 );
16 }
17
18 logger.info(
19 await bigcommerce.v3.get("/catalog/products", { query: { limit: 1 } }),
20 "read 1 bigcommerce product"
21 );
22};

connections.bigcommerce.currentClientId : string | undefined 

Returns the Client ID of the BigCommerce app that has authenticated the current request, if it is known. Returns undefined otherwise.

connections.bigcommerce.currentSecret: string | undefined 

Returns the Client Secret of the BigCommerce app that has authenticated the current request, if it is known. Returns undefined otherwise.

connections.bigcommerce.currentStoreId: string | undefined 

Returns the unique Gadget-assigned id of the currently authenticated bigcommerce/store record, if it is set. Returns undefined otherwise.

connections.bigcommerce.currentStoreHash: string | undefined 

Returns the store hash of the currently authenticated BigCommerce store, if it is set. Returns undefined otherwise.

connections.bigcommerce.currentUserId: string | undefined 

Returns the user ID of the currently authenticated BigCommerce user stored in the session model, if it is set. Returns undefined otherwise.

connections.openai 

If you have configured an OpenAI connection with Gadget, connections.openai is an instance of the official OpenAI JavaScript client for making API calls to all of OpenAI's services. When using Gadget's built-in credentials for OpenAI, only some calls are available, but when using your own credentials, all API functionality is available.

See OpenAI's node documentation for more details on the OpenAI JavaScript client.

api/actions/makeOpenAICall.js
JavaScript
1export const run: ActionRun = async ({ connections }) => {
2 const chatCompletion = await connections.openai.chat.completions.create({
3 model: "gpt-4-turbo",
4 messages: [{ role: "user", content: "What are some synonyms for Gadget?" }],
5 });
6
7 return { message: chatCompletion.choices[0].message };
8};
1export const run: ActionRun = async ({ connections }) => {
2 const chatCompletion = await connections.openai.chat.completions.create({
3 model: "gpt-4-turbo",
4 messages: [{ role: "user", content: "What are some synonyms for Gadget?" }],
5 });
6
7 return { message: chatCompletion.choices[0].message };
8};

Model Actions 

save(record) 

Saves the given record to the database.

models/widget/actions/update.js
JavaScript
import { save } from "gadget-server";
export const run: ActionRun = async ({ record }) => {
await save(record);
};
import { save } from "gadget-server";
export const run: ActionRun = async ({ record }) => {
await save(record);
};

If the record is a new record, this will persist the record and assign it an ID. If the record is an existing record, this will update the database with any changed values from the record.

save will validate the record before persisting it. save persists the record to the database using your app's Internal API.

Returns 

Async function, which returns a Promise. Resolves to void. Any encountered validation errors will be thrown.

deleteRecord(record) 

Deletes the given record from the database.

models/widget/actions/delete.js
JavaScript
import { deleteRecord } from "gadget-server";
export const run: ActionRun = async ({ record }) => {
await deleteRecord(record);
};
import { deleteRecord } from "gadget-server";
export const run: ActionRun = async ({ record }) => {
await deleteRecord(record);
};

delete removes the record from your database using your app's Internal API.

Returns 

Async function, which returns a Promise. Resolves to void.

applyParams(params, record) 

Sets incoming parameters onto a record object.

models/widget/actions/update.js
JavaScript
import { applyParams } from "gadget-server";
export const run: ActionRun = async ({ params, record }) => {
applyParams(params, record);
await save(record);
};
import { applyParams } from "gadget-server";
export const run: ActionRun = async ({ params, record }) => {
applyParams(params, record);
await save(record);
};

applyParams will set any incoming parameters onto the record. This is useful for updating a record with the parameters from an action or similar call. applyParams does not persist the record -- it just mutates the in-memory record object.

Parameters 
  • params - The data passed to an action from API calls, webhook events, or direct user inputs, which include the fields of a record.
  • record - Record to apply parameters to.
Returns 

Returns void.

Background actions 

enqueue(action, input, options) 

Enqueues a model or global action to run in the background.

api/actions/enqueueBackgroundAction.js
JavaScript
1export const run: ActionRun = async ({ api }) => {
2 await api.enqueue(
3 api.someModelOrGlobalAction,
4 {},
5 { id: "background-action-1", priority: "HIGH" }
6 );
7};
1export const run: ActionRun = async ({ api }) => {
2 await api.enqueue(
3 api.someModelOrGlobalAction,
4 {},
5 { id: "background-action-1", priority: "HIGH" }
6 );
7};
Parameters 
  • action - Any model or global action.
  • input - Parameters or data to pass to an action.
  • options (optional) - Options for governing how a background action is enqueued:
    • id - A unique identifier to use for the background action. Must be unique among all other background actions within its environment. If not set, a unique ID will be auto-generated and returned.
    • priority - How high to place this background action in its queue, HIGH priority actions will be executed before default priority actions, and those before LOW priority actions. If not set, the default priority will be used.
    • queue - A queue to put this background action in that limits the maximum concurrency of all actions in the queue. If not set, the action will go into the global queue, and won't be concurrency limited. For more info on queue options read the reference here.
    • retries - Configure how many times to retry the action if it fails, and how fast. For more info on the available retry options, see the retries reference.
    • startAt - The time to start running a background action. The time defined must be formatted as an ISO string.
Returns 

Returns void.

queue 

queue allows you to place the background action in a dedicated queue and also enables you to control the maximum concurrency of all actions in the queue.

api/actions/enqueueBackgroundAction.js
JavaScript
1export const run: ActionRun = async ({ api }) => {
2 await api.enqueue(
3 api.someModelOrGlobalAction,
4 {},
5 {
6 queue: {
7 name: "my-queue",
8 maxConcurrency: 4,
9 },
10 }
11 );
12};
1export const run: ActionRun = async ({ api }) => {
2 await api.enqueue(
3 api.someModelOrGlobalAction,
4 {},
5 {
6 queue: {
7 name: "my-queue",
8 maxConcurrency: 4,
9 },
10 }
11 );
12};
  • queue: The name of the queue.
  • maxConcurrency: The maximum concurrency of all actions in the queue. If not set, the default will be set to 1

retries 

retries allows you to configure how fast and how many times to retry the background action if it fails.

retries can be set to an integer to make your background action retry that many times with the default retry schedule, or set to an object to configure exactly how many times and at what rate retries occur.

api/actions/enqueueBackgroundAction.js
JavaScript
1export const run: ActionRun = async ({ api }) => {
2 await api.enqueue(
3 api.someModelOrGlobalAction,
4 {},
5 {
6 retries: {
7 retryCount: 5,
8 maxInterval: 60000,
9 backoffFactor: 2,
10 initialInterval: 1000,
11 randomizeInterval: true,
12 },
13 // OR simply retries: 5
14 }
15 );
16};
1export const run: ActionRun = async ({ api }) => {
2 await api.enqueue(
3 api.someModelOrGlobalAction,
4 {},
5 {
6 retries: {
7 retryCount: 5,
8 maxInterval: 60000,
9 backoffFactor: 2,
10 initialInterval: 1000,
11 randomizeInterval: true,
12 },
13 // OR simply retries: 5
14 }
15 );
16};
  • retryCount: The maximum number of times to retry the operation if it keeps failing. The default is 6.
  • maxInterval: The maximum amount of time to delay a retry while exponentially backing off. The default is not set, so the retry can backoff indefinitely.
  • backoffFactor: The exponential backoff factor to use for calculating the retry delay for successive retries. Set this higher to delay longer. The default is 2.
  • initialInterval: How long to initially delay the first retry, in milliseconds. The default is 1000 ms.
  • randomizeInterval: Randomizes the delays between attempts by multiplying with a factor between 1 to 2. The default is false.

Shopify 

preventCrossShopDataAccess(params, record, options) 

Enforce that the given record is only accessible by the current shop. For multi-tenant Shopify applications, this is key for enforcing data can only be accessed by the shop that owns it.

models/shopifyProduct/actions/update.js
JavaScript
1import { applyParams, save } from "gadget-server";
2import { preventCrossShopDataAccess } from "gadget-server/shopify";
3
4export const run: ActionRun = async ({ params, record, logger, api }) => {
5 applyParams(params, record);
6 await preventCrossShopDataAccess(params, record);
7 await save(record);
8};
1import { applyParams, save } from "gadget-server";
2import { preventCrossShopDataAccess } from "gadget-server/shopify";
3
4export const run: ActionRun = async ({ params, record, logger, api }) => {
5 applyParams(params, record);
6 await preventCrossShopDataAccess(params, record);
7 await save(record);
8};
  • For existing records, this function verifies the record object has the same shopId as the shop in the current session, and throws if not.
  • For new records, this function sets the record's shopId to the current session's shopId.
Parameters 
  • params - Incoming parameters, validated against the current shopId
  • record - Record to validate or set the shopId on
  • options:
    • shopBelongsToField: Picks which belongs to relationship on a model is used for cross-shop validation. Must be the API identifier of a belongs to relationship to the shopifyShop model. If there are multiple relationships to the shopifyShop model on the passed-in record, this is a required parameter
    • customerBelongsToField: Picks which belongs to relationship on a model is used for cross-customer validation. Must be the API identifier of a belongs to relationship to the shopifyCustomer model. If there are multiple relationships to the shopifyCustomer model on the passed-in record, this is a required parameter
    • enforceCustomerTenancy: A boolean field that disables cross-customer validation. Defaults to true
Returns 

Returns void. Throws if the record is not accessible by the current shop.

finishBulkOperation(record) 

Updates the state of a bulkOperation record from Shopify when the operation completes.

models/shopifyBulkOperation/actions/complete.js
JavaScript
1import { applyParams, save } from "gadget-server";
2import {
3 preventCrossShopDataAccess,
4 finishBulkOperation,
5} from "gadget-server/shopify";
6
7export const run: ActionRun = async ({ params, record, logger, api }) => {
8 applyParams(params, record);
9 await preventCrossShopDataAccess(params, record);
10 await finishBulkOperation(record);
11 await save(record);
12};
1import { applyParams, save } from "gadget-server";
2import {
3 preventCrossShopDataAccess,
4 finishBulkOperation,
5} from "gadget-server/shopify";
6
7export const run: ActionRun = async ({ params, record, logger, api }) => {
8 applyParams(params, record);
9 await preventCrossShopDataAccess(params, record);
10 await finishBulkOperation(record);
11 await save(record);
12};
Parameters 
  • record - The bulkOperation record to update

globalShopifySync(params) 

Start running a global Shopify sync for all shops matching the params. This will create a shopifySync record and trigger a sync for each shop.

Parameters 
  • params: The params object for the sync -apiKeys: the list of Shopify APP API keys to trigger a sync for, default: all enabled apps
    • syncSince: the start date to start syncing data from, default: all time
    • models: the list of model api identifiers to trigger syncs for, default: all enabled models
    • force: should Gadget still run actions for records where the updated_at timestamps match
    • startReason: a string reason to store on the created shopifySync records
returns 

Async function. Returns void.

BigCommerce 

BigCommerce functions can be imported from gadget-server/bigcommerce.

preventCrossStoreDataAccess(params, record, options) 

Enforce that the given record is only accessible by the current store. For multi-tenant BigCommerce applications, this is key for enforcing data can only be accessed by the store that owns it.

api/models/bigcommerce/product/actions/update.js
JavaScript
1import { applyParams, save } from "gadget-server";
2import { preventCrossStoreDataAccess } from "gadget-server/bigcommerce";
3
4export const run: ActionRun = async ({ params, record, logger, api }) => {
5 applyParams(params, record);
6 await preventCrossStoreDataAccess(params, record);
7 await save(record);
8};
1import { applyParams, save } from "gadget-server";
2import { preventCrossStoreDataAccess } from "gadget-server/bigcommerce";
3
4export const run: ActionRun = async ({ params, record, logger, api }) => {
5 applyParams(params, record);
6 await preventCrossStoreDataAccess(params, record);
7 await save(record);
8};
  • For existing records, this function verifies the record object has the same storeId as the shop in the current session, otherwise an Error is thrown.
  • For new records, this function sets the record's storeId to the current session's bigcommerceStore.
Parameters 
  • params - Incoming parameters, validated against the current storeId
  • record - Record to validate or set the storeId on
  • options:
    • storeBelongsToField: Picks which belongs to relationship on a model is used for cross-shop validation. Must be the API identifier of a belongs to relationship to the bigcommerce/store model. If there are multiple relationships to the bigcommerce/store model on the passed-in record, this is a required parameter
Returns 

Returns void. Throws if the record is not accessible by the current shop.

OpenAI 

openAIResponseStream 

Converts the result returned when making a request using the OpenAI connection with stream: true into a readable stream that Fastify can respond with.

openAIResponseStream must be imported from gadget-server/ai a sub module of gadget-server

api/actions/getOpenAIStream.js
JavaScript
1import { openAIResponseStream } from "gadget-server/ai";
2
3// Using the openAIResponseStream function to convert an AsyncIterable into a Readable stream
4export const run: ActionRun = async ({ params, connections, logger, api }) => {
5 const stream = await connections.openai.chat.completions.create({
6 model: "gpt-3.5-turbo",
7 messages: [{ role: "user", content: "Hello!" }],
8 stream: true,
9 });
10
11 return openAIResponseStream(stream, {
12 onComplete: (content) => {
13 console.log(content);
14 },
15 });
16};
1import { openAIResponseStream } from "gadget-server/ai";
2
3// Using the openAIResponseStream function to convert an AsyncIterable into a Readable stream
4export const run: ActionRun = async ({ params, connections, logger, api }) => {
5 const stream = await connections.openai.chat.completions.create({
6 model: "gpt-3.5-turbo",
7 messages: [{ role: "user", content: "Hello!" }],
8 stream: true,
9 });
10
11 return openAIResponseStream(stream, {
12 onComplete: (content) => {
13 console.log(content);
14 },
15 });
16};
Parameters 
  • stream - An AsyncIterable containing OpenAI response parts.
returns 

A Readable stream with the transformed content from the input stream.

Vite 

gadget Vite plugin 

gadget() is a Vite plugin that automatically detects the frontend framework type used in the Vite config. The plugin modifies Vite configuration parameters so your app frontend can be run and deployed properly in Gadget.

It is imported from gadget-server/vite.

vite.config.mjs
JavaScript
1import react from "@vitejs/plugin-react-swc";
2import { defineConfig } from "vite";
3import { gadget } from "gadget-server/vite";
4
5export default defineConfig({
6 plugins: [gadget(), react()],
7 clearScreen: false,
8});
1import react from "@vitejs/plugin-react-swc";
2import { defineConfig } from "vite";
3import { gadget } from "gadget-server/vite";
4
5export default defineConfig({
6 plugins: [gadget(), react()],
7 clearScreen: false,
8});

Currently, the plugin detects if your app frontend is using Remix or Vite + React Router.

This plugin is needed in your Vite config starting framework version 1.2.

Injected configuration 

The gadget() plugin injects the following configuration into your Vite config, depending on the frontend framework type detected:

Vite + React Router config 

The following configuration is injected into your Vite config while developing:

gadget() output - Vite + React Router - developing
json
1{
2 "build": {
3 "manifest": true,
4 "outDir": "./.gadget/vite-dist",
5 "emptyOutDir": true
6 }
7}

When building for production, the following configuration is outputted:

gadget() output - Vite + React Router - building production bundle
json
1{
2 "build": {
3 "manifest": true,
4 "outDir": ".gadget/vite-dist",
5 "emptyOutDir": true
6 },
7 "base": "https://app-assets.gadget.dev/a/{applicationId}/{productionEnvironmentId}"
8}

Additional HTML tags are also outputted to help with setting up your app frontend.

While developing, a dev harness is injected into your app frontend so Gadget can show you errors and popups:

dev harness setup
html
1<script type="module">
2 if (false) {
3 false.on("gadget:viteError", (data) => {
4 const event = new CustomEvent("gadget:viteError", {
5 detail: data,
6 });
7 window.dispatchEvent(event);
8 });
9 }
10</script>
11<script async crossorigin src="https://app-assets.gadget.dev/assets/devHarness.min.js"></script>

When connecting to Shopify (AppBridge v4) or BigCommerce, additional scripts are added to your frontend while developing and bundling:

injected HTML scripts for connections
html
1<!-- If the app has a Shopify connection -->
2<!-- SHOPIFY_API_KEY is replaced with the actual API key when we serve the HTML file -->
3<script src="https://cdn.shopify.com/shopifycloud/app-bridge.js" data-api-key="%SHOPIFY_API_KEY%"></script>
4
5<!-- If the app has a BigCommerce connection -->
6<script src="https://cdn.bigcommerce.com/jssdk/bc-sdk.js"></script>
Remix config 

The following configuration is injected into your Vite config while developing:

gadget() output - Remix - developing
json
{
"build": {
"manifest": true
}
}

When building for production, the following configuration is outputted:

gadget() output - Remix - building production bundle
json
1{
2 "build": {
3 "manifest": true
4 },
5 "base": "https://app-assets.gadget.dev/a/{applicationId}/{productionEnvironmentId}/"
6}

Remix 

remixViteOptions 

Gadget provides pre-defined Remix configuration in remixViteOptions to help set up your Vite app to work with Remix.

It is imported from gadget-server/remix.

vite.config.mjs
JavaScript
1import { defineConfig } from "vite";
2import { gadget } from "gadget-server/vite";
3// import the pre-defined config
4import { remixViteOptions } from "gadget-server/remix";
5import { vitePlugin as remix } from "@remix-run/dev";
6
7export default defineConfig({
8 plugins: [
9 gadget(),
10 remix({
11 // pass the config to the remix plugin
12 ...remixViteOptions,
13 }),
14 ],
15});
1import { defineConfig } from "vite";
2import { gadget } from "gadget-server/vite";
3// import the pre-defined config
4import { remixViteOptions } from "gadget-server/remix";
5import { vitePlugin as remix } from "@remix-run/dev";
6
7export default defineConfig({
8 plugins: [
9 gadget(),
10 remix({
11 // pass the config to the remix plugin
12 ...remixViteOptions,
13 }),
14 ],
15});

remixViteOptions contains the following configuration:

remixViteOptions
json
1{
2 "buildDirectory": ".gadget/remix-dist/build",
3 "appDirectory": "web",
4 "future": {
5 "unstable_optimizeDeps": true
6 }
7}

We upload and serve the bundled Remix files inside the .gadget/remix-dist/build directory, so we need to specify the buildDirectory value from Remix.

appDirectory references the directory containing your frontend files, which is web by default.

Check out the Remix documentation for more information about these parameters.

Action Contexts 

Each Action and Global Action is passed a context object as its first and only argument. This context object contains all the inputs necessary for running an action, as well as some utilities.

The context object is usually destructured in the run or onSuccess function arguments with curly braces:

JavaScript
export const run: ActionRun = async ({ api, record, params, ...rest }) => {
// ...
};
export const run: ActionRun = async ({ api, record, params, ...rest }) => {
// ...
};
Properties 
PropertyTypeDescription
apiinstance of your app's clientA connected, authorized instance of the generated API client for the current Gadget application. See the API Reference for more details on this object's interface
paramsRecord<string, any>The incoming data from the API call invoking this action
recordGadgetRecord<Model>The record this action is operating on. Only available in Model Actions, and not available in Global Actions.
sessionSessionA record representing the current user's session, if there is one
configRecord<string, string>An object of all the environment variables created in Gadget's Environment Variables editor
connectionsConnectionsAn object containing client objects for all connections. Read the connections guide to see what each connection provides
loggerLoggerInstanceA logger object suitable for emitting log entries viewable in Gadget's Log Viewer
emailsGadgetMailerA instance of the Gadget wrapper for NodeMailer, used to facilitate the sending of emails.
modelNotYetTypedAn object describing the metadata for the model currently being operated on, like the fields and validations this model applies
currentAppUrlstringThe current URL for your app and environment. e.g. https://my-app--development.gadget.app
triggerAn object containing what event invoked the model's Action to run and trigger the Run Effect.
requestRequestDataAn object describing the incoming HTTP request, if this action was triggered by an HTTP request

Exported Types 

The gadget-server package contains TypeScript types specific to your application, generated from your app's models and fields. This gives great type safety where your actions and routes can import a specific type that describes the required inputs and output.

Actions 

gadget-server exports a type for each Action's context argument, which is the data passed to the run or onSuccess function.

The context type is named <Action><Model>ActionContext, where <action> is the capitalized name of your action, and <model> is the capitalized name of your model. For example, if you have an action named create on a model named `user, you can import the type for that action like this:

JavaScript
import { CreateUserActionContext } from "gadget-server";
import { CreateUserActionContext } from "gadget-server";

Global Actions 

gadget-server exports a type for each Global Action's context argument, which is the data passed to the run or onSuccess function.

The context type is named <Action>GlobalActionContext, where <action> is the capitalized name of your Global Action. For example, if you have a Global Action named flipWidgets, you can import the type for that action like this:

JavaScript
import { FlipWidgetsGlobalActionContext } from "gadget-server";
import { FlipWidgetsGlobalActionContext } from "gadget-server";

Trigger Descriptors 

api trigger 

api triggers describe calls to your Gadget app's GraphQL API, like those made by the JS client or in the GraphQL playground.

An example API trigger
json
1{
2 "type": "api",
3 "mutationName": "updateWidget",
4 "rootModel": "widget",
5 "rootAction": "update",
6 "rawParams": {
7 "id": "123",
8 "widget": {
9 "title": "New Widget Title",
10 "inventoryCount": 10
11 }
12 }
13}
Properties 
  • type: will always be set to "api"
  • mutationName: the string name of the mutation called in the API
  • rootModel: the API identifier of the Model the mutation was called on. Can be different than the root-level model when invoking Nested Actions. Is not set for Global Actions.
  • rootAction: the API identifier of the Action triggered by the mutation. Can be different than the root-level action when invoking Nested Actions.
  • rawParams: the params passed to this API call, including any data for nested actions if passed

scheduler trigger 

scheduler triggers describe actions invoked by the built-in Scheduler within Gadget. No other data is currently passed with this type of trigger.

json
{
"type": "scheduler"
}

shopify_sync trigger 

shopify_sync triggers describe actions run by Gadget's Shopify Sync, including daily syncs and manual syncs.

An example Shopify Sync trigger
json
1{
2 "type": "shopify_sync",
3 "shopId": "123456",
4 "apiVersion": "2023-01",
5 "shopifyScopes": ["read_products", "write_products"],
6 "syncId": "1",
7 "syncSince": null,
8 "models": ["shopifyShop", "shopifyProduct"],
9 "force": false,
10 "startReason": undefined // will be "scheduled" if Action ran via daily sync
11}
Properties 
  • type: will always be set to "shopify_sync"
  • shopId: the identifier of the Shopify shop being synced
  • apiVersion: the version of the Shopify API being used for the sync
  • shopifyScopes: the available OAuth scopes of the Shopify shop being synced
  • syncId: the identifier of the sync record tracking the state of this sync (optional, only available if set)
  • syncSince: the specified date range of this sync (optional, only set if specified when the sync was started)
  • models: the list of model API identifiers that this sync will work on
  • force: indicates if this sync is being run in 'force' mode, which will always run actions even if the 'updated_at' timestamps match between Gadget and Shopify
  • startReason: the string describing the reason why this sync was started (optional, only set if specified when the sync began)

shopify_webhook trigger 

shopify_webhook triggers describe actions that occur in response to Shopify webhooks, such as 'products/update' or 'orders/create'.

json
1// An example Shopify Webhook trigger
2{
3 "type": "shopify_webhook",
4 "topic": "products/update",
5 "payload": {
6 "id": 788032119674292900,
7 "title": "Example T-Shirt",
8 "body_html": "An example T-Shirt",
9 "vendor": "Acme",
10 "product_type": "Shirts",
11 "created_at": null,
12 "handle": "example-t-shirt"
13 // ... etc matching Shopify's format exactly
14 },
15 "shopId": "shop123",
16 "retries": 0
17}
Properties 
  • type: will always be set to "shopify_webhook"
  • topic: the string representing the topic of the incoming webhook from Shopify, like products/update or orders/create
  • payload: the raw incoming payload from Shopify, which includes all the data sent by the webhook unchanged
  • shopId: the identifier for the Shopify store that received the webhook
  • retries: the number of times this webhook has been retried

shopify_oauth trigger 

shopify_oauth triggers describe actions invoked during the installation of an app through the Shopify Partners connection process. No other data is currently passed with this type of trigger.

json
{
"type": "shopify_oauth"
}

shopify_admin trigger 

shopify_admin triggers describe actions invoked during the installation of a Shopify app provisioned in the Shopify Admin. No other data is currently passed with this type of trigger.

json
{
"type": "shopify_admin"
}

RequestData 

The RequestData type describes an incoming HTTP request being processed by an Action.

Note: The RequestData object is passed to Actions and Global Actions, and is a read-only view of the incoming request. This is different than the Request object passed to HTTP Route handlers which has more properties only available in HTTP routes.

Properties 
  • ip: the requesting client's IP (also known as the x-forwarded-for header in other systems)
  • url: the requested URL (usually /api/graphql)
  • method: the HTTP request method, like GET, POST, etc
  • userAgent: the passed user agent string for this request
  • headers: a map of strings to strings or string arrays describing each incoming request header
  • id: a unique identifier assigned to this request by Gadget, used for logging and available in the x-request-id response header
JavaScript
export const run: ActionRun = async ({ api, request, logger, ...rest }) => {
// log the IP of the client making this request
logger.info(request.ip);
};
export const run: ActionRun = async ({ api, request, logger, ...rest }) => {
// log the IP of the client making this request
logger.info(request.ip);
};

RouteHandler 

The RouteHandler type describes the handler exported from a route file.

in a route file
TypeScript
1import type { RouteHandler } from "gadget-server";
2
3const route: RouteHandler<{ Body: { name: string } }> = async ({
4 request,
5 reply,
6}) => {
7 const { name } = request.body;
8
9 await reply.send({ message: `Hello, ${name}!` });
10};
11
12route.options = {
13 schema: {
14 body: {
15 type: "object",
16 properties: {
17 name: { type: "string" },
18 },
19 },
20 },
21};
22
23export default route;

This type is based on the RouteHandlerMethod type from Fastify, with the addition of the options property which can be used to define the request and response schemas for the route. Check out the route configuration guide for more details on the options property.

Inside the route handler generic type, you can specify the expected request body, query parameters, and more.

The generic type accepts a generic object RouteGenericInterface from Fastify containing five named properties: Body, Querystring, Params, Headers and Reply. The interfaces Body, Querystring, Params and Headers will be passed down through the route method into the route method handler request instance and the Reply interface to the reply instance.

Examples:

Route handler with a typed request body
TypeScript
1import type { RouteHandler } from "gadget-server";
2
3const route: RouteHandler<{ Body: { name: string } }> = async ({
4 request,
5 reply,
6}) => {
7 // name is typed as a string
8 const { name } = request.body;
9
10 await reply.send({ message: `Hello, ${name}!` });
11};
12
13export default route;
Route handler with a typed request body and query parameters
TypeScript
1import type { RouteHandler } from "gadget-server";
2
3const route: RouteHandler<{
4 Body: { age: number };
5 Querystring: { name: string };
6}> = async ({ request, reply }) => {
7 const { name } = request.query;
8 const { age } = request.body;
9
10 // ...
11};
12
13export default route;
Route handler with a typed URL parameters
TypeScript
1// api/routes/GET-[id].ts file
2import type { RouteHandler } from "gadget-server";
3
4const route: RouteHandler<{ Params: { id: string } }> = async ({
5 request,
6 reply,
7}) => {
8 const { id } = request.params;
9
10 // ...
11};
12
13export default route;

Check out the HTTP Routes guide for more details on how to define routes with parameters.

You can also add a type to a response payload to ensure that the response body is typed correctly.

Route handler with a typed response
TypeScript
1import type { RouteHandler } from "gadget-server";
2
3const route: RouteHandler<{ Reply: { message: string } }> = async ({
4 request,
5 reply,
6}) => {
7 // reply.send has to be called with an object that matches the Reply type,
8 // or it will have a type error
9 await reply.send({ message: "Hello, world!" });
10};
11
12export default route;

Check out the Fastify documentation for more details on the generic types available.

Route handlers 

Route context 

All route handlers registered in HTTP Routes are passed a context object as their first and only argument:

Context keyDescription
requestthe Request object describing the incoming HTTP request
replythe Reply object for sending an HTTP response
apia connected, authorized instance of the generated API client for the current Gadget application. See the API Reference for more details on this object's interface.
applicationSessiona record representing the current user's session, if there is one.
applicationSessionIDthe ID of the record representing the current user's session, if there is one.
connectionsan object containing client objects for all connections. Read the connections guide to see what each connection provides.
loggera logger object suitable for emitting log entries viewable in Gadget's Log Viewer.
configan object of all the environment variables created in Gadget's Environment Variables editor.
currentAppUrlthe current url for the environment. e.g. https://my-app.gadget.app

request 

The request object passed in the route context describes the incoming HTTP request, with properties for accessing the HTTP request headers, the request body, the matched route, and more. request is powered by Fastify, a high-performance HTTP framework for nodejs.

Request objects have the following fields:

FastifyRequest fieldDescription
querythe parsed query string from the incoming request, its format is specified by the route's querystringParser
bodythe request payload, see Content-Type Parser for details on what request payloads Fastify natively parses and how to support other content types
paramsthe params matching the URL
headersthe headers getter and setter
methodthe HTTP method for the route, like GET, POST, or DELETE
rawthe incoming HTTP request from Node core
idthe request ID
loga logger instance for the incoming request
ipthe IP address of the incoming request
hostnamethe host of the incoming request (derived from X-Forwarded-Host header when the trustProxy option is enabled). For HTTP/2 compatibility it returns :authority if no host header exists.
protocolthe protocol of the incoming request (will always be https on Gadget)
methodthe method of the incoming request
urlthe URL of the incoming request
routerMethodthe method defined for the router that is handling the request
routerPaththe path pattern defined for the router that is handling the request
is404true if the request is being handled by a 404 error handler, false if it is not
socketthe underlying connection of the incoming request
routeSchemathe scheme definition set for the router that is handling the request
routeConfigthe route config object
routeOptionsthe route option object passed when defining the route
bodyLimiteither the server-wide limit or route-specific limit on the size of the request body
urlthe path of the URL to match this route
logLevellog level defined for this route
versiona semver-compatible string that defines the version of the endpoint
exposeHeadRoutecreates a sibling HEAD route for any GET routes
prefixTrailingSlashstring used to determine how to handle passing / as a route with a prefix.

For more details on the Request object, see the Fastify documentation.

reply 

The reply object passed to each route in the context has functions for setting up and sending an HTTP response from your server for your route. The object is a FastifyReply object from Fastify, a high-performance HTTP framework for nodejs.

Reply objects have these functions and properties:

Reply propertyDescription
code(statusCode)sets the status code
status(statusCode)an alias for .code(statusCode)
statusCoderead and set the HTTP status code
header(name, value)sets a response header
headers(object)sets all the keys of the object as response headers
getHeader(name)retrieve the value of an already set header
getHeaders()gets a shallow copy of all current response headers
removeHeader(key)remove the value of a previously set header
hasHeader(name)determine if a header has been set
trailer(key, function)sets a response trailer
hasTrailer(key)determine if a trailer has been set
removeTrailer(key)remove the value of a previously set trailer
type(value)sets the header Content-Type
redirect([code,] dest)redirect to the specified URL with an optional status code. If not provided, the status code defaults to 302
callNotFound()invokes the custom not found handler
serialize(payload)serializes the specified payload using the default JSON serializer or using the custom serializer (if one is set) and returns the serialized payload
serializer(function)sets a custom serializer for the payload
send(payload)sends the payload to the user, could be a plain text, a buffer, JSON, stream, or an Error object
senta boolean value that you can use if you need to know if send has already been called
rawthe http.ServerResponse from Node core
logthe logger instance of the incoming request
requestthe incoming request
contextaccess the request's context property

For more details on the Reply object, see the Fastify documentation.

LoggerInstance 

The LoggerInstance object is a Pino logger instance that can be used to log messages from your Gadget app.

JavaScript
1import { logger } from "gadget-server";
2// log a plain old string message at the info level
3logger.info("Hello world");
4
5// log a structured object and a message at the info level
6logger.info({ key: "value", foo: 42, record: someRecord }, "making progress");
7
8// log an error at the error level
9try {
10 "foo" in null;
11} catch (error) {
12 logger.error({ error });
13}
1import { logger } from "gadget-server";
2// log a plain old string message at the info level
3logger.info("Hello world");
4
5// log a structured object and a message at the info level
6logger.info({ key: "value", foo: 42, record: someRecord }, "making progress");
7
8// log an error at the error level
9try {
10 "foo" in null;
11} catch (error) {
12 logger.error({ error });
13}

LoggerInstance is a high performance alternative to console.log which is optimized for use in production. console.log logs will still appear in your Log Viewer but are not recommended for use in production.

The default log levels (and associated log functions) are trace, debug, info, warn, error, and fatal.

View the Pino logger docs for full API documentation.

Passing structured data 

An object can optionally be supplied as the first parameter to log messages. Each key and value of the object is stringified and written to your application's logs as JSON.

JavaScript
logger.info({ value: { foo: "bar" } });
logger.info({ value: { foo: "bar" } });

Log calls at any level can also pass Error objects to get an easy-to-digest log entry in the Log Viewer for the error, including its stack trace and any other associated details. To log Error objects, pass the object at the error key of the structured data:

JavaScript
const error = new Error("something went wrong");
logger.warn({ error }, "error encountered, continuing");
const error = new Error("something went wrong");
logger.warn({ error }, "error encountered, continuing");

Was this page helpful?