Use Amazon EventBridge Scheduler to invoke a short AWS Lambda function that submits a DynamoDB export job to Amazon S3. DynamoDB performs the export asynchronously, so the function records the returned export ARN and exits; it does not wait for the table data to finish copying. Check the export status before a downstream process reads the files.

This walkthrough schedules full snapshots. DynamoDB exports do not consume table read capacity, but an S3 export is a separate copy for analytics, retention, or data movement. Keep point-in-time recovery (PITR) or another backup method enabled for table recovery. For a one-time export from the console or CLI, see our DynamoDB export to S3 walkthrough.

Required setup

Prepare DynamoDB and the S3 destination

  1. Enable PITR on the DynamoDB table. Exports require it. The recovery period is configurable from 1 to 35 days; 35 days is the default. Select export times within the table's actual restorable range. AWS reports this range as EarliestRestorableDateTime and LatestRestorableDateTime; the latest point is typically about five minutes behind the current time. If the request omits ExportTime, DynamoDB uses the latest restorable point. See AWS's PITR guide.
  2. Create or choose a private S3 bucket and an export prefix such as exports/orders. DynamoDB does not support Requester Pays buckets. For private-bucket and object-storage basics, see our Amazon S3 fundamentals guide.
  3. Choose the export format. DynamoDB supports DynamoDB JSON and Amazon Ion. The service writes compressed data files and manifests beneath an export ID under the prefix; compression is handled by the export, and there is no Parquet or compression-format option. The manifests identify the files for that specific export.

S3 encrypts new uploads with SSE-S3 by default. If you choose SSE-KMS for the export, the KMS key must be in the S3 bucket's Region; grant the export principal kms:GenerateDataKey and kms:Decrypt on that key for multipart uploads, through IAM and the key policy. If the source table uses a customer-managed KMS key, allow DynamoDB to decrypt the table data through that key policy. See AWS's export prerequisites and SSE-KMS permissions.

Give each role only its job

There are two roles in this setup. The Lambda execution role calls DynamoDB and allows the export service to write to the destination prefix. The EventBridge Scheduler execution role can invoke only this Lambda function. The Lambda console can create the Scheduler role when you add the schedule trigger; do not put the DynamoDB or S3 export permissions on that role.

Attach the following scoped policy to the Lambda execution role, replacing the Region, account, table, bucket, and prefix. The standard AWSLambdaBasicExecutionRole policy, or equivalent CloudWatch Logs permissions, lets the function write its submission log.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "StartExportForOneTable",
      "Effect": "Allow",
      "Action": "dynamodb:ExportTableToPointInTime",
      "Resource": "arn:aws:dynamodb:REGION:ACCOUNT_ID:table/TABLE_NAME"
    },
    {
      "Sid": "WriteExportObjectsToOnePrefix",
      "Effect": "Allow",
      "Action": [
        "s3:AbortMultipartUpload",
        "s3:PutObject",
        "s3:PutObjectAcl"
      ],
      "Resource": "arn:aws:s3:::BUCKET_NAME/exports/orders/*"
    }
  ]
}

For a bucket in another account, the bucket owner must also allow the Lambda role to write to the prefix, and the request must include the bucket owner's account ID. The export request itself must be made with credentials from the source table's account; assume a role there if needed. Customer-managed KMS keys require separate, narrowly scoped permissions. AWS lists the export's S3 actions and cross-account requirements in its requesting an export documentation.

Create a Lambda function that submits the export

Choose a supported runtime and set configuration

Create a Lambda function with a currently supported Node.js runtime; Node.js 24.x is supported as of this update. Check AWS's Node.js runtime page before deploying because runtime support changes. Save the ES module as index.mjs and set the handler to index.handler. The function uses AWS SDK for JavaScript v3. Supported Lambda Node.js runtimes include SDK v3, but its version varies by runtime and Region; bundle a pinned SDK dependency when you need to control the version.

Set these environment variables:

  • TABLE_ARN: the source table ARN.
  • S3_BUCKET: the destination bucket name.
  • S3_PREFIX: the object prefix, such as exports/orders.
  • S3_BUCKET_OWNER: optional 12-digit account ID when the destination bucket belongs to another account.

The function only submits the export request. Deploy it in the source table's Region because the SDK client uses Lambda's Region by default, or set the client's region to the table's Region. The destination S3 bucket may be in another Region. Choose memory and timeout for the API call and logging needs, not to copy the table. A standard Lambda invocation can run for at most 15 minutes, while DynamoDB export duration varies and has no completion-time guarantee. See AWS's Lambda timeout guidance and DynamoDB export behavior.

Submit a retry-safe full export

Use an EventBridge Scheduler target input containing its scheduled time, as shown in the next section. The scheduled time stays the same across retries for that occurrence. This lets the handler send the same idempotency token and request parameters if Scheduler redelivers the event or Lambda retries the asynchronous invocation.

import { createHash } from "node:crypto";
import {
  DynamoDBClient,
  ExportTableToPointInTimeCommand
} from "@aws-sdk/client-dynamodb";

const dynamodb = new DynamoDBClient({});
const tableArn = process.env.TABLE_ARN;
const bucket = process.env.S3_BUCKET;
const prefix = process.env.S3_PREFIX || "exports";
const bucketOwner = process.env.S3_BUCKET_OWNER;

if (!tableArn || !bucket) {
  throw new Error("Set TABLE_ARN and S3_BUCKET");
}

export const handler = async (event) => {
  const scheduledTime = event?.scheduledTime;
  if (typeof scheduledTime !== "string") {
    throw new Error("The Scheduler input must include scheduledTime");
  }

  const cleanPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
  const exportPrefix = cleanPrefix + "/" + scheduledTime.slice(0, 10);
  const clientToken = createHash("sha256")
    .update(tableArn + ":" + scheduledTime)
    .digest("hex")
    .slice(0, 36);

  const request = {
    TableArn: tableArn,
    S3Bucket: bucket,
    S3Prefix: exportPrefix,
    ExportType: "FULL_EXPORT",
    ExportFormat: "DYNAMODB_JSON",
    ClientToken: clientToken
  };

  if (bucketOwner) {
    request.S3BucketOwner = bucketOwner;
  }

  const result = await dynamodb.send(
    new ExportTableToPointInTimeCommand(request)
  );
  const exportArn = result.ExportDescription?.ExportArn;
  if (!exportArn) {
    throw new Error("DynamoDB did not return an export ARN");
  }

  console.info(JSON.stringify({
    event: "dynamodb_export_submitted",
    exportArn,
    scheduledTime
  }));

  return { exportArn, scheduledTime, status: "SUBMITTED" };
};

The code omits ExportTime, so each scheduled job requests the latest restorable snapshot. For a chosen historical snapshot, set a valid export time inside the table's PITR range. DynamoDB's ClientToken makes identical requests idempotent for eight hours after the first request completes; after that, the same token can start a new export. Keep Scheduler's retry age shorter than eight hours when relying on this token, and keep the request configuration stable during retries. See the ExportTableToPointInTime API reference.

Test one invocation before scheduling it

Deploy the function, configure its environment variables and execution role, then invoke it with a test event like this:

{
  "scheduledTime": "2026-10-04T03:00:00Z"
}

A successful invocation returns an export ARN and logs dynamodb_export_submitted. That means DynamoDB accepted the request; it does not mean the files are ready. The ARN is the identifier you use to check completion.

Schedule the Lambda function

Use Amazon EventBridge Scheduler for new scheduled invocations. AWS classifies EventBridge scheduled rules as a legacy feature and recommends Scheduler for cron or rate schedules, retries, and dead-letter queues. In the Lambda console:

  1. Open the function and choose Add trigger.
  2. Select Scheduler, choose a recurring schedule, and enter a cron or rate expression. For example, cron(0 3 * * ? *) runs daily at 03:00 when the schedule timezone is UTC.
  3. Set the target input to {"scheduledTime":"<aws.scheduler.scheduled-time>"}. Scheduler replaces the context attribute with the planned invocation time.
  4. Use a Scheduler execution role scoped to invoking this function. Configure its delivery retries and, if failed deliveries need review, a dead-letter queue. Separately configure Lambda asynchronous invocation retries, maximum event age, and an on-failure destination; Scheduler can accept the invocation before the handler runs, so its delivery result does not show handler success. Keep retry windows within DynamoDB's eight-hour client-token lifetime when relying on that token.

Scheduler invokes Lambda asynchronously. Scheduler delivery retries cover attempts to hand the event to Lambda. After Lambda accepts it, Lambda's asynchronous queue handles function errors and throttles according to the function's own retry, maximum event-age, and on-failure settings. Neither layer tracks whether the separate DynamoDB export later completes. See AWS's Lambda asynchronous error handling.

Check exports and plan the schedule

Monitor submission and completion separately

Use Lambda logs and the Lambda Errors metric to investigate failed submissions or function errors. The sample emits a log record for each accepted export request; this Logs Insights query finds those records:

fields @timestamp, @message
| filter @message like /dynamodb_export_submitted/
| sort @timestamp desc
| limit 20

To check whether an export finished, call DynamoDB's DescribeExport operation with the ARN returned by the function. The identity running the check needs dynamodb:DescribeExport on arn:aws:dynamodb:REGION:ACCOUNT_ID:table/TABLE_NAME/export/*. Grant S3 read access separately if that identity also verifies the manifests and data files. Keep these read permissions off the starter Lambda role unless the function itself performs those checks. The export status is IN_PROGRESS, COMPLETED, or FAILED; a failure includes a code and message.

aws dynamodb describe-export --export-arn EXPORT_ARN --region REGION --query 'ExportDescription.{Status:ExportStatus,FailureCode:FailureCode,FailureMessage:FailureMessage,Manifest:ExportManifest}'

In the console, export jobs also appear under DynamoDB's Exports to S3 view. When an export reports COMPLETED, use the manifest-summary.json and manifest-files.json under its AWSDynamoDB/<ExportId>/ path to find and verify the compressed data files. An automated consumer should wait for that terminal status before reading them. A submission log or files appearing under the S3 prefix does not establish completion; use DescribeExport. AWS does not guarantee how long the asynchronous export will take, so do not poll inside the submitting Lambda or make downstream work depend on a fixed delay.

Review the full cost

Plan for PITR, DynamoDB export processing, S3 object storage and requests, and the Lambda and Scheduler invocations. Full exports are charged based on the table data and local secondary indexes at the selected point in time; incremental exports are charged based on the data processed for their interval. S3 storage and request charges continue while exported files remain in the bucket. Check current Regional rates on AWS's DynamoDB pricing page and S3 pricing page rather than relying on a fixed per-GB estimate. Set lifecycle retention only after checking how long the exports must remain available.

Account for export windows and service quotas

A full export is a snapshot from within the PITR window. An incremental export covers a selected interval within that window; its period must be at least 15 minutes and no more than 24 hours, with the start inclusive and the end exclusive. Incremental output is a change-oriented format and is not a replacement for full snapshots unless downstream processing is designed for it.

AWS's current DynamoDB quota documentation lists up to 300 concurrent export jobs or 100 TB of total in-flight table exports for both full and incremental exports; the service checks these quotas before queuing a request. Check the applied quota in the Region you use before scaling a schedule, since account quotas can differ. Exports are asynchronous, do not consume read capacity units, and may take different amounts of time as table size and service load change. Choose a cadence that accounts for jobs still in progress. See DynamoDB export quotas.

Summary

  • Enable PITR and select a private S3 bucket with the required prefix and encryption.
  • Give the Lambda execution role scoped DynamoDB and S3 permissions; let Scheduler invoke only the Lambda function.
  • Use a stable scheduled-time token so Scheduler retries do not submit duplicate identical exports.
  • Record the export ARN and use DescribeExport to check for COMPLETED or FAILED before consuming the output.
  • Check current account quotas and regional prices as export volume and retention grow.