ex_aws: flexible AWS API clients for Elixir applications
A flexible, easy to use set of clients AWS APIs for Elixir
At a glance
- What is it?
- ExAws gives Elixir applications a composable interface for AWS APIs, with credential resolution, request signing, regional configuration, streaming responses, and separate service packages. Its direct request API is flexible, but service packages must supply the required codecs and HTTP dependencies.
- Who is it for?
- Elixir teams should consider ExAws when they want request-level control over AWS calls and separate service packages. It is a poorer fit when a fully generated service client would remove protocol and request construction work.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 31 days ago.
- What is it written in?
- Mainly Elixir, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
ExAws modular service packages and request flow
ExAws addresses a specific problem in Elixir applications that call AWS services but do not want every AWS integration bundled into one dependency. Version 2 separates each service into its own package. An application using S3, for example, needs both the core `ex_aws` package and `ex_aws_s3`. Other services are published through the ExAws organization, and the README directs users to that organization for the available service list.
The repository describes ExAws itself as a flexible set of AWS API clients. Its core responsibility is not a complete, generated interface for every AWS operation. Instead, a service package creates an operation request, and `ExAws.request()` completes the request path. According to the documented usage, the request carries or obtains the information needed to sign the call, resolve a region, and send the operation through the configured HTTP client. The default client is `hackney`.
The same request can be sent synchronously or consumed as a stream when the service operation supports streaming. That separation is useful for Elixir code because the caller can choose ordinary list or scalar handling for many operations and select `ExAws.stream!()` for operations that expose streamed results. The README does not document which responses can be streamed beyond stating that some operations support it, so service documentation must be checked before relying on that behavior for a particular call.
This design also makes dependency selection explicit. Core request processing, an individual AWS service package, an HTTP client, and any service-specific codec are distinct concerns. That is more deliberate than installing every service at once, although it also means that the caller must assemble the complete dependency set.
Adding ExAws and making an S3 request
The README does not provide a separate installation command. It instructs users to add ExAws dependencies to the Elixir project's dependency list. For S3, the documented packages are the core client, the S3 service package, `hackney`, and `sweet_xml`. The JSON codec is not shown in this example because the required codec can differ between services.
Add these entries under `deps` in the project's Mix configuration:
defp deps do
[
{:ex_aws, "~> 2.1"},
{:ex_aws_s3, "~> 2.0"},
{:hackney, "~> 1.9"},
{:sweet_xml, "~> 0.6"},
]
endThese version requirements are copied from the README. The service package creates an S3 operation, and `ExAws.request()` sends it using the configured credentials and region:
ExAws.S3.list_objects("my-bucket") |> ExAws.request()The bucket name is the first argument. A successful request returns the decoded response, although the README does not specify the exact response module or fields for `list_objects`. If the bucket belongs to another region, pass the region when sending the request:
ExAws.S3.list_objects("my-bucket") |> ExAws.request(region: "us-west-1")For a supported streaming operation, the README uses `Enum.to_list()` to collect the streamed items:
ExAws.S3.list_objects("my-bucket") |> ExAws.stream!() |> Enum.to_list()That example is documented, but the README does not explain which S3 variants stream or how partial failures behave. It also requires valid AWS credentials. Before using these calls against a real bucket, the application needs an access key, a temporary credential source, or a role supported by the configured credential chain.
Credential resolution order and session tokens
ExAws requires valid AWS keys and documents a credential chain intended to avoid placing long-lived credentials directly in application configuration. The default chain first reads the standard `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables. It then attempts EKS Pod Identity and falls back to IAM-based resolution. In ECS, ExAws can use an assigned task role. Outside that specific ECS case, the documented fallback is the instance role.
For EKS applications configured with Pod Identity, ExAws says no additional client configuration is required. The runtime is expected to provide `AWS_CONTAINER_CREDENTIALS_FULL_URI`, which identifies the credential endpoint, and `AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE`, which points to the JWT token file. ExAws detects those variables automatically. This provides pod-scoped credentials rather than relying on the broader scope associated with an instance role.
Temporary sessions also require token handling. The following configuration reads the standard session token environment variable:
config :ex_aws,
access_key_id: {:system, "AWS_ACCESS_KEY_ID"},
security_token: {:system, "AWS_SESSION_TOKEN"},
secret_access_key: {:system, "AWS_SECRET_ACCESS_KEY"}Instance-role authentication needs `security_token` configured with both the environment lookup and `:instance_role`. The README specifically warns that omitting this arrangement can cause authentication errors or even silent failure:
config :ex_aws,
access_key_id: [{:system, "AWS_ACCESS_KEY_ID"}, :instance_role],
secret_access_key: [{:system, "AWS_SECRET_ACCESS_KEY"}, :instance_role],
security_token: [{:system, "AWS_SESSION_TOKEN"}, :instance_role]AWS CLI profile support is optional. It requires `configparser_ex`, after which an `{:awscli, profile_name, timeout}` entry can be added to the credential list. ExAws then reads `~/.aws/config` and `~/.aws/credentials`. If `AWS_PROFILE` already names a profile, the documented `{:awscli, :system, timeout}` form can use it.
Default region, Hackney options, codecs, and S3 paths
ExAws separates several defaults that can otherwise become embedded in application code. The default region can be fixed in configuration:
config :ex_aws,
region: "us-west-2"It can instead come from the standard region environment variable:
config :ex_aws,
region: {:system, "AWS_REGION"}HTTP behavior is controlled through `:hackney_opts`. The following configuration enables redirects and sets the receive timeout shown in the README:
config :ex_aws,
:hackney_opts,
follow_redirect: true,
recv_timeout: 30_000JSON decoding defaults to Jason, but applications can select another configured codec. The README names Poison as the replacement example:
config :ex_aws,
json_codec: PoisonThat setting only addresses JSON. Each service may also require an XML codec, as demonstrated by `sweet_xml` in the S3 dependency example. Consequently, choosing `Poison` does not remove the need to inspect a service package's dependency requirements.
Path normalization is another project-specific behavior. ExAws normally changes multiple consecutive slash characters in a request path to one slash. The README notes that literal paths can matter for S3 and disables normalization with:
config :ex_aws,
normalize_path: falseThis is not merely a formatting preference. For S3 keys whose names must remain literal, automatic normalization could construct the wrong object path. The README identifies the setting but does not provide a failing object-key example, so the relevant S3 package documentation and expected request path remain important when handling unusual keys.
Direct AWS requests without a service operation module
ExAws also offers an alternative to adding a specific service module: direct use of the core request API. Instead of calling `ExAws.S3.list_objects()` and other generated or service-specific operations, the developer constructs the AWS request details. The README requires identifying the protocol, whether JSON or query; the request path; the AWS service name used during signature generation; and the method, body, query parameters, and headers appropriate to the operation.
This is a real difference in approach. Service packages encode an operation interface and reduce the amount of AWS-specific construction in application code. Direct ExAws usage leaves that construction to the caller while retaining ExAws handling for request execution. It may fit an operation that is not represented by a selected service package, or a codebase that wants explicit control over each request. It is less attractive when developers would have to reproduce protocol details for routine operations.
The README points to the AWS API reference and the AWS SDK for Go API models as possible sources for this information. It does not include a complete direct-request example, exact request module API, response decoding instructions, or error shape. As a result, direct usage is documented as a capability rather than a fully specified tutorial here. The service-specific package documentation remains the better starting point when a package exists for the required AWS service.
There is also a dependency boundary to consider. Direct core use still requires a compatible HTTP client and any codec needed by the protocol being sent. Avoiding a service package does not remove those transport and encoding requirements.
Maintenance signals, versioning, and upgrade choices
The repository is not archived, and its last push was on 2026-09-22. Those are the available maintenance signals. The README, however, contains mixed version context. Its dependency instructions target ExAws 2.x, including `ex_aws` `~> 2.1` and `ex_aws_s3` `~> 2.0`, while the listed recent releases include `v1.0.0-beta1`, `v0.4.13`, and `v0.4.9`. The README's `v1.0.0-beta1` badge is also inconsistent with its 2.x setup instructions. This mismatch means release dates alone are not enough to choose dependencies.
The service split raises upgrade cost because an application is no longer managing a single all-in-one package. Core and service packages must be compatible, HTTP and codec dependencies must remain available, and application configuration may need review. Role-based authentication through `role_arn` and `source_profile` additionally requires `ex_aws_sts`, so applications adopting that authentication route have another package-level dependency to account for.
The project uses the MIT license. That identifies the license recorded for the repository, but it does not answer operational questions such as whether a particular AWS operation matches an application's permissions. Users still need to check AWS credentials, service roles, token configuration, region, and service package requirements separately. The README also does not document a compatibility matrix for Elixir versions, OTP releases, or every codec. Those boundaries should be checked against the package metadata and application constraints before an upgrade.
Editorial conclusion
Elixir teams should consider ExAws when they want request-level control over AWS calls and separate service packages. It is a poorer fit when a fully generated service client would remove protocol and request construction work. Verify first that the required service package exists, that its JSON or XML codec is installed, and that the intended credential source is configured correctly.
Frequently asked questions
Which ExAws packages are needed to call Amazon S3?
The README requires the core `ex_aws` package and the separate `ex_aws_s3` package. Its example also adds `hackney` as the HTTP client and `sweet_xml` as the XML codec.
How does ExAws find AWS credentials?
By default, ExAws checks `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`, then tries EKS Pod Identity and IAM resolution. IAM resolution can use an ECS task role or fall back to an instance role.
Can ExAws use AWS CLI configuration profiles?
Yes, but the README requires `configparser_ex`. ExAws can then read a named profile from `~/.aws/config` and `~/.aws/credentials`, or use a profile selected through `AWS_PROFILE`.
What is the difference between ExAws.request() and ExAws.stream!?
`ExAws.request()` sends a service operation and returns its result, while `ExAws.stream!()` consumes an operation as a stream. The README states that streaming support depends on the service operation.
When should normalize_path be disabled for ExAws?
The README says ExAws normally collapses repeated slash characters. It identifies S3 as a case where literal paths can be necessary and documents `normalize_path: false` as the way to preserve them.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ex-aws-ex-aws)Was this useful?