<a id="cloud-tableflow-integrate-with-snowflake-horizon-catalog"></a>

# Integrate Tableflow with Snowflake Horizon Catalog in Confluent Cloud

Snowflake Horizon Catalog is the recommended way to consume Tableflow tables
in Snowflake. The integration works through catalog federation.

Snowflake uses a catalog-linked database to connect directly to the
Tableflow Apache Iceberg™ REST Catalog. It automatically discovers every Bring
Your Own Storage (BYOS) Tableflow table in an Apache Kafka® cluster and reads the
data from your storage bucket. Snowflake treats Tableflow tables as
read-only. You don’t configure anything in Tableflow, Tableflow doesn’t
copy metadata into a second catalog, and Snowflake needs no per-table setup.

This approach supersedes
[Snowflake Open Catalog synchronization](integrate-with-snowflake-open-catalog-or-apache-polaris.md#cloud-tableflow-integrate-with-snowflake-open-catalog)
for new deployments. Snowflake no longer offers Open Catalog sign-up to new
accounts and directs them to Horizon Catalog. For more information,
see the [Snowflake Open Catalog overview](https://docs.snowflake.com/en/user-guide/opencatalog/overview).

## How the integration works

- A Snowflake catalog integration object connects to the Tableflow REST
  Catalog endpoint, authenticating with a Tableflow-scoped Confluent Cloud API key
  by using the OAuth 2.0 client credentials flow. Snowflake obtains and
  refreshes tokens automatically.
- A Snowflake catalog-linked database built on that integration polls the
  catalog, with a default interval of 30 seconds. The Kafka cluster appears as
  a schema, and every BYOS Tableflow table materializes as an externally
  managed Iceberg table. Newly enabled topics appear automatically.
- Snowflake reads data from your BYOS bucket through a read-only external
  volume backed by an IAM role that you own. The Tableflow REST Catalog
  doesn’t vend storage credentials for BYOS buckets, so you must create the
  external volume.
- Tableflow remains the only writer and performs all table maintenance,
  including compaction and snapshot management. Snowflake is a zero-copy,
  read-only consumer.

## Prerequisites

- A Tableflow-enabled topic in Iceberg format with status `Running`,
  stored in a BYOS Amazon S3 bucket. Horizon Catalog can’t federate topics
  on Confluent-managed storage, and these steps don’t cover Azure Data Lake
  Storage.
- A Tableflow API key, created with
  `confluent api-key create --resource tableflow`. The key’s principal must
  have permission to list and read every topic you want Snowflake to
  discover. For more information, see [Grant Role-Based Access for Tableflow in Confluent Cloud](../../operate/tableflow-rbac.md#tableflow-rbac).
- A Snowflake role with the `CREATE INTEGRATION`,
  `CREATE EXTERNAL VOLUME`, and `CREATE DATABASE` account privileges.
- Permission to create an IAM policy and role in the AWS account that owns
  the BYOS bucket.

## Step 1: Create an IAM policy for the BYOS bucket

In the AWS account that owns the bucket, create a read-only IAM policy. In
the AWS console, navigate to **IAM > Policies > Create policy > JSON**, and
save the following policy as `snowflake-tableflow-read-policy`. If the bucket
uses SSE-KMS, also allow `kms:Decrypt` on the bucket’s KMS key.

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {"Effect": "Allow", "Action": ["s3:GetObject", "s3:GetObjectVersion"],
     "Resource": "arn:aws:s3:::<bucket>/*"},
    {"Effect": "Allow", "Action": ["s3:ListBucket", "s3:GetBucketLocation"],
     "Resource": "arn:aws:s3:::<bucket>"}
  ]
}
```

## Step 2: Create the IAM role

In the AWS console, navigate to **IAM > Roles > Create role > Custom trust
policy**. Start with a placeholder trust policy that trusts your own account.
You replace the principal in Step 4. Attach the policy from Step 1 and name
the role `snowflake-tableflow-read-role`. Choose your external ID now. Use a
unique, hard-to-guess value, such as a UUID, to protect the role against
confused-deputy access. Pinning the external ID enables you to author the
final trust policy in one pass.

```json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"AWS": "arn:aws:iam::<aws-account-id>:root"},
    "Action": "sts:AssumeRole",
    "Condition": {"StringEquals": {"sts:ExternalId": "<pinned-external-id>"}}
  }]
}
```

## Step 3: Create the external volume in Snowflake

Point `STORAGE_BASE_URL` at the bucket root so one volume covers every
current and future Tableflow table in the bucket. The catalog-linked
database validates every table against this base path, so store every BYOS
Tableflow table in the Kafka cluster in this bucket. Keep
`ALLOW_WRITES = FALSE`.

```sql
CREATE EXTERNAL VOLUME tableflow_byos_vol
  STORAGE_LOCATIONS = ((
    NAME = '<bucket>-byos',
    STORAGE_PROVIDER = 'S3',
    STORAGE_BASE_URL = 's3://<bucket>/',
    STORAGE_AWS_ROLE_ARN = 'arn:aws:iam::<aws-account-id>:role/snowflake-tableflow-read-role',
    STORAGE_AWS_EXTERNAL_ID = '<pinned-external-id>'
  ))
  ALLOW_WRITES = FALSE;

DESCRIBE EXTERNAL VOLUME tableflow_byos_vol;
```

From the `DESCRIBE` output, note the `STORAGE_AWS_IAM_USER_ARN` value for
the trust policy in the next step.

## Step 4: Grant Snowflake access to the IAM role

In AWS, edit the trust relationship of `snowflake-tableflow-read-role` and
replace the placeholder principal with the `STORAGE_AWS_IAM_USER_ARN` value
from Step 3, keeping the pinned external ID.

```json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"AWS": "<STORAGE_AWS_IAM_USER_ARN>"},
    "Action": "sts:AssumeRole",
    "Condition": {"StringEquals": {"sts:ExternalId": "<pinned-external-id>"}}
  }]
}
```

Verify the data path from Snowflake. Because the volume doesn’t allow writes,
the read and write sub-checks report `UNVERIFIED`. You can ignore these
results.

```sql
SELECT SYSTEM$VERIFY_EXTERNAL_VOLUME('tableflow_byos_vol');
```

## Step 5: Create the catalog integration

Use `ACCESS_DELEGATION_MODE = EXTERNAL_VOLUME_CREDENTIALS`. Don’t use
`VENDED_CREDENTIALS`. The Tableflow REST Catalog doesn’t vend storage
credentials to Snowflake, so that mode passes verification but fails at
database creation. You can’t change the mode on an existing integration, so
if you created one with `VENDED_CREDENTIALS`, drop it and create a new one.

The Kafka cluster ID is the catalog namespace. Copy `<region>`,
`<org-id>`, and `<env-id>` from the **REST Catalog Endpoint** shown in the
**API access** section of the Tableflow page in Confluent Cloud Console.
`OAUTH_TOKEN_URI` is that same endpoint with `/v1/oauth/tokens` appended.

```sql
CREATE CATALOG INTEGRATION tableflow_horizon_int
  CATALOG_SOURCE = ICEBERG_REST
  TABLE_FORMAT = ICEBERG
  CATALOG_NAMESPACE = '<kafka-cluster-id>'
  REST_CONFIG = (
    CATALOG_URI = 'https://tableflow.<region>.aws.confluent.cloud/iceberg/catalog/organizations/<org-id>/environments/<env-id>',
    CATALOG_API_TYPE = PUBLIC,
    ACCESS_DELEGATION_MODE = EXTERNAL_VOLUME_CREDENTIALS
  )
  REST_AUTHENTICATION = (
    TYPE = OAUTH,
    OAUTH_TOKEN_URI = 'https://tableflow.<region>.aws.confluent.cloud/iceberg/catalog/organizations/<org-id>/environments/<env-id>/v1/oauth/tokens',
    OAUTH_CLIENT_ID = '<tableflow-api-key>',
    OAUTH_CLIENT_SECRET = '<tableflow-api-secret>',
    OAUTH_ALLOWED_SCOPES = ('catalog')
  )
  ENABLED = TRUE;

SELECT SYSTEM$VERIFY_CATALOG_INTEGRATION('tableflow_horizon_int');
```

The verification returns `"success" : true` when the connection works.

## Step 6: Create the catalog-linked database

Create one linked database per Kafka cluster, scoped to that cluster’s
namespace. The statement validates live against the catalog and the volume,
so allow 30 to 60 seconds.

```sql
CREATE DATABASE tableflow_linked_db
  LINKED_CATALOG = (
    CATALOG = 'tableflow_horizon_int',
    ALLOWED_NAMESPACES = ('<kafka-cluster-id>'),
    SYNC_INTERVAL_SECONDS = 30
  )
  EXTERNAL_VOLUME = 'tableflow_byos_vol';

SELECT SYSTEM$CATALOG_LINK_STATUS('tableflow_linked_db');
SHOW ICEBERG TABLES IN DATABASE tableflow_linked_db;
```

Every BYOS Tableflow table in the cluster that’s stored under the volume’s
base path materializes within a sync cycle.

## Query the tables

Tables surface under a schema named after the Kafka cluster ID, one table per
topic. User-defined namespaces configured for external catalog integrations
don’t apply, because Horizon Catalog reads the Tableflow Iceberg REST
Catalog directly. Snowflake uppercases unquoted identifiers, so quote the
namespace and table name exactly as they appear in Tableflow.

```sql
SELECT * FROM tableflow_linked_db."<kafka-cluster-id>"."<topic-name>" LIMIT 100;
```

A newly enabled topic shows its schema after the next catalog sync but returns
rows only after the first Tableflow data commit. From then on, freshness
tracks the Tableflow commit cadence plus the sync interval. For more
information, see
[Query Iceberg Tables with Snowflake](../query-engines/query-with-snowflake.md#cloud-tableflow-query-with-snowflake).

## Limitations

- BYOS only: tables on Confluent-managed storage can’t be consumed through
  this method, because you can’t grant IAM access to Confluent-owned
  buckets, and the catalog doesn’t vend credentials to Snowflake.
- One bucket per catalog-linked database: Snowflake validates every table
  against the external volume’s base path, so store every BYOS Tableflow
  table in the Kafka cluster in this bucket.
- Read-only in Snowflake: the federated tables are externally managed, with
  no cloning, no materialized views on tables protected by row access
  policies, and no DML.

Snowflake governance features, including masking policies, row access
policies, and tags, apply to the federated tables inside Snowflake.

## Related content

- [Query Iceberg Tables with Snowflake](../query-engines/query-with-snowflake.md#cloud-tableflow-query-with-snowflake)
- [Integrate with AWS Glue Catalog](integrate-with-aws-glue-catalog.md#cloud-tableflow-integrate-with-aws-glue-catalog)
- [Integrate with Snowflake Open Catalog](integrate-with-snowflake-open-catalog-or-apache-polaris.md#cloud-tableflow-integrate-with-snowflake-open-catalog)
- [Integrate Catalogs with Tableflow in Confluent Cloud](overview.md#cloud-tableflow-how-to-guides-catalog-integration)

#### NOTE
This website includes content developed at the [Apache Software Foundation](https://www.apache.org/)
under the terms of the [Apache License v2](https://www.apache.org/licenses/LICENSE-2.0.html).
