If you aren’t interested in a code-forward approach, we recommend starting with Autobuild, which uses AI to analyze your template or documentation and then automatically creates and deploys a Blueprint (for schema definition) and a Listener (for validations and transformations) to your Flatfile App.

Once you’ve started with Autobuild, you can always download your Listener code and continue building with code from there!

About Listeners

Listeners handle different types of events in Flatfile:

  • Space Configuration: Setting up workbooks, sheets, and themes
  • Data Validation: Custom field and record-level validation
  • Job Processing: Handling long-running operations
  • File Operations: Processing uploaded files
  • User Interactions: Responding to custom actions

for more details, see Listeners

Scoping Listeners

Each listener is tied to a specific environment using the FLATFILE_ENVIRONMENT_ID environment variable. This ensures the listener only processes events from its assigned environment.

For more granular control, you can use listener.namespace() to organize and filter events. Namespaces let you create listeners that only respond to events from specific spaces, workbooks, or other scoped contexts within your environment.

For more details and namespace patterns, see Scoping with Namespaces.

Install Dependencies

Install the required packages for building Flatfile listeners:

npm install @flatfile/listener @flatfile/api

Create Your Listener File

Create a new file called listener.js (or listener.ts for TypeScript):

import { FlatfileListener } from "@flatfile/listener";
import api from "@flatfile/api";

export default function (listener) {
  // Configure the space when it's created
  listener.on("space:configure", async (event) => {
    const spaceId = event.context.spaceId;

    await api.workbooks.create({
      spaceId,
      name: "My Workbook",
      sheets: [
        {
          name: "contacts",
          slug: "contacts",
          fields: [
            { key: "name", type: "string", label: "Full Name" },
            { key: "email", type: "string", label: "Email" },
          ],
        },
      ],
      actions: [
        {
          label: "Submit",
          description: "Send data to destination system",
          operation: "submitActionForeground",
          mode: "foreground",
        },
      ],
    });
  });

  // Handle when someone clicks Submit
  listener.on(
    "job:ready",
    { job: "workbook:submitActionForeground" },
    async (event) => {
      const { jobId } = event.context;

      // Get the data
      const job = await api.jobs.get(jobId);
      const records = await api.records.get(job.data.workbookId);

      // Process it (log to console for now)
      console.log("Processing records:", records.data.length);

      // Mark job as complete
      await api.jobs.complete(jobId, {
        outcome: { message: "Data processed successfully!" },
      });
    }
  );
}

Testing and Deployment

For complete testing and deployment documentation, see the CLI Reference.

Authentication Setup

For complete authentication setup examples, see the Authentication Examples guide.

# .env
FLATFILE_API_KEY=sk_your_api_key_here
FLATFILE_ENVIRONMENT_ID=us_env_your_environment_id

Local Development

To test your listener locally, you can use the flatfile develop command. This will start a local server that will listen for events and respond to them, and will also watch for changes to your listener code and automatically reload the server.

# Run locally with file watching
npx flatfile develop

# With custom entry point
npx flatfile develop ./src/my-listener.ts

Deploy to Flatfile Cloud

Deploying your listener will create a new Agent in your Flatfile environment. Under the hood, this will compile all of your listener code and its dependencies into a single file, which it sends to the Flatfile API’s POST /v1/agents endpoint.

# Basic deployment
npx flatfile deploy

# With custom slug
npx flatfile deploy -s my-listener

That’s it! Your listener will:

  • Create a workbook when a space is opened
  • Process data when users click Submit
  • Handle the complete data import workflow

What Just Happened?

Your minimal listener handles two key events:

  1. space:configure - Sets up the data structure (workbook + sheets)
  2. job:ready - Processes data when users submit

Common Patterns

Add validation:

  • This example sets up a bare metal event listener on the commit:created event
  • Depending on your use case, a better pattern may be to use the Record Hooks plugin, which provides a more object-oriented approach to updating records.
listener.on("commit:created", async (event) => {
  const records = await api.records.get(event.context.sheetId);

  for (const record of records.data) {
    if (!record.values.email?.value?.includes("@")) {
      await api.records.update(record.id, {
        email: {
          value: record.values.email?.value,
          messages: [{ type: "error", message: "Invalid email" }],
        },
      });
    }
  }
});

Add a custom action:

// In your sheet config
actions: [
  {
    operation: "export-data",
    mode: "foreground",
    label: "Export to Database",
    description: "Send data to your system",
  },
];

// Handle the action
listener.on("job:ready", { job: "sheet:export-data" }, async (event) => {
  // Your export logic here
});

Next Steps