Guide · AWS Lake Formation · LF-Tags

MCP Server Lake Formation LF-Tags — TBAC, CreateLFTagCommand, tag expressions

LF-tags (Lake Formation tag-based access control, or TBAC) replace per-resource permission grants with a tag-matching system: you define key+value tags, attach them to databases and tables, and grant IAM principals access to any resource that matches a tag expression. From an MCP server you create tags with CreateLFTagCommand, assign them to resources with AddLFTagsToResourceCommand, and issue permissions via GrantPermissionsCommand using the LFTagPolicy resource type. The critical insight is that an LFTagPolicy grant is dynamic — when you tag a new table with environment=prod, any principal that holds a grant on LFTagPolicy { Expression: [{ TagKey: 'environment', TagValues: ['prod'] }] } automatically gains access to that table without a new GrantPermissionsCommand call.

TL;DR

Create a tag key with allowed values using CreateLFTagCommand({ TagKey: 'environment', TagValues: ['prod', 'staging'] }). Assign it to a table: AddLFTagsToResourceCommand({ Resource: { Table: { DatabaseName, Name } }, LFTags: [{ TagKey: 'environment', TagValues: ['prod'] }] }). Grant a role access to all prod tables: GrantPermissionsCommand({ Resource: { LFTagPolicy: { ResourceType: 'TABLE', Expression: [{ TagKey: 'environment', TagValues: ['prod'] }] } }, Permissions: ['SELECT'] }). Any table later tagged environment=prod is instantly accessible to that role — no new grant needed.

How LF-tags work

An LF-tag has a key (a string name like environment or data-classification) and a list of allowed values (the set of valid strings like ['prod', 'staging', 'dev']). You assign one or more values from the allowed set to a Glue database, table, or column. Then instead of granting permissions directly to resources, you grant permissions on a tag expression — a list of tag conditions that the resource must satisfy.

ConceptExampleNotes
Tag keyenvironmentCreated with CreateLFTagCommand; unique per account+region+catalog
Tag valueprodMust be in the tag's allowed values list; assigned to resources
Tag expression (AND)[{ TagKey: 'env', TagValues: ['prod'] }, { TagKey: 'tier', TagValues: ['gold'] }]Both conditions must match — resource must have env=prod AND tier=gold
Tag expression (OR within key)[{ TagKey: 'env', TagValues: ['prod', 'staging'] }]Resource must have env=prod OR env=staging
ResourceType'DATABASE' or 'TABLE'Determines which Glue objects are matched by the expression

When you tag a database with environment=prod, that tag propagates to all tables in the database as a parent tag. Tables can override the inherited tag by setting their own value. This propagation means you can tag once at the database level and have permissions apply automatically to all current and future tables in that database.

Creating and managing LF-tag keys

CreateLFTagCommand defines a new tag key with its set of allowed values. UpdateLFTagCommand adds or removes values from an existing key. You cannot remove a value that is currently assigned to any resource — Lake Formation returns ConstraintViolationException if you try.

import {
  LakeFormationClient,
  CreateLFTagCommand,
  UpdateLFTagCommand,
  DeleteLFTagCommand,
  GetLFTagCommand,
  ListLFTagsCommand,
} from '@aws-sdk/client-lakeformation';

const lakeFormation = new LakeFormationClient({ region: process.env.AWS_REGION ?? 'us-east-1' });

export async function createTag(
  key: string,
  values: string[],
): Promise<void> {
  await lakeFormation.send(new CreateLFTagCommand({
    TagKey: key,       // e.g. 'environment'
    TagValues: values, // e.g. ['prod', 'staging', 'dev']
  }));
}

// Add new allowed values to an existing tag
export async function addTagValues(
  key: string,
  newValues: string[],
): Promise<void> {
  await lakeFormation.send(new UpdateLFTagCommand({
    TagKey: key,
    TagValuesToAdd: newValues,
    // TagValuesToDelete: [] — omit to leave existing values unchanged
  }));
}

// Remove allowed values (fails if any resource still uses that value)
export async function removeTagValues(
  key: string,
  valuesToRemove: string[],
): Promise<void> {
  await lakeFormation.send(new UpdateLFTagCommand({
    TagKey: key,
    TagValuesToDelete: valuesToRemove,
  }));
}

export async function getTag(key: string) {
  const response = await lakeFormation.send(new GetLFTagCommand({ TagKey: key }));
  return { key: response.TagKey, values: response.TagValues };
}

export async function listAllTags() {
  const tags = [];
  let nextToken: string | undefined;

  do {
    const response = await lakeFormation.send(
      new ListLFTagsCommand({ NextToken: nextToken, ResourceShareType: 'ALL' })
    );
    tags.push(...(response.LFTags ?? []));
    nextToken = response.NextToken;
  } while (nextToken);

  return tags;
}

DeleteLFTagCommand permanently removes a tag key and all its assignments from every resource. This is irreversible and cannot be undone. Any LFTagPolicy grants that reference this key become orphaned — they remain in the system but match nothing. Remove all dependent grants before deleting a tag key.

export async function deleteTag(key: string): Promise<void> {
  // Warning: this is irreversible and silently orphans LFTagPolicy grants
  // that reference this key. Revoke those grants first.
  await lakeFormation.send(new DeleteLFTagCommand({ TagKey: key }));
}

Assigning tags to resources

AddLFTagsToResourceCommand assigns one or more tag key+value pairs to a Glue database, table, or set of columns. The value you assign must be in the tag's allowed values list — Lake Formation rejects values not in the list.

import {
  AddLFTagsToResourceCommand,
  RemoveLFTagsFromResourceCommand,
  GetResourceLFTagsCommand,
} from '@aws-sdk/client-lakeformation';

// Tag a database (tags propagate to all tables as parent tags)
export async function tagDatabase(
  databaseName: string,
  tags: Array<{ key: string; value: string }>,
): Promise<void> {
  await lakeFormation.send(new AddLFTagsToResourceCommand({
    Resource: {
      Database: { Name: databaseName },
    },
    LFTags: tags.map(t => ({
      TagKey: t.key,
      TagValues: [t.value],  // assign exactly one value per key per resource
    })),
  }));
}

// Tag a specific table
export async function tagTable(
  databaseName: string,
  tableName: string,
  tags: Array<{ key: string; value: string }>,
): Promise<void> {
  await lakeFormation.send(new AddLFTagsToResourceCommand({
    Resource: {
      Table: { DatabaseName: databaseName, Name: tableName },
    },
    LFTags: tags.map(t => ({ TagKey: t.key, TagValues: [t.value] })),
  }));
}

// Get all tags assigned to a resource (including inherited parent tags)
export async function getResourceTags(
  databaseName: string,
  tableName?: string,
) {
  const resource = tableName
    ? { Table: { DatabaseName: databaseName, Name: tableName } }
    : { Database: { Name: databaseName } };

  const response = await lakeFormation.send(new GetResourceLFTagsCommand({
    Resource: resource,
    ShowAssignedLFTags: true,  // include inherited tags from parent databases
  }));

  return {
    // Tags directly assigned to this resource
    lf_tags_on_database: response.LFTagsOnDatabase,
    lf_tags_on_table: response.LFTagsOnTable,
    lf_tags_on_columns: response.LFTagsOnColumns,
  };
}

// Remove a tag from a resource
export async function removeTagFromTable(
  databaseName: string,
  tableName: string,
  tagKey: string,
): Promise<void> {
  // First get the current value so we know what to remove
  const current = await lakeFormation.send(new GetResourceLFTagsCommand({
    Resource: { Table: { DatabaseName: databaseName, Name: tableName } },
    ShowAssignedLFTags: true,
  }));

  const tableTag = current.LFTagsOnTable?.find(t => t.TagKey === tagKey);
  if (!tableTag) return;  // tag not assigned to this resource

  await lakeFormation.send(new RemoveLFTagsFromResourceCommand({
    Resource: { Table: { DatabaseName: databaseName, Name: tableName } },
    LFTags: [{ TagKey: tagKey, TagValues: tableTag.TagValues ?? [] }],
  }));
}

Granting permissions via LFTagPolicy

A LFTagPolicy grant gives a principal access to any resource whose tags match the expression. The ResourceType field ('DATABASE' or 'TABLE') scopes the match to Glue databases or tables respectively. Matching is dynamic — newly tagged resources are automatically included without requiring a new grant.

import { GrantPermissionsCommand } from '@aws-sdk/client-lakeformation';

// Grant SELECT on all prod tables to an analytics role
export async function grantProdTablesSelect(
  principalArn: string,
): Promise<void> {
  await lakeFormation.send(new GrantPermissionsCommand({
    Principal: { DataLakePrincipalIdentifier: principalArn },
    Resource: {
      LFTagPolicy: {
        ResourceType: 'TABLE',
        Expression: [
          {
            TagKey: 'environment',
            TagValues: ['prod'],
            // Matches any table tagged environment=prod
          },
        ],
      },
    },
    Permissions: ['SELECT', 'DESCRIBE'],
    PermissionsWithGrantOption: [],
  }));
}

// Multi-condition expression: prod AND gold tier (both must match — AND logic)
export async function grantProdGoldTablesSelect(
  principalArn: string,
): Promise<void> {
  await lakeFormation.send(new GrantPermissionsCommand({
    Principal: { DataLakePrincipalIdentifier: principalArn },
    Resource: {
      LFTagPolicy: {
        ResourceType: 'TABLE',
        Expression: [
          { TagKey: 'environment', TagValues: ['prod'] },  // AND
          { TagKey: 'tier',        TagValues: ['gold'] },  // AND
          // Both conditions must match — this is NOT an OR
        ],
      },
    },
    Permissions: ['SELECT'],
    PermissionsWithGrantOption: [],
  }));
}

// OR within a single key: prod OR staging
export async function grantProdOrStagingSelect(
  principalArn: string,
): Promise<void> {
  await lakeFormation.send(new GrantPermissionsCommand({
    Principal: { DataLakePrincipalIdentifier: principalArn },
    Resource: {
      LFTagPolicy: {
        ResourceType: 'TABLE',
        Expression: [
          {
            TagKey: 'environment',
            TagValues: ['prod', 'staging'],
            // Multiple values in a single entry = OR within that key
          },
        ],
      },
    },
    Permissions: ['SELECT', 'DESCRIBE'],
    PermissionsWithGrantOption: [],
  }));
}

The expression evaluation rule: within a single LFTagExpression entry, multiple TagValues are evaluated as OR. Multiple entries in the Expression array are evaluated as AND. So [{ key: 'env', values: ['prod', 'staging'] }, { key: 'tier', values: ['gold'] }] means: (env=prod OR env=staging) AND tier=gold.

Revoking and listing tag-based grants

Use RevokePermissionsCommand with the same LFTagPolicy structure to revoke a tag-based grant. Use ListPermissionsCommand with Resource: { LFTagPolicy: {...} } to find which principals hold grants on a specific tag expression — but note that this requires specifying the exact expression; there is no "list all LFTagPolicy grants" API that returns all expressions without filtering.

import {
  RevokePermissionsCommand,
  ListPermissionsCommand,
} from '@aws-sdk/client-lakeformation';

export async function revokeProdTablesSelect(principalArn: string): Promise<void> {
  await lakeFormation.send(new RevokePermissionsCommand({
    Principal: { DataLakePrincipalIdentifier: principalArn },
    Resource: {
      LFTagPolicy: {
        ResourceType: 'TABLE',
        Expression: [{ TagKey: 'environment', TagValues: ['prod'] }],
      },
    },
    Permissions: ['SELECT', 'DESCRIBE'],
    PermissionsWithGrantOption: [],
  }));
}

// List all principals with a tag-based grant on prod tables
export async function listProdTableGrantees() {
  const grants = [];
  let nextToken: string | undefined;

  do {
    const response = await lakeFormation.send(new ListPermissionsCommand({
      Resource: {
        LFTagPolicy: {
          ResourceType: 'TABLE',
          Expression: [{ TagKey: 'environment', TagValues: ['prod'] }],
        },
      },
      NextToken: nextToken,
    }));
    grants.push(...(response.PrincipalResourcePermissions ?? []));
    nextToken = response.NextToken;
  } while (nextToken);

  return grants;
}

IAM reference for LF-tag operations

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "LFTagManagement",
      "Effect": "Allow",
      "Action": [
        "lakeformation:CreateLFTag",
        "lakeformation:UpdateLFTag",
        "lakeformation:DeleteLFTag",
        "lakeformation:GetLFTag",
        "lakeformation:ListLFTags",
        "lakeformation:AddLFTagsToResource",
        "lakeformation:RemoveLFTagsFromResource",
        "lakeformation:GetResourceLFTags",
        "lakeformation:SearchTablesByLFTags",
        "lakeformation:SearchDatabasesByLFTags"
      ],
      "Resource": "*"
    },
    {
      "Sid": "LFTagPolicyGrants",
      "Effect": "Allow",
      "Action": [
        "lakeformation:GrantPermissions",
        "lakeformation:RevokePermissions",
        "lakeformation:ListPermissions"
      ],
      "Resource": "*"
    }
  ]
}

Error handling reference

ErrorCauseFix
AlreadyExistsExceptionCreateLFTagCommand with a key that already existsUse GetLFTagCommand first to check; use UpdateLFTagCommand to add values to existing key
EntityNotFoundExceptionTag key referenced in AddLFTagsToResourceCommand does not existCreate the tag key first with CreateLFTagCommand
InvalidInputExceptionTag value not in the allowed values list for that keyUse GetLFTagCommand to verify allowed values before assigning; use UpdateLFTagCommand to add new values
ConstraintViolationExceptionDeleteLFTagCommand or UpdateLFTagCommand removing a value still assigned to resourcesUse SearchTablesByLFTagsCommand to find resources using that value and RemoveLFTagsFromResourceCommand on each before deleting
AccessDeniedExceptionMissing lakeformation:CreateLFTag or lakeformation:AddLFTagsToResourceAdd required actions to MCP server IAM policy; only data lake admins can create/delete tags by default

Related pages