--- layout: page title: Upload to Amazon S3 nav_title: Amazon S3 permalink: /guides/advanced/storage-destinations/s3/ parent: Storage Destinations grand_parent: Advanced great_grand_parent: Guides nav_order: 1 description: >- Upload images generated from HTML/CSS, URL screenshots, and PDFs directly to Amazon S3 using an IAM role and HTML/CSS to Image. --- # Upload images to Amazon S3 {: .no_toc } {: .fs-9 } Send every rendered image or PDF to an Amazon S3 bucket you control. {: .fs-6 .fw-300 }
HTML/CSS to Image connects to Amazon S3 by assuming an IAM role in your AWS account. HCTI receives temporary credentials when it needs to write an object, so you do not need to create or share a long-lived AWS access key. {% include hint.md title="Plan availability" text="Storage destinations are available on the **10,000 images/month plan or higher**. If you'd like to try this on a different plan, email **support@htmlcsstoimage.com**." %} ## Before you begin You need: - An existing S3 bucket and its AWS region. - Permission to create an IAM role and attach a permissions policy. - An optional key prefix, such as `hcti-renders`, if HCTI objects should stay within one part of the bucket. Open the [Storage Destinations dashboard](https://htmlcsstoimage.com/dashboard/storage-destinations), click **Add storage destination**, and choose **Amazon S3**. Enter the bucket, region, and optional key prefix first; HCTI uses those values to generate scoped example policies. ## Create the IAM role The dashboard generates a trust policy and a role permissions policy. They answer different questions and belong in different parts of the IAM role. ### Add the trust policy The trust policy controls **who can assume the role**. It contains: - The HTML/CSS to Image storage writer role as the principal. - `sts:AssumeRole` as the allowed action. - An `sts:ExternalId` condition unique to your HCTI organization. In AWS IAM, go to **Roles**, choose **Create role**, select **Custom trust policy**, and paste the dashboard-generated trust policy. The external ID prevents the HCTI writer role from using this role for a different organization. Use the generated value without modifying or reusing it. The trust policy only allows HCTI to assume the role. It does not grant access to your S3 bucket. ### Attach the permissions policy Attach the dashboard-generated role permissions policy as an inline or managed policy. It scopes access to the bucket and optional prefix entered in HCTI. | Permission | Required? | Purpose | |:-----------|:----------|:--------| | `s3:PutObject` | **Required** | Writes rendered images and the connection-test object. | | `s3:GetObject` | Optional | Reloads an original for a later resize, crop, or format conversion when **Disable HCTI Storage** is selected. | | `s3:DeleteObject` | Optional | Removes the small object created by **Test connection**. It is not used to delete rendered images. | The generated resources cover rendered objects under `arn:aws:s3::://*`, including connection-test objects under `/.hcti/connection-tests/`. Write access is enough to connect and upload base renders. Include read access if HCTI may need to create a transformation after the initial render while HCTI Storage is disabled. Include delete access if you want connection tests to clean up after themselves. ## Finish the destination in HCTI 1. Finish creating the role and copy its ARN. 2. Return to the storage destination form and paste the ARN into **IAM role ARN**. 3. Confirm the bucket, AWS region, and optional key prefix. 4. Click **Test connection**. 5. Enable and save the destination after the test succeeds. 6. Copy the destination `id`. The connection test assumes the role and writes a small text object. Cleanup is best-effort, so a missing `s3:DeleteObject` permission does not make a successful write test fail. ## Upload an HCTI image to S3 Pass the destination ID as `storage_destination_id` when you create an image: ```bash curl -X POST https://hcti.io/v1/image \ -u 'user-id:api-key' \ -H 'Content-Type: application/json' \ -d '{ "url": "https://example.com", "storage_destination_id": "your-storage-destination-id" }' ``` Rendering is lazy. With normal HCTI storage enabled, request the image URL returned by the API to render the image and write both the HCTI copy and the S3 copy. With **Disable HCTI Storage** selected, send an authenticated `PUT` request to the `/v1/store/...` URL returned by the create request. The base object is stored as `/.png`, or `.pdf` when the request uses `pdf_options`. Resized, cropped, and reformatted versions use separate object keys. ## Troubleshooting Amazon S3 uploads - **HCTI cannot assume the role** — Confirm that the trust relationship contains the generated HCTI principal and the exact organization-specific external ID. - **The connection test returns Access Denied** — Confirm that the role permissions policy allows `s3:PutObject` on the bucket and configured prefix, and that a bucket policy does not explicitly deny the request. - **The connection test uses the wrong endpoint** — Confirm that the AWS region in HCTI matches the bucket's region. - **A later transformation fails with HCTI Storage disabled** — Add `s3:GetObject` for the configured object prefix. - **Connection-test objects remain** — Add `s3:DeleteObject` for the `.hcti/connection-tests/` path. AWS documents [creating a role with a custom trust policy](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-custom.html) and [using external IDs for third-party access](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_common-scenarios_third-party.html). {% include hint.md title="Next: use your storage destination" text="[Continue to the Storage Destinations guide](/guides/advanced/storage-destinations/) for templates, batches, object storage behavior, and the complete `PUT /v1/store` response." %} {% include code_footer.md version=1 %}