Netlify Functions, by default, time out after 10 seconds, making them unsuitable for long-running tasks.

Netlify Background Functions are Netlify’s solution to this. They let you offload computationally intensive or long-running operations from your API endpoints to a separate, asynchronous process that doesn’t have the same strict timeout limitations. This means your API endpoint can respond quickly to the user, while the actual work happens in the background.

Let’s see this in action. Imagine you have a Netlify Function that needs to process a large image file, perhaps resizing it, applying filters, and then uploading it to a CDN. A standard Netlify Function would time out long before this process completes.

Here’s a simplified example of how you might trigger a background function.

First, your regular Netlify Function (let’s call it trigger-background-task.js) would look something like this:

// Netlify Function: trigger-background-task.js
// Triggered by an HTTP request

exports.handler = async (event, context) => {
  const { imageUrl } = JSON.parse(event.body);

  try {
    // This is where we enqueue the background task.
    // We're telling Netlify to run our background function.
    // The payload is whatever data the background function needs.
    await context.client.invokeBackgroundFunction('process-image', { imageUrl });

    return {
      statusCode: 202, // Accepted
      body: JSON.stringify({ message: 'Image processing initiated. We will notify you when complete.' }),
    };
  } catch (error) {
    console.error('Error invoking background function:', error);
    return {
      statusCode: 500,
      body: JSON.stringify({ message: 'Failed to initiate image processing.' }),
    };
  }
};

This trigger-background-task.js function receives an imageUrl in its request body. Instead of processing the image itself, it uses context.client.invokeBackgroundFunction('process-image', { imageUrl }) to tell Netlify to execute a different function named process-image in the background, passing the imageUrl to it. The original function then immediately returns a 202 Accepted status, indicating that the request has been received and processing has begun.

Now, for the actual work, you’d have your background function, process-image.js, located in a separate file within your functions directory (e.g., netlify/functions/background/process-image.js). Notice the background subdirectory; this is how Netlify distinguishes background functions.

// Netlify Function: netlify/functions/background/process-image.js
// This is the background function that does the heavy lifting.

const imageProcessingService = require('../../utils/image-processor'); // Assume this is your image processing logic
const notifyUser = require('../../utils/notifier'); // Assume this sends a notification

exports.handler = async (event, context) => {
  const { imageUrl } = event.payload; // Payload from the invoking function

  console.log(`Starting background processing for image: ${imageUrl}`);

  try {
    // Simulate a long-running task
    const processedImageUrl = await imageProcessingService.process(imageUrl);
    console.log(`Image processed successfully: ${processedImageUrl}`);

    // Notify the user that the task is complete
    await notifyUser(context.identity.user.email, 'Image Processing Complete', `Your image at ${imageUrl} has been processed and is available at ${processedImageUrl}.`);

    return {
      statusCode: 200,
      body: JSON.stringify({ message: 'Image processing completed successfully.' }),
    };
  } catch (error) {
    console.error('Background image processing failed:', error);
    // Optionally, notify the user about the failure
    await notifyUser(context.identity.user.email, 'Image Processing Failed', `There was an error processing your image at ${imageUrl}. Please try again.`);

    // It's important to return a successful status code even on error
    // to prevent Netlify from retrying indefinitely. The error is logged.
    return {
      statusCode: 200,
      body: JSON.stringify({ message: 'Background processing encountered an error.' }),
    };
  }
};

In process-image.js, event.payload contains the data sent from invokeBackgroundFunction. Here, it’s the imageUrl. The function then performs the actual image processing (represented by imageProcessingService.process) and, upon success, notifies the user (via notifyUser). If an error occurs, it logs the error and still returns a 200 status code. This is crucial: background functions that encounter an error but don’t return a successful status code will be retried by Netlify.

The mental model here is one of decoupling. Your frontend or API gateway (your main Netlify Functions) are responsible for handling user requests and providing quick responses. They act as dispatchers. The actual, time-consuming work is delegated to a separate, asynchronous system – the background functions. This means your users aren’t waiting around for a slow operation to complete; they get an immediate confirmation that their request is being handled.

The key lever you control is the invokeBackgroundFunction method. This is the bridge between your synchronous API endpoints and your asynchronous background workers. You pass the name of the background function to run and any data it needs as a payload. Netlify then takes care of queuing and executing this background function in an environment optimized for longer tasks. You can also control retry behavior by how you handle errors within the background function itself.

A critical detail often missed is how Netlify handles retries for background functions. If a background function crashes or exits with a non-200 status code, Netlify will automatically attempt to re-run it. This is a safety net, but it can lead to unexpected behavior or excessive retries if not managed carefully. For example, if your background function has a bug that causes it to fail consistently, Netlify will keep trying to execute it, potentially consuming resources and incurring costs. It’s therefore essential to ensure your background functions are robust and always return a 200 status code, even if an internal error occurred, logging the error for later investigation.

The next step is to implement a reliable notification system to inform users when their background tasks are truly complete or have failed.

Want structured learning?

Take the full Netlify course →