> ## Documentation Index
> Fetch the complete documentation index at: https://docs.elementum.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect Snowflake to Elementum

> Step-by-step setup for a CloudLink between Snowflake and Elementum, in the order you'll execute it.

This page is the chronological setup guide for a Snowflake CloudLink. Follow it top to bottom: prerequisites, IP whitelisting, key-pair authentication, the Snowflake setup script, the Elementum-side connection, and verification.

If you're new to CloudLink or unsure how it differs from Snowflake itself, start with the [CloudLink Overview](/administration/cloudlink-overview).

## How it works

Elementum connects directly to your Snowflake account using a dedicated service user, role, warehouse, and database. The connection is read/write where you grant it, and read-only where you don't.

| Side                       | What it owns                                                                                                                                                                                |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Your Snowflake account** | Your data (databases, tables, views). You grant access to a dedicated `ELEMENTUM` user/role with the exact permissions you choose. You can restrict access to known Elementum IP addresses. |
| **Elementum platform**     | A reader/writer service account that connects in-place to your Snowflake account. No data is copied or stored outside Snowflake; all operations execute in your Snowflake environment.      |

<Info>
  Both Internet and VPN traffic are encrypted with TLS. VPN provides additional security through least-privilege network controls.
</Info>

<Note>
  **Attachments and file uploads aren't stored in your Snowflake account.** File contents live in Elementum-managed cloud storage, not in a Snowflake table or blob column. Attachment metadata (name, media type, URL, size, and so on) is available through automations and the [REST API](/api-reference/api-introduction). See [Where attachments are stored](/workflows/file-uploads-attachments#where-attachments-are-stored) for the full picture.
</Note>

## Prerequisites

Before starting, confirm you have:

<Steps>
  <Step title="Snowflake access">
    * **`ACCOUNTADMIN`** role for setup
    * Tables/views with a primary key or unique identifier
    * Permission to create network policies
  </Step>

  <Step title="Elementum access">
    * **Admin privileges** in Elementum to configure CloudLink connections
    * Your organization domain is already whitelisted on the Elementum side (`[your-org].elementum.io`)
  </Step>

  <Step title="A plan for what to grant">
    Identify which databases, schemas, and tables Elementum should access, and which need read/write vs read-only.
  </Step>
</Steps>

## Step 1: Whitelist Elementum IP addresses

Configure your Snowflake network policy to allow connections from Elementum.

| Region      | IP Addresses                                        |
| ----------- | --------------------------------------------------- |
| **US East** | `44.210.166.136`, `44.209.114.114`, `52.72.254.246` |
| **Europe**  | `18.185.13.42`, `63.182.157.140`, `3.65.106.188`    |

Whitelist **all IP addresses listed for your region** -- not just one. Elementum routes traffic across all addresses in the region's pool, so omitting any of them will cause intermittent connection failures. Use the SQL below for your region (or the combined option for multi-region). The network policy is applied to the `ELEMENTUM` user later in [Step 4](#step-4-grant-permissions-and-set-the-network-policy).

<CodeGroup>
  ```sql US region theme={null}
  USE ROLE ACCOUNTADMIN;

  CREATE NETWORK POLICY IF NOT EXISTS ELEMENTUM_ACCESS_POLICY
    ALLOWED_IP_LIST = (
      '44.210.166.136',
      '44.209.114.114',
      '52.72.254.246'
    )
    COMMENT = 'Network policy for Elementum platform access';
  ```

  ```sql Europe region theme={null}
  USE ROLE ACCOUNTADMIN;

  CREATE NETWORK POLICY IF NOT EXISTS ELEMENTUM_ACCESS_POLICY
    ALLOWED_IP_LIST = (
      '18.185.13.42',
      '63.182.157.140',
      '3.65.106.188'
    )
    COMMENT = 'Network policy for Elementum platform access';
  ```

  ```sql Combined (US + Europe) theme={null}
  USE ROLE ACCOUNTADMIN;

  CREATE NETWORK POLICY IF NOT EXISTS ELEMENTUM_ACCESS_POLICY
    ALLOWED_IP_LIST = (
      -- US Region
      '44.210.166.136',
      '44.209.114.114',
      '52.72.254.246',
      -- Europe Region
      '18.185.13.42',
      '63.182.157.140',
      '3.65.106.188'
    )
    COMMENT = 'Network policy for Elementum platform access';
  ```
</CodeGroup>

<Warning>
  Use the combined policy if your organization is configured for multi-region access or if you want to allow connections from both US and Europe.
</Warning>

<Tip>
  If your Elementum organization runs on AWS, you can use [AWS PrivateLink](/administration/snowflake-aws-privatelink) instead of public-internet IP whitelisting so CloudLink traffic between Elementum and your Snowflake account stays on the AWS network.
</Tip>

## Step 2: Get the public key from Elementum

Elementum uses RSA key-pair authentication. The private key stays in Elementum's infrastructure; you assign the public key to your Snowflake service user.

<Steps>
  <Step title="Open CloudLink settings">
    In Elementum, navigate to **Organization Settings** → **CloudLinks**.
  </Step>

  <Step title="Start a new connection">
    Click **+ CloudLink** and select **Snowflake** as the platform.
  </Step>

  <Step title="Copy the public key">
    The RSA public key is displayed in the connection setup dialog. Click **Copy Public Key** to copy it. You'll paste it into the Snowflake setup script in [Step 3](#step-3-run-the-snowflake-setup-script).
  </Step>
</Steps>

<Info>
  Each Elementum environment generates its own unique key pair. If you're setting up [multiple environments](#multi-environment-setup), copy the public key separately from each environment's CloudLink settings.
</Info>

Leave the Elementum dialog open—you'll come back to it in [Step 5](#step-5-add-credentials-in-elementum).

## Step 3: Run the Snowflake setup script

The script below creates the user, role, warehouse, database, and platform schema that Elementum needs. Run each step in order, replacing `<PASTE_PUBLIC_KEY_FROM_ELEMENTUM_UI>` with the key you copied in Step 2.

<Warning>
  This script requires the `ACCOUNTADMIN` role.
</Warning>

<Steps>
  <Step title="Create the ELEMENTUM role">
    ```sql theme={null}
    USE ROLE ACCOUNTADMIN;
    CREATE ROLE IF NOT EXISTS ELEMENTUM;
    GRANT ROLE ELEMENTUM TO ROLE SYSADMIN;
    ```
  </Step>

  <Step title="Create the ELEMENTUM service user">
    ```sql theme={null}
    USE ROLE ACCOUNTADMIN;
    CREATE USER IF NOT EXISTS ELEMENTUM
      TYPE = SERVICE
      RSA_PUBLIC_KEY = '<PASTE_PUBLIC_KEY_FROM_ELEMENTUM_UI>';
    GRANT ROLE ELEMENTUM TO USER ELEMENTUM;
    ```

    Paste the raw public key value without the `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` header/footer lines.
  </Step>

  <Step title="Create the Elementum warehouse">
    ```sql theme={null}
    USE ROLE SYSADMIN;
    CREATE WAREHOUSE IF NOT EXISTS ELEMENTUM
      WITH WAREHOUSE_SIZE = 'MEDIUM',
      MIN_CLUSTER_COUNT = 1,
      MAX_CLUSTER_COUNT = 10,
      AUTO_SUSPEND = 60;

    GRANT USAGE ON WAREHOUSE ELEMENTUM TO ROLE ELEMENTUM;
    ```

    **Defaults:** Medium size, 1–10 clusters with auto-scaling, 60-second auto-suspend. Adjust based on your workload; see [Snowflake warehouses](/administration/snowflake-warehouses) for sizing guidance.
  </Step>

  <Step title="Create the Elementum database and platform schema">
    ```sql theme={null}
    USE ROLE SYSADMIN;
    CREATE DATABASE IF NOT EXISTS ELEMENTUM;
    GRANT OWNERSHIP ON DATABASE ELEMENTUM TO ROLE ELEMENTUM;

    USE ROLE ELEMENTUM;
    USE DATABASE ELEMENTUM;
    CREATE SCHEMA IF NOT EXISTS ELEMENTUM_PLATFORM;
    ```

    <Warning>
      **Do not modify or add tables to the `ELEMENTUM_PLATFORM` schema.** It's reserved for Elementum's internal platform operations. See [The platform schema concept](/administration/cloudlink-overview#the-platform-schema-critical-concept).
    </Warning>
  </Step>

  <Step title="Create a schema for customer data (optional)">
    Use this only if you want a dedicated schema for tables built specifically for Elementum (such as data-exchange tables). Don't put these tables in `ELEMENTUM_PLATFORM`.

    ```sql theme={null}
    USE ROLE ELEMENTUM;
    USE DATABASE ELEMENTUM;
    CREATE SCHEMA IF NOT EXISTS PUBLIC;
    ```
  </Step>
</Steps>

## Step 4: Grant permissions and set the network policy

Apply the network policy you created in Step 1, then grant the `ELEMENTUM` role access to the data you want available in Elementum.

### Apply the network policy

```sql theme={null}
USE ROLE ACCOUNTADMIN;
ALTER USER ELEMENTUM SET NETWORK_POLICY = ELEMENTUM_ACCESS_POLICY;

-- Verify
DESC USER ELEMENTUM;
```

### Grant data access

The pattern is database usage → schema usage → table grants. Choose the access level you need.

```sql theme={null}
USE ROLE SYSADMIN;

-- Database and schema usage
GRANT USAGE ON DATABASE <INSERT_DATABASE_NAME_HERE> TO ROLE ELEMENTUM;
GRANT USAGE ON SCHEMA <INSERT_DATABASE_NAME_HERE>.<INSERT_SCHEMA_NAME_HERE> TO ROLE ELEMENTUM;

-- Table grants (fully qualified)
GRANT INSERT, UPDATE, DELETE, SELECT
  ON TABLE <INSERT_DATABASE_NAME_HERE>.<INSERT_SCHEMA_NAME_HERE>.<INSERT_TABLE_NAME_HERE>
  TO ROLE ELEMENTUM;
```

<Tabs>
  <Tab title="Full access (read/write)">
    ```sql theme={null}
    USE ROLE SYSADMIN;

    GRANT USAGE ON DATABASE SALES_DB TO ROLE ELEMENTUM;
    GRANT USAGE ON SCHEMA SALES_DB.PUBLIC TO ROLE ELEMENTUM;

    GRANT INSERT, UPDATE, DELETE, SELECT ON TABLE SALES_DB.PUBLIC.CUSTOMERS TO ROLE ELEMENTUM;
    GRANT INSERT, UPDATE, DELETE, SELECT ON TABLE SALES_DB.PUBLIC.ORDERS TO ROLE ELEMENTUM;
    ```
  </Tab>

  <Tab title="Read-only">
    ```sql theme={null}
    USE ROLE SYSADMIN;

    GRANT USAGE ON DATABASE REFERENCE_DB TO ROLE ELEMENTUM;
    GRANT USAGE ON SCHEMA REFERENCE_DB.PUBLIC TO ROLE ELEMENTUM;

    GRANT SELECT ON TABLE REFERENCE_DB.PUBLIC.PRODUCTS TO ROLE ELEMENTUM;
    GRANT SELECT ON TABLE REFERENCE_DB.PUBLIC.CATEGORIES TO ROLE ELEMENTUM;
    ```
  </Tab>

  <Tab title="Schema-level (all tables)">
    ```sql theme={null}
    USE ROLE SYSADMIN;

    GRANT USAGE ON DATABASE ANALYTICS_DB TO ROLE ELEMENTUM;
    GRANT USAGE ON SCHEMA ANALYTICS_DB.PUBLIC TO ROLE ELEMENTUM;

    GRANT SELECT ON ALL TABLES IN SCHEMA ANALYTICS_DB.PUBLIC TO ROLE ELEMENTUM;
    GRANT SELECT ON FUTURE TABLES IN SCHEMA ANALYTICS_DB.PUBLIC TO ROLE ELEMENTUM;
    ```
  </Tab>
</Tabs>

### Optional grants

The following grants are only needed if you plan to use the matching capability. Skip any that don't apply.

<AccordionGroup>
  <Accordion title="Enable change tracking (for change-based automations)">
    Required if you want Elementum to start workflows when data is added or updated in Snowflake.

    ```sql theme={null}
    -- Enable change tracking on each table you want to monitor
    ALTER TABLE <DATABASE>.<SCHEMA>.<TABLE> SET CHANGE_TRACKING = TRUE;
    ```

    Example:

    ```sql theme={null}
    ALTER TABLE SALES_DB.PUBLIC.CUSTOMERS SET CHANGE_TRACKING = TRUE;
    ALTER TABLE SALES_DB.PUBLIC.ORDERS SET CHANGE_TRACKING = TRUE;

    -- Verify
    SHOW TABLES LIKE 'CUSTOMERS' IN SCHEMA SALES_DB.PUBLIC;
    ```
  </Accordion>

  <Accordion title="Grant Cortex AI access (for Snowflake Cortex provider, AI Search, AI Automations)">
    Required if you plan to use [Snowflake Cortex as your AI provider](/ai-agents/snowflake-cortex-setup), AI Search, AI Automations, or ML forecasting.

    ```sql theme={null}
    USE ROLE ACCOUNTADMIN;

    -- Enable cross-region Cortex access
    ALTER ACCOUNT SET CORTEX_ENABLED_CROSS_REGION = 'ANY_REGION';

    -- Cortex user role
    GRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE ELEMENTUM;

    -- Cortex Search Service creation
    GRANT CREATE CORTEX SEARCH SERVICE ON SCHEMA ELEMENTUM_PLATFORM TO ROLE ELEMENTUM;

    -- ML model creation
    GRANT CREATE SNOWFLAKE.ML.ANOMALY_DETECTION ON SCHEMA ELEMENTUM.ELEMENTUM_PLATFORM TO ROLE ELEMENTUM;
    GRANT CREATE SNOWFLAKE.ML.CLASSIFICATION   ON SCHEMA ELEMENTUM.ELEMENTUM_PLATFORM TO ROLE ELEMENTUM;
    GRANT CREATE SNOWFLAKE.ML.FORECAST         ON SCHEMA ELEMENTUM.ELEMENTUM_PLATFORM TO ROLE ELEMENTUM;
    ```

    **Capabilities enabled:** anomaly detection, classification, forecasting, LLM access (Cortex Complete), and Cortex Search.
  </Accordion>

  <Accordion title="Grant BI view permissions (for external BI tools)">
    Required only if you plan to expose Elementum data to external BI tools (Power BI, Tableau, Looker) through Elementum's BI view feature.

    ```sql theme={null}
    USE ROLE ACCOUNTADMIN;

    GRANT USAGE ON DATABASE <DB_NAME> TO ROLE ELEMENTUM;
    GRANT USAGE ON SCHEMA  <DB_NAME>.<SCHEMA_NAME> TO ROLE ELEMENTUM;
    GRANT CREATE VIEW ON SCHEMA <DB_NAME>.<SCHEMA_NAME> TO ROLE ELEMENTUM;
    ```

    Example:

    ```sql theme={null}
    USE ROLE ACCOUNTADMIN;

    GRANT USAGE ON DATABASE ANALYTICS_DB TO ROLE ELEMENTUM;
    GRANT USAGE ON SCHEMA ANALYTICS_DB.BI_VIEWS TO ROLE ELEMENTUM;
    GRANT CREATE VIEW ON SCHEMA ANALYTICS_DB.BI_VIEWS TO ROLE ELEMENTUM;
    ```

    Users and BI tools also need separate `SELECT` grants to query the views Elementum creates. See [Tables](/data/tables#snowflake-bi-view-permissions) for the full permission set.

    <Warning>
      **Maintain view ownership:** the `ELEMENTUM` role retains ownership of any BI views it creates. Do not transfer ownership, or Elementum will lose the ability to update or manage the views.
    </Warning>
  </Accordion>
</AccordionGroup>

## Step 5: Add credentials in Elementum

Return to the CloudLink dialog you opened in Step 2.

<Steps>
  <Step title="Enter connection details">
    | Field              | Value                                                                           |
    | ------------------ | ------------------------------------------------------------------------------- |
    | **Name**           | A descriptive name (for example, `Production Snowflake`)                        |
    | **Account URL**    | Your Snowflake account URL (for example, `your-account.snowflakecomputing.com`) |
    | **Username**       | `ELEMENTUM`                                                                     |
    | **Authentication** | RSA Key Pair (configured automatically using the public key from Step 2)        |
    | **Role**           | `ELEMENTUM`                                                                     |
    | **Warehouse**      | `ELEMENTUM`                                                                     |
    | **Schema**         | `ELEMENTUM_PLATFORM` (Elementum's platform schema, **not** your data schema)    |

    <Warning>
      The **Schema** field must be the empty `ELEMENTUM_PLATFORM` schema, not your business data schema. See [The platform schema concept](/administration/cloudlink-overview#the-platform-schema-critical-concept).
    </Warning>
  </Step>

  <Step title="Test the connection">
    Click **Test Connection** to verify credentials, key-pair authentication, and network access end-to-end. A successful test confirms the IP whitelist, public key, role grants, and warehouse usage are all set correctly.
  </Step>

  <Step title="Select tables to integrate">
    Once connected, browse your Snowflake environment:

    1. **Database** — Choose the database that contains your tables.
    2. **Schema** — Pick the schema with your data.
    3. **Table** — Choose the table(s) to bring into Elementum.

    Only databases, schemas, and tables that the `ELEMENTUM` role has access to will appear.

    **Performance check:** when you select a table, Elementum runs a test query to measure response time. If the table responds slowly, you'll see a warning before completing the connection.

    | Warning level | Query time      | Recommendation                                   |
    | ------------- | --------------- | ------------------------------------------------ |
    | Optimal       | Under 3 seconds | Proceed                                          |
    | Moderate      | 3–5 seconds     | Review optimization before proceeding            |
    | Slow          | Over 5 seconds  | Strongly consider optimization before connecting |

    <Warning>
      Slow tables affect workflow execution times, record load times, and automation reliability. See [Snowflake warehouses](/administration/snowflake-warehouses) and [Snowflake table types](/administration/snowflake-table-types) for optimization guidance.
    </Warning>
  </Step>

  <Step title="Add naming and field mapping">
    For each table, set:

    * **App name** — the application this data belongs to
    * **Table display name** — user-friendly name shown in Elementum
    * **Description** — optional context
    * **Primary key** — the unique identifier column
    * **Field mappings** — column-to-field-type, labels, and visibility

    Field types include: Text, Number, Date, Timestamp, Boolean, JSON, Array, Currency, Percentage, and References (for relationships).
  </Step>

  <Step title="Set the resource scheduler">
    The default sync interval is **20 minutes**. Adjust based on data freshness needs:

    * Shorter intervals = fresher data, more Snowflake credits consumed.
    * Longer intervals = lower cost, suitable for slower-changing data.

    <Warning>
      More frequent syncs consume more Snowflake credits. Balance freshness against cost.
    </Warning>
  </Step>

  <Step title="Provision the Query Profile Table">
    After the CloudLink is created, open its details by clicking the CloudLink name on the **CloudLinks** page in **Organization Settings**. In the **Query Profile Table** section, copy the provided DDL and run it in your Snowflake warehouse to provision the dynamic table that backs query-profile lookups. This improves query performance by giving Elementum fast, low-cost access to query execution metrics without scanning `ACCOUNT_USAGE` on every request.

    <Info>
      The DDL requires the `ACCOUNTADMIN` role and grants the `ELEMENTUM` role the permissions it needs to read `SNOWFLAKE.ACCOUNT_USAGE` and execute the scheduled task. Once provisioned, the **Query Profile Table** status updates to **Provisioned** in the CloudLink dialog.
    </Info>

    For more on what the table contains, why it's recommended, and verification and troubleshooting steps, see [Snowflake Query Profile Table](/administration/snowflake-query-profile-table).
  </Step>
</Steps>

## Step 6: Verify the connection

Run these checks in Snowflake to confirm the role, warehouse, and data access work as expected.

<Steps>
  <Step title="Verify role and warehouse">
    ```sql theme={null}
    USE ROLE ELEMENTUM;
    USE WAREHOUSE ELEMENTUM;
    USE DATABASE ELEMENTUM;

    SELECT CURRENT_ROLE(), CURRENT_WAREHOUSE(), CURRENT_DATABASE();
    ```

    Expected: `ELEMENTUM` for all three.
  </Step>

  <Step title="Verify data access">
    ```sql theme={null}
    USE ROLE ELEMENTUM;
    USE WAREHOUSE ELEMENTUM;

    SELECT COUNT(*) FROM SALES_DB.PUBLIC.CUSTOMERS;
    ```

    Replace with your actual database, schema, and table.
  </Step>

  <Step title="Verify change tracking (if enabled)">
    ```sql theme={null}
    SHOW TABLES LIKE 'CUSTOMERS' IN SCHEMA SALES_DB.PUBLIC;
    -- Look for "change_tracking" = "ON"

    SELECT *
    FROM SALES_DB.PUBLIC.CUSTOMERS
    CHANGES(INFORMATION => DEFAULT)
    AT(TIMESTAMP => DATEADD(HOUR, -1, CURRENT_TIMESTAMP()))
    LIMIT 5;
    ```
  </Step>

  <Step title="Verify Cortex access (if enabled)">
    ```sql theme={null}
    USE ROLE ELEMENTUM;
    USE DATABASE ELEMENTUM;
    USE SCHEMA ELEMENTUM_PLATFORM;

    SELECT SNOWFLAKE.CORTEX.COMPLETE(
      'mistral-large',
      'What is machine learning?'
    ) AS response;
    ```

    A successful response confirms Cortex access is configured.
  </Step>

  <Step title="Verify in Elementum">
    1. The connection shows as **Connected** on the CloudLinks page.
    2. The integrated table loads correctly in Elementum.
    3. Creating or updating a record (where write access was granted) syncs back to Snowflake.
  </Step>
</Steps>

## Use the connection

After verification, use the connection from elsewhere in Elementum:

* **Preview data** — On the CloudLinks page, click **Explore** next to the connection to preview rows from any table that connection can access. Useful for confirming the data and column names match what you expect before referencing the table in a workflow.
* **Save Snowflake functions for automations** — Click **Functions** next to the connection, choose the database and schema that contain the function, and select the function to save it. Saved functions are available in the **Run Function** action in [automations](/workflows/automation-system). If a function doesn't appear, confirm the function is set up correctly in Snowflake (see Snowflake's [function reference](https://docs.snowflake.com/en/sql-reference-functions)) and that the `ELEMENTUM` role has the privileges it needs:

  ```sql theme={null}
  GRANT USAGE ON DATABASE DATABASE_NAME TO ROLE ELEMENTUM;
  GRANT USAGE ON SCHEMA DATABASE_NAME.SCHEMA_NAME TO ROLE ELEMENTUM;
  GRANT USAGE ON STAGE DATABASE_NAME.SCHEMA_NAME.STAGE_NAME TO ROLE ELEMENTUM;
  GRANT SELECT ON VIEW DATABASE_NAME.SCHEMA_NAME.VIEW_NAME TO ROLE ELEMENTUM;
  ```

For full capability docs, see [What you can do once connected](/administration/cloudlink-overview#what-you-can-do-once-connected) on the CloudLink Overview.

## Multi-environment setup

If you use [organization environments](/administration/understand-organization-environments) for development, staging, and production, create separate Snowflake resources for each environment.

<Error>
  Every CloudLink and environment combination needs a completely separate `USER`, `ROLE`, `DATABASE`, and `SCHEMA`. Users from one environment must not have access to another environment's database or schema.
</Error>

### Required isolation

| Environment | User                | Role                | Database            | Schema               |
| ----------- | ------------------- | ------------------- | ------------------- | -------------------- |
| Production  | `ELEMENTUM_PROD`    | `ELEMENTUM_PROD`    | `ELEMENTUM_PROD`    | `ELEMENTUM_PLATFORM` |
| Staging     | `ELEMENTUM_STAGING` | `ELEMENTUM_STAGING` | `ELEMENTUM_STAGING` | `ELEMENTUM_PLATFORM` |
| Development | `ELEMENTUM_DEV`     | `ELEMENTUM_DEV`     | `ELEMENTUM_DEV`     | `ELEMENTUM_PLATFORM` |

### Setup script for additional environments

Run this for each environment, replacing `DEV` with your environment name. Copy the public key separately from each Elementum environment's CloudLink settings.

```sql theme={null}
USE ROLE ACCOUNTADMIN;

CREATE ROLE IF NOT EXISTS ELEMENTUM_DEV;
GRANT ROLE ELEMENTUM_DEV TO ROLE SYSADMIN;

CREATE USER IF NOT EXISTS ELEMENTUM_DEV
  TYPE = SERVICE
  RSA_PUBLIC_KEY = '<PASTE_PUBLIC_KEY_FROM_ELEMENTUM_UI>';
GRANT ROLE ELEMENTUM_DEV TO USER ELEMENTUM_DEV;

USE ROLE SYSADMIN;
CREATE WAREHOUSE IF NOT EXISTS ELEMENTUM_DEV
  WITH WAREHOUSE_SIZE = 'MEDIUM',
  MIN_CLUSTER_COUNT = 1,
  MAX_CLUSTER_COUNT = 10,
  AUTO_SUSPEND = 60;
GRANT USAGE ON WAREHOUSE ELEMENTUM_DEV TO ROLE ELEMENTUM_DEV;

CREATE DATABASE IF NOT EXISTS ELEMENTUM_DEV;
GRANT OWNERSHIP ON DATABASE ELEMENTUM_DEV TO ROLE ELEMENTUM_DEV;

USE ROLE ELEMENTUM_DEV;
USE DATABASE ELEMENTUM_DEV;
CREATE SCHEMA IF NOT EXISTS ELEMENTUM_PLATFORM;
```

### Sharing external data across environments (optional)

You **can** grant multiple environment users access to the same external business data tables if you need realistic data for testing.

```sql theme={null}
USE ROLE SYSADMIN;

-- Grant the same business data to both PROD and DEV
GRANT USAGE ON DATABASE BUSINESS_DATA TO ROLE ELEMENTUM_PROD;
GRANT USAGE ON SCHEMA BUSINESS_DATA.PUBLIC TO ROLE ELEMENTUM_PROD;
GRANT SELECT ON ALL TABLES IN SCHEMA BUSINESS_DATA.PUBLIC TO ROLE ELEMENTUM_PROD;

GRANT USAGE ON DATABASE BUSINESS_DATA TO ROLE ELEMENTUM_DEV;
GRANT USAGE ON SCHEMA BUSINESS_DATA.PUBLIC TO ROLE ELEMENTUM_DEV;
GRANT SELECT ON ALL TABLES IN SCHEMA BUSINESS_DATA.PUBLIC TO ROLE ELEMENTUM_DEV;
```

<Warning>
  When environments share access to external data, changes made in one environment are visible in all of them. This is usually fine for read-only reference data; be cautious with shared write access.
</Warning>

## Key rotation

Snowflake supports two simultaneous public keys per user (`RSA_PUBLIC_KEY` and `RSA_PUBLIC_KEY_2`), enabling zero-downtime rotation. Elementum recommends rotating keys every 90 days.

<Steps>
  <Step title="Generate a new key pair in Elementum">
    In **Organization Settings → CloudLinks**, click **Rotate Key** for the connection. Copy the new public key from the dialog.
  </Step>

  <Step title="Assign the new key to the secondary slot">
    ```sql theme={null}
    ALTER USER ELEMENTUM SET RSA_PUBLIC_KEY_2 = '<NEW_PUBLIC_KEY>';
    ```
  </Step>

  <Step title="Verify the new key works">
    Click **Test Connection** in Elementum.
  </Step>

  <Step title="Promote the new key and remove the old one">
    ```sql theme={null}
    ALTER USER ELEMENTUM UNSET RSA_PUBLIC_KEY;
    ALTER USER ELEMENTUM SET RSA_PUBLIC_KEY = '<NEW_PUBLIC_KEY>';
    ALTER USER ELEMENTUM UNSET RSA_PUBLIC_KEY_2;
    ```
  </Step>
</Steps>

<Warning>
  Do not remove the old key before confirming the new key works. Use Snowflake's dual-key support to avoid disrupting active connections.
</Warning>

### Disable password authentication

If the Snowflake user was previously created with a password, disable password-based login after key-pair authentication is confirmed:

```sql theme={null}
ALTER USER ELEMENTUM SET DISABLE_DIRECT_LOGIN = TRUE;
```

This ensures the service account can only be accessed through key-pair authentication.

## Key-pair authentication reference

For background on how key-pair authentication works (private vs public key, why it's required for Cortex features), see [Authentication on the CloudLink Overview](/administration/cloudlink-overview#authentication).

## Troubleshooting

<AccordionGroup>
  <Accordion title="Cannot connect from Elementum">
    * Verify Elementum IP addresses are whitelisted in your Snowflake network policy.
    * Confirm the RSA public key was added correctly to the Snowflake user (run `DESC USER ELEMENTUM` and check `RSA_PUBLIC_KEY_FP`).
    * Check that the `ELEMENTUM` user has the `ELEMENTUM` role granted.
    * Verify the warehouse is not suspended and has available compute.
  </Accordion>

  <Accordion title="&#x22;User does not exist&#x22; error">
    * Ensure the user was created with `TYPE = SERVICE`.
    * Verify the user creation script ran successfully.
    * Check that `ACCOUNTADMIN` was the active role.
  </Accordion>

  <Accordion title="&#x22;JWT token is invalid&#x22; or authentication failures">
    * Verify the public key was copied completely from the Elementum UI without extra whitespace or line breaks.
    * Confirm the key was assigned to the correct user (`DESC USER ELEMENTUM` should show a value for `RSA_PUBLIC_KEY_FP`).
    * Ensure the key was pasted without the `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` lines.
  </Accordion>

  <Accordion title="&#x22;Public key fingerprint mismatch&#x22;">
    The public key in Snowflake doesn't match the private key Elementum holds. Re-copy the public key from Elementum and re-assign:

    ```sql theme={null}
    ALTER USER ELEMENTUM SET RSA_PUBLIC_KEY = '<NEW_PUBLIC_KEY>';
    ```
  </Accordion>

  <Accordion title="Connection stopped working after key rotation">
    * Run `DESC USER ELEMENTUM` to see which key slots have values.
    * Ensure the current Elementum public key is in either `RSA_PUBLIC_KEY` or `RSA_PUBLIC_KEY_2`.
    * Follow the [key rotation procedure](#key-rotation) to safely rotate without downtime.
  </Accordion>

  <Accordion title="Tables not visible in Elementum">
    * **Most likely cause:** you entered your data schema in the **Schema** field instead of `ELEMENTUM_PLATFORM`.
    * Verify `GRANT` statements include all required tables.
    * Confirm database and schema usage is granted on top of table grants.
  </Accordion>

  <Accordion title="&#x22;Insufficient privileges&#x22; errors">
    * Verify `GRANT` statements were executed for all required tables.
    * Check that the role has warehouse usage permission.
    * Confirm database and schema `USAGE` grants exist.
  </Accordion>

  <Accordion title="Cannot use Cortex features">
    * Verify `CORTEX_ENABLED_CROSS_REGION` is set to `'ANY_REGION'`.
    * Confirm `SNOWFLAKE.CORTEX_USER` database role is granted to `ELEMENTUM`.
    * Check that Cortex is available in your Snowflake region.
    * Verify `ACCOUNTADMIN` was used to grant Cortex permissions.
    * Ensure the connection uses key-pair authentication (Cortex features require it).
  </Accordion>

  <Accordion title="Slow query performance or high credit consumption">
    * Increase warehouse size or cluster count.
    * Add clustering keys / partitioning on large tables.
    * Review sync interval—shorter intervals consume more credits.
    * See [Snowflake warehouses](/administration/snowflake-warehouses) and [Snowflake table types](/administration/snowflake-table-types) for tuning guidance.
  </Accordion>
</AccordionGroup>

## Security best practices

<Tabs>
  <Tab title="Least privilege">
    * Grant only the permissions the `ELEMENTUM` role needs.
    * Use read-only access where write access isn't required.
    * Audit granted permissions regularly.
    * Remove access to tables no longer in use.
  </Tab>

  <Tab title="Network">
    * Whitelist Elementum IPs.
    * Use VPN for an additional security layer.
    * Monitor connection logs and set up alerts for suspicious activity.
  </Tab>

  <Tab title="Authentication">
    * Use RSA key-pair authentication, not passwords.
    * Rotate keys every 90 days.
    * Never share the service account credentials outside authorized personnel.
  </Tab>

  <Tab title="Monitoring">
    * Review query history regularly.
    * Monitor warehouse credit consumption.
    * Set up cost alerts in Snowflake.
    * Track data access patterns.
  </Tab>
</Tabs>

## Next steps

<CardGroup cols={2}>
  <Card title="Snowflake warehouses" icon="server" href="/administration/snowflake-warehouses">
    Size and configure your Snowflake warehouse for Elementum workloads.
  </Card>

  <Card title="Snowflake table types" icon="table" href="/administration/snowflake-table-types">
    Choose Standard vs Hybrid tables for the right performance/cost balance.
  </Card>

  <Card title="Snowflake stages" icon="folder" href="/administration/snowflake-stages">
    Process files stored in Snowflake stages with Elementum automations.
  </Card>

  <Card title="License Patrol setup" icon="key" href="/administration/snowflake-license-patrol">
    Additional steps for License Patrol customers.
  </Card>

  <Card title="Snowflake Cortex AI provider" icon="sparkles" href="/ai-agents/snowflake-cortex-setup">
    Use Snowflake Cortex for AI services in Elementum.
  </Card>

  <Card title="Build automations" icon="bolt" href="/workflows/automation-system">
    Create workflows that act on your Snowflake data.
  </Card>
</CardGroup>
