Neon is expanding into a backend: Object Storage, Functions, and AI Gateway now in beta
/Object Storage/Authentication

Object storage authentication

How Neon credentials map to S3 access keys

Beta

The Neon Object Storage is in Beta. Share your feedback on Discord or via the Neon Console.

Neon Object Storage uses the same scoped-credential system as AI Gateway: one credential API mints branch-scoped tokens that differ by scope (Object Storage uses storage:read and storage:write). You create a scoped credential via the Neon API, and it maps directly to the S3 Access Key ID and Secret Access Key your SDK expects. No AWS account or IAM configuration required.

Creating a credential

An object storage credential requires at minimum one of:

  • storage:read: allows GetObject, HeadObject, ListObjects, and ListBuckets
  • storage:write: allows all read operations plus PutObject and DeleteObject

In the Neon Console, select your branch and click Credentials under APP BACKEND in the sidebar. Click Create credential, give it a name, and check the storage scopes you need.

After creation, the credentials are shown once. Copy the snippet or click Download .env before closing:

AWS_ENDPOINT_URL_S3=https://br-cool-darkness-a1b2c3d4.storage.c-1.us-east-2.aws.neon.build
AWS_ACCESS_KEY_ID=nak_live_...
AWS_SECRET_ACCESS_KEY=nsk_live_...
AWS_REGION=us-east-2

To view or revoke credentials later, return to the Credentials page and use the action menu (⋮) next to the credential.

Mapping to your S3 SDK

Neon credential fieldS3 SDK parameter
token_idAccess Key ID
s3_secret_access_keySecret Access Key
us-east-2Region

warning

Both api_token and s3_secret_access_key are returned exactly once at creation. They cannot be retrieved again. Store them in a secrets manager or environment variables before the response is lost.

Configure your S3 client using these values:

import { S3Client } from '@aws-sdk/client-s3';

const client = new S3Client({
  region: process.env.AWS_REGION,
  endpoint: process.env.AWS_ENDPOINT_URL_S3,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,   // token_id
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, // s3_secret_access_key
  },
  forcePathStyle: true,
});

note

forcePathStyle: true is required for the AWS SDK for JavaScript when using a custom S3 endpoint.

Pull credentials with neon

For local development, neon env pull writes storage credentials to your .env file automatically. No manual copy-paste from the API response:

neon env pull --file .env.local

This populates AWS_ENDPOINT_URL_S3, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_REGION for the current branch alongside your database connection string. To check the current credential status:

neon config status

For production deployments, use the API-based workflow to create named, scoped credentials.

Read vs write scopes

Issue separate credentials for read and write access when you want to limit exposure:

  • Server-side code that uploads files: storage:write (includes read)
  • Client-side or CDN code that only fetches: storage:read
  • Presigned URLs: generate these server-side from a storage:write credential. The URL itself requires no credential in the browser.

The S3 data plane enforces scope on every request. A credential without a storage scope returns 403 AccessDenied on all S3 operations. Server-side COPY requires both storage:read and storage:write.

Credentials in Neon Functions

When your code runs inside Neon Functions, Neon injects storage credentials automatically. You don't need to create a credential:

VariableValue
AWS_ACCESS_KEY_IDS3 Access Key ID
AWS_SECRET_ACCESS_KEYS3 Secret Access Key
AWS_ENDPOINT_URL_S3Branch S3 endpoint URL
AWS_REGIONObject storage region (e.g. us-east-2)

See Environment variables for the full list of variables Neon injects into a function.

Credentials are branch-scoped and tied to the function's serving branch. Injected values are defaults, not reserved names: a user-supplied environment variable with the same name (set via --env or in neon.ts) overrides the injected credential, same as any other Neon-injected variable. See Environment variables. Because the credentials use AWS-standard names, the AWS SDK picks them up automatically when you don't override them. Only forcePathStyle needs explicit configuration:

import { S3Client } from '@aws-sdk/client-s3';

const client = new S3Client({
  region: process.env.AWS_REGION,
  endpoint: process.env.AWS_ENDPOINT_URL_S3,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
  forcePathStyle: true,
});

How branch binding works

Each credential is tied to the branch it was created on. It is valid for:

  • That branch (the anchor branch)
  • Any branch descended from it: preview branches, feature branches, CI branches

It's not valid for branches outside that lineage.

main  ──── credential valid here
  └── preview/feature-x  ──── and here
        └── preview/sub-branch  ──── and here
staging  ──── credential NOT valid here (different lineage)

Listing credentials

The Credentials page in the Console shows all credentials for the current branch: name, key ID, creation date, and last used time. To list via the API:

curl "https://console.neon.tech/api/v2/projects/{project_id}/branches/{branch_id}/credentials" \
  -H "Authorization: Bearer $NEON_API_KEY"

This returns credential metadata. Secrets are never returned after creation.

Revoking credentials

To revoke from the Console, open the Credentials page and use the action menu (⋮) next to the credential. To revoke via the API:

curl -X DELETE "https://console.neon.tech/api/v2/projects/{project_id}/branches/{branch_id}/credentials/{token_id}" \
  -H "Authorization: Bearer $NEON_API_KEY"

To rotate a credential, create a new one, update your environment variables, then revoke the old one.

Common errors

ErrorCauseFix
403 InvalidAccessKeyIdtoken_id is wrong or revokedCheck AWS_ACCESS_KEY_ID is set correctly. If you rotated the credential, update the value.
403 SignatureDoesNotMatchWrong secret access keyCheck AWS_SECRET_ACCESS_KEY is the value from credential creation. Secrets can't be retrieved. Revoke and recreate if lost.
403 AccessDeniedCredential lacks the required scopeRecreate with storage:write for uploads/deletes
403 AccessDeniedBranch not in credential lineageUse a credential created on this branch or an ancestor

Need help?

Join our Discord Server to ask questions or see what others are doing with Neon. For paid plan support options, see Support.

Was this page helpful?
Edit on GitHub