Rate this page:

Functions FAQ

This Twilio product is currently available as a Beta release. Some features are not yet implemented and others may be changed before the product is declared as Generally Available. Beta products are not covered by a Twilio SLA. Learn more about beta product support.

Why does my Function return 'runtime application timed out'?

There are two likely reasons your Function has completed with the error: 'runtime application timed out'.

The most common reason is that your Function has exceeded the 10-second execution time limit. You can determine this by looking at the execution logs on the Function instance page. The last log line after execution will tell you how many milliseconds the Function took to execute. If the processing time is greater than 10,000 milliseconds, then Twilio terminated your Function.

Function timeout in logs

The other, more subtle reason your Function ended with an application timeout is because of an incorrect invocation of callback(). If your Function does not call the callback() method or the method is unreachable, your Function will continue executing until it reaches the time limit and ultimately fails. A very common mistake is to forget to capture the catch() rejected state of a Promise and calling callback() there as well. The Function Execution documentation provides extensive details on the functionality and usage of the callback() method. Below are several examples of correct use of callback() to complete execution and emit a response.

        Example of how to appropriately use callback() with an asynchronous HTTP request

        Complete Execution with Asynchronous HTTP Request

        Example of how to appropriately use callback() with an asynchronous HTTP request
              Example of how to return an empty HTTP 200 OK

              Return a Simple Successful Response

              Example of how to return an empty HTTP 200 OK

              Why isn't my code running?

              The most common reason we have seen that a Function appears not to run is the misuse of callback. Your Function invocation terminates when it calls callback. If your request is asynchronous, for example, an API call to a Twilio resource, then you must call callback after the success response of the request has resolved. This would be in the then/catch blocks chained to a request, or after a request that uses the await keyword in an async context.

              Can API Calls occur after a Function Execution has ended?

              Yes! Outgoing API requests from inside a Function (either directly via axios/got, or through an SDK such as Twilio's) can still be in an enqueued state even after the execution of a Function has ended.

              This happens if you make an asynchronous request that returns a Promise, and forget to either await it or chain on a .then handler. Here, the Function may finish execution before the request occurs or completes. Then, your request may be deferred, sit idle, and run on a subsequent Function invocation in your Service before our system cleans it up.

              How do I keep this from happening?

              Always be sure to wait for the Promises generated by your API calls and methods to resolve before invoking callback. Below are examples of improperly and properly handled asynchronous logic, one which uses .then chaining, and another using async/await syntax.

              // An example of an improperly handled Promise
              exports.handler = (context, event, callback) => {
                const client = context.getTwilioClient();
                  body: "hi",
                  to: toNumber,
                  from: fromNumber,
                return callback(null, 'success');
              Using .then chaining
              // An example of properly waiting for message creation to
              // finish before ending Function execution
              exports.handler = (context, event, callback) => {
                const client = context.getTwilioClient();
                    body: 'hi',
                    to: event.toNumber,
                    from: event.fromNumber,
                  .then((message) => {
                    return callback(null, `Success! Message SID: ${message.sid}`);
                  // Ideally, you would also have some .catch logic to handle errors
              Using async/await
              // An example of properly waiting for message creation to
              // finish before ending Function execution
              exports.handler = async (context, event, callback) => {
                const client = context.getTwilioClient();
                const message = await client.messages.create({
                  body: 'hi',
                  to: event.toNumber,
                  from: event.fromNumber,
                return callback(null, `Success! Message SID: ${message.sid}`);
                // Ideally, you would add some try/catch logic to handle errors

              How do I construct Voice TwiML?

              You can generate Voice TwiML using the Twilio Node library, which comes packaged within your Function.

                    Example of how to construct a Voice TwiML Response

                    Construct Voice TwiML

                    Example of how to construct a Voice TwiML Response

                    How do I construct Messaging TwiML?

                    You can generate Messaging TwiML using the Twilio Node library, which comes packaged within your Function.

                          Example of how to construct Messaging TwiML

                          Construct Messaging TwiML

                          Example of how to construct Messaging TwiML

                          How do I return JSON?

                          Simply return your object as outlined in the Function Execution documentation or the following sample code:

                                Example of how to return JSON in HTTP 200 OK

                                Return a Successful JSON Response

                                Example of how to return JSON in HTTP 200 OK

                                Can I interact with the OPTIONS request?

                                No, you cannot interact with the pre-flight OPTIONS request that browsers send.

                                The Runtime client will automatically respond to OPTIONS requests with the following values:

                                Access-Control-Allow-Origin: *
                                Access-Control-Allow-Headers: *
                                Access-Control-Allow-Methods: GET, POST, OPTIONS

                                This means that all origins may access your Function, all headers are passed through, and that the only methods allowed to access your Function are GET, POST, and OPTIONS.

                                How do I send CORS headers?

                                You can send CORS headers by using the Twilio Response object described in this example.

                                How can I use an ES Module in my Function?

                                If you've tried to use a package such as got or p-retry in your Functions lately, you may have seen this error in your logs:

                                Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /Users/your-name/your-project/node_modules/got/dist/source/index.js
                                  require() of ES modules is not supported.
                                  require() of /Users/your-name/your-project/node_modules/got/dist/source/index.js from /Users/your-name/your-project/functions/retry.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
                                  Instead rename index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /Users/your-name/your-project/node_modules/got/package.json.
                                      at new NodeError (internal/errors.js:322:7)
                                      at Object.Module._extensions..js (internal/modules/cjs/loader.js:1102:13)
                                      at Module.load (internal/modules/cjs/loader.js:950:32)
                                      at Function.Module._load (internal/modules/cjs/loader.js:790:12)
                                      at Module.require (internal/modules/cjs/loader.js:974:19)
                                      at require (internal/modules/cjs/helpers.js:101:18)
                                      at Object. (/Users/your-name/your-project/functions/retry.js:2:13)
                                      at Module._compile (internal/modules/cjs/loader.js:1085:14)
                                      at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
                                      at Module.load (internal/modules/cjs/loader.js:950:32)

                                This stems from the fact that packages in the Node.js ecosystem are migrating over from the old CommonJS (CJS) standard to the newer, ES Modules (ESM) standard. You can read about the differences in far more detail in this blog post.

                                At the moment, Functions only support CJS, but many packages such as p-retry that you might want to use are now only exported as ESM. You could get around this by pinning to an older version of the library that is still CJS, but that opens you up to vulnerabilities and old, unsupported dependencies.

                                A better solution is to leverage dynamic imports, which allow you to import and run code from an ESM package even if your Function is still in CJS.

                                Using the import method in this scenario returns a Promise, so you will need to properly handle that promise before running the rest of your Function's code. You can do so by nesting your Function's logic within a .then chain, like so:

                                exports.handler = (context, event, callback) => {
                                  import('got').then(({ default: got }) => {
                                      .then((response) => {
                                        // ... the rest of your Function logic

                                However, it's recommended to leverage async/await syntax to handle the promise and minimize the amount of chaining/nesting necessary in your Function:

                                exports.handler = async (context, event, callback) => {
                                  const { default: got } = await import('got');
                                  const response = await got('').json();
                                  // ... the rest of your Function logic

                                Note in the above examples that you are destructuring the default export from the got package, and renaming it to the intended name you'd use when importing it with a static require statement. Refer to this guide on API Calls to see a full example of a Function using dynamic imports with an ESM package.

                                You can use this same strategy for packages that export multiple, named methods:

                                const { getPhoneNumbers, ahoy } = await import('some-package');

                                Lastly, you can import all methods from a package to a single variable, and access them by name like so:

                                const somePackage = await import('some-package');
                                somePackage.default(); // Runs the default export from 'some-package'

                                Can I execute my Functions on a schedule?

                                Currently, Functions are event-driven and can only be invoked by HTTP.

                                Can I see examples of existing Functions?

                                Absolutely! You can find a variety of examples in the navigation bar as well as on our Function Examples page.

                                How many Functions can I create?

                                During the beta period, the UI editor can load and present up to 100 Functions or Assets by default. If a service has over 100 Functions or Assets, click on Load more to view the remaining resources, or the next 100 if there are more than that.

                                The Deploy All and Save buttons, and the ability to change Dependencies, will be disabled until all the Functions and Assets in the service have been loaded. Similarly, it is not possible to delete Functions or Assets until all the Functions or Assets are loaded for view.

                                To create and manage more Functions and/or Assets, we suggest using the Serverless Toolkit or the Functions and Assets API directly.

                                What limits are in place on Function invocation?

                                We limit Functions to 30 concurrent invocations — meaning that if you have over 30 Functions being invoked at the same time, you will see new Function invocations return with a 429 status code. To keep below the threshold, optimize your Functions to return as fast as possible — avoid artificial timeouts and use asynchronous calls to external systems rather than waiting on many API calls to complete.

                                Can I create temporary files when executing a Function?

                                Yes. See this blog post for a description and samples on how to store files for one-off usage.

                                Are there any Twilio resources for learning Node.js?

                                Which runtimes are available for Twilio Functions?

                                The following Node.js runtimes are currently available for Twilio Functions:

                                runtime Version Status Comments
                                node12 Node.js 12.22.11 deprecated
                                node14 Node.js 14.19.1 available (default for new Services) Will be deprecated on April 30, 2023
                                node16 TBD coming soon No ETA, yet

                                The runtime value is a parameter that you can provide at build time. See an example on how to pass this parameter, or read the Node.js migration guide for more context.

                                Rate this page:

                                Need some help?

                                We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd by visiting Twilio's Stack Overflow Collective or browsing the Twilio tag on Stack Overflow.


                                      Thank you for your feedback!

                                      Please select the reason(s) for your feedback. The additional information you provide helps us improve our documentation:

                                      Sending your feedback...
                                      🎉 Thank you for your feedback!
                                      Something went wrong. Please try again.

                                      Thanks for your feedback!

                                      Refer us and get $10 in 3 simple steps!

                                      Step 1

                                      Get link

                                      Get a free personal referral link here

                                      Step 2

                                      Give $10

                                      Your user signs up and upgrade using link

                                      Step 3

                                      Get $10

                                      1,250 free SMSes
                                      OR 1,000 free voice mins
                                      OR 12,000 chats
                                      OR more