unity catalog

GuideOperate Unity CatalogStorage

Set managed storage for catalogs and schemas on S3

Give Unity Catalog catalogs and schemas S3 storage roots under an external location, and see where the server places managed volumes and tables.

A storage root on a catalog or schema tells Unity Catalog where to put managed tables and volumes. When you create a managed object, you don't give it a location. The server allocates one under the schema's storage root or, if the schema has none, under the catalog's. On S3, that root must lie inside an external location, because the external location's storage credential is what the server uses to vend access to the managed data. For the difference between managed and external objects, see External tables and catalog-managed Delta tables.

Set up the client

Set up the client for your interface:

Start Python with the SDK:

uv run --with unitycatalog-client==0.7.0 --with obstore==0.11.1 python -m asyncio
managed_storage.pyFull script
from obstore.store import from_url
from unitycatalog.client import (
    ApiClient,
    AwsIamRoleRequest,
    CatalogsApi,
    Configuration,
    CreateCatalog,
    CreateCredentialRequest,
    CreateExternalLocation,
    CreateSchema,
    CreateVolumeRequestContent,
    CredentialPurpose,
    CredentialsApi,
    ExternalLocationsApi,
    GenerateTemporaryVolumeCredential,
    SchemasApi,
    TemporaryCredentialsApi,
    VolumeOperation,
    VolumesApi,
    VolumeType,
)

config = Configuration(host="http://localhost:8080/api/2.1/unity-catalog")

The examples use an external location lake on s3://uc-docs/lake:

managed_storage.pyFull script
await CredentialsApi(api).create_credential(
    CreateCredentialRequest(
        name="lake_storage",
        purpose=CredentialPurpose.STORAGE,
        aws_iam_role=AwsIamRoleRequest(
            role_arn="arn:aws:iam::123456789012:role/uc-storage-lake"
        ),
    )
)
await ExternalLocationsApi(api).create_external_location(
    CreateExternalLocation(
        name="lake", url="s3://uc-docs/lake", credential_name="lake_storage"
    )
)

Create a catalog with a storage root

Pass storage_root when you create the catalog. The server appends its own prefix and the catalog's ID, and returns the result as storage_location:

managed_storage.pyFull script
sales = await CatalogsApi(api).create_catalog(
    CreateCatalog(name="sales", storage_root="s3://uc-docs/lake/sales")
)
print(sales.storage_location)
# s3://uc-docs/lake/sales/__unitystorage/catalogs/<catalog id>

The root can be the external location's URL or any path below it. Several catalogs can share one root, since each gets its own ID-named directory.

The root is fixed when the catalog is created. The update API has no storage_root field. To move managed data, create a new catalog and copy it.

Required privileges: ownership of the metastore or CREATE CATALOG on it, plus ownership of the external location or CREATE MANAGED STORAGE on it.

Give a schema its own storage root

A schema without a root uses its catalog's. Give a schema its own root when its data belongs in a different prefix, for example one with a different storage role or retention policy:

managed_storage.pyFull script
schemas = SchemasApi(api)
await schemas.create_schema(CreateSchema(name="raw", catalog_name="sales"))
curated = await schemas.create_schema(
    CreateSchema(
        name="curated",
        catalog_name="sales",
        storage_root="s3://uc-docs/lake/curated",
    )
)
print(curated.storage_location)
# s3://uc-docs/lake/curated/__unitystorage/schemas/<schema id>

A schema's root doesn't have to be under its catalog's root. Here curated uses a sibling prefix inside the same external location.

Required privileges: ownership of the catalog, or USE CATALOG and CREATE SCHEMA on it. For a root, also ownership of the external location or CREATE MANAGED STORAGE on it.

Create managed objects

Create a managed volume without a location, and the server picks the nearest root:

managed_storage.pyFull script
volumes = VolumesApi(api)
drops = await volumes.create_volume(
    CreateVolumeRequestContent(
        catalog_name="sales",
        schema_name="raw",
        name="drops",
        volume_type=VolumeType.MANAGED,
    )
)
reports = await volumes.create_volume(
    CreateVolumeRequestContent(
        catalog_name="sales",
        schema_name="curated",
        name="reports",
        volume_type=VolumeType.MANAGED,
    )
)
print(drops.storage_location)  # under the catalog's root
print(reports.storage_location)  # under the schema's root

Managed paths have these forms:

Object's schemaManaged volume path
Has no root (sales.raw)<catalog root>/__unitystorage/catalogs/<catalog id>/volumes/<volume id>
Has a root (sales.curated)<schema root>/__unitystorage/schemas/<schema id>/volumes/<volume id>

Managed tables follow the same pattern with tables/<table id>. Engines create them through the catalog. See Create and update a catalog-managed Delta table.

Clients reach managed data with vended credentials, the same way as for external data:

managed_storage.pyFull script
vended = await TemporaryCredentialsApi(
    api
).generate_temporary_volume_credentials(
    GenerateTemporaryVolumeCredential(
        volume_id=reports.volume_id, operation=VolumeOperation.WRITE_VOLUME
    )
)
aws = vended.aws_temp_credentials
store = from_url(
    vended.url,
    access_key_id=aws.access_key_id,
    secret_access_key=aws.secret_access_key,
    session_token=aws.session_token,
    region="us-east-1",
)
await obstore.put_async(
    store, "2026-10/daily.csv", b"day,total\n2026-10-01,42\n"
)

The __unitystorage prefix belongs to the server. An external table or volume whose location contains it is rejected with Input path '…' contains managed storage prefix __unitystorage.:

The example script checks this rejection in _check_reserved_prefix.

Delete a catalog with managed storage

managed_storage.pyFull script
await CatalogsApi(api).delete_catalog(name="sales", force=True)

force deletes the catalog's schemas and everything in them. Deleting the catalog doesn't delete the external location or the storage credential.

Required privileges: ownership of the catalog or of the metastore.

Next steps