Snowflake: Unload Data to Angler S3
Set up Angler's S3 bucket as a Snowflake external stage and unload customers, orders, line items, and products on a schedule.
Snowflake can unload your backend data directly into Angler's S3 bucket using a storage integration and an external stage. No AWS access keys are exchanged: Angler creates an IAM role scoped to your workspace folder, and Snowflake assumes that role when it writes files. Once set up, a scheduled Snowflake task runs the unload with no external scheduler.
Integration values are exchanged through your onboarding specialistThe role ARN, S3 path, Snowflake IAM user ARN, and external ID are specific to your account. Send and receive them only through your Angler onboarding specialist; do not post them in shared channels or tickets.
Folder layout
Each object lands in its own folder under your workspace path. Folder names are case-sensitive.
| Folder | Contents |
|---|---|
customers/ | One row per customer, including email and phone if available |
orders/ | One row per order, including customer id and order totals |
lineItems/ | One row per order line, keyed to its order id |
products/ | One row per product or variant in the catalog |
Path pattern:
s3://<angler_bucket>/inbound/<workspace_id>/<folder>/
Your onboarding specialist will send the exact bucket name and workspace id.
Prerequisites
- A Snowflake role with
ACCOUNTADMIN, or the globalCREATE INTEGRATIONprivilege, to create the storage integration. - A role with
CREATE STAGEon the target schema andUSAGEon the integration, to create the stage. - A warehouse for the scheduled task, and read access to the four source tables.
- A Snowflake account hosted on AWS in a standard (non-government, non-China) region. If yours differs, tell your onboarding specialist before starting.
Values exchanged
| Value | Sent by | Sent to | Source |
|---|---|---|---|
IAM role ARN (STORAGE_AWS_ROLE_ARN) | Angler | You | Role Angler creates for your Snowflake account |
| S3 path (bucket and workspace id) | Angler | You | Your inbound folder in Angler's bucket |
STORAGE_AWS_IAM_USER_ARN | You | Angler | Output of DESC INTEGRATION (Step 2) |
STORAGE_AWS_EXTERNAL_ID | You | Angler | Output of DESC INTEGRATION (Step 2) |
Setup overview
You run Steps 1 to 5 in Snowflake; Angler handles two AWS steps (A and B) around them.
| Step | Owner | Action |
|---|---|---|
| A | Angler | Create the IAM policy and role scoped to your folder; send role ARN and S3 path |
| 1 | You | Create the storage integration |
| 2 | You | Run DESC INTEGRATION; send IAM user ARN and external ID |
| B | Angler | Add those values to the role's trust policy; confirm back |
| 3 | You | Grant usage to the unload role (optional) |
| 4 | You | Create the file format and stage; validate |
| 5 | You | Unload a sample, then schedule the recurring task |
Step 1: Create the storage integration
Use the role ARN and S3 path from your onboarding specialist. Limit allowed locations to your workspace path.
USE ROLE ACCOUNTADMIN;
CREATE STORAGE INTEGRATION angler_s3_int
TYPE = EXTERNAL_STAGE
STORAGE_PROVIDER = 'S3'
ENABLED = TRUE
STORAGE_AWS_ROLE_ARN = '<role_arn_from_angler>'
STORAGE_ALLOWED_LOCATIONS = ('s3://<angler_bucket>/inbound/<workspace_id>/');Step 2: Retrieve the Snowflake IAM user and external ID
DESC INTEGRATION angler_s3_int;Record STORAGE_AWS_IAM_USER_ARN and STORAGE_AWS_EXTERNAL_ID from the output and send both to your onboarding specialist. Angler adds them to the role's trust policy and confirms when it is done; Step 4 will fail until then.
Do not recreate the integrationRunning
CREATE OR REPLACEon the integration after sending these values generates a new external ID, which breaks the trust relationship until Angler updates it again. If you must recreate it, send the new values to your onboarding specialist.
Step 3: Grant usage to the unload role
Skip this step if ACCOUNTADMIN will own the stage and task.
GRANT USAGE ON INTEGRATION angler_s3_int TO ROLE <unload_role>;
GRANT CREATE STAGE ON SCHEMA <db>.<schema> TO ROLE <unload_role>;Step 4: Create the file format and external stage
Point the stage at your workspace root, so one stage serves all four folders. The trailing slash on the URL is required.
USE ROLE <unload_role>;
USE SCHEMA <db>.<schema>;
CREATE FILE FORMAT angler_csv_gz
TYPE = CSV
COMPRESSION = GZIP
FIELD_OPTIONALLY_ENCLOSED_BY = '"'
NULL_IF = ('')
EMPTY_FIELD_AS_NULL = TRUE;
CREATE STAGE angler_stage
STORAGE_INTEGRATION = angler_s3_int
URL = 's3://<angler_bucket>/inbound/<workspace_id>/'
FILE_FORMAT = angler_csv_gz;Validate the connection:
SELECT SYSTEM$VALIDATE_STORAGE_INTEGRATION(
'angler_s3_int',
's3://<angler_bucket>/inbound/<workspace_id>/',
'validate_test.txt',
'write');Step 5: Unload data to the four folders
Run one COPY INTO per object. Start with a small sample of each, so Angler can confirm the expected fields before you schedule anything.
COPY INTO @angler_stage/customers/
FROM (SELECT * FROM <db>.<schema>.customers)
HEADER = TRUE
INCLUDE_QUERY_ID = TRUE
MAX_FILE_SIZE = 268435456;
COPY INTO @angler_stage/orders/
FROM (SELECT * FROM <db>.<schema>.orders)
HEADER = TRUE
INCLUDE_QUERY_ID = TRUE
MAX_FILE_SIZE = 268435456;
COPY INTO @angler_stage/lineItems/
FROM (SELECT * FROM <db>.<schema>.line_items)
HEADER = TRUE
INCLUDE_QUERY_ID = TRUE
MAX_FILE_SIZE = 268435456;
COPY INTO @angler_stage/products/
FROM (SELECT * FROM <db>.<schema>.products)
HEADER = TRUE
INCLUDE_QUERY_ID = TRUE
MAX_FILE_SIZE = 268435456;HEADER = TRUEwrites column names, which Angler uses to map fields.INCLUDE_QUERY_ID = TRUEgives every run unique file names, so a new drop never overwrites an earlier one.MAX_FILE_SIZEcaps each file at 256 MB.
What to include
- customers: customer id, email, phone, name, address fields, created and updated timestamps.
- orders: order id, customer id, order-level email if available, totals, currency, status, channel or source, created and updated timestamps.
- lineItems: line id, order id, product or variant id, quantity, price, discount.
- products: product and variant ids, title, category, brand, price.
Your onboarding specialist will share the full field mapping. Two rules matter most: the customer id on orders must match the id in customers exactly, and each row should appear once per drop.
Backfill, then incremental
Send history first as a one-time full unload; agree the start date with your onboarding specialist. After that, filter each run on updated_at with a short overlap, so late edits and refunds are captured:
COPY INTO @angler_stage/orders/
FROM (
SELECT * FROM <db>.<schema>.orders
WHERE updated_at >= DATEADD(day, -3, CURRENT_DATE())
)
HEADER = TRUE
INCLUDE_QUERY_ID = TRUE;Apply the same filter to customers, line items, and products.
Schedule with a Snowflake task
Wrap the four incremental statements in a stored procedure, then run it on a schedule:
CREATE OR REPLACE TASK angler_daily_unload
WAREHOUSE = <warehouse>
SCHEDULE = 'USING CRON 0 6 * * * UTC'
AS
CALL <db>.<schema>.angler_unload();
ALTER TASK angler_daily_unload RESUME;A daily cadence is strongly recommended.
Troubleshooting
| Symptom | Likely cause and fix |
|---|---|
Access Denied or assume-role error on stage or COPY | Angler has not yet updated the trust policy (Step B), or the integration was recreated. Confirm with your onboarding specialist. |
Location ... is not allowed by integration | The stage URL is outside STORAGE_ALLOWED_LOCATIONS. Use the exact workspace path, with a trailing slash. |
| Files land in the wrong place | Folder names are case-sensitive: lineItems/, not lineitems/ or line_items/. |
| Access still works after a change was revoked | Snowflake caches temporary AWS credentials for up to 60 minutes. |
Reference
Updated about 1 hour ago
