Creating Topics in Omni is a great way to help users navigate your data more easily. Topics bring together relevant tables, joins, measures, and dimensions into a logical structure that matches how business users think about their work.
Topics also help you scope your data such that users are only seeing the data you want them to. This is especially important for AI querying. When users ask questions with AI, their AI assistant runs within the boundaries of a Topic. If a user can’t see data when querying by themselves, their AI assistant can’t see it either. No need to worry about a user asking, “What’s the salary of everyone at my company?” and AI returning that to them. ![]()
In this article, I describe the key parameters you can define in Topics that help you scope your data, making the experience better (and safer) for users and AI. For more best practices, see our docs.
Joins
# Example: Orders Overview topic
base_view: orders
group_label: Sales & Revenue
joins:
inventory_items: # Joins inventory_items to the Topic's base table
products: # Joins products to inventory_items
distribution_centers: {} # Joins distribution_centers to products
users: {} # Joins users to the Topic's base table
Global join relationships are defined in the Relationships file in your Omni model. Here, you can specify what tables should join, how they should be aliased, and their join type (left/right/inner, one-to-one, one-to-many, many-to-many).
Then, in your Topic file, you can call those relationship definitions to show them in the field picker. This also enables AI to know what tables are available and how they should join. This syntax supports nesting joins, so even if your question requires multiple tables, Omni knows how to connect your data (and even avoid fanouts).
(Note: For advanced use cases, you can also define Topic-scoped views and Topic-scoped relationships that are only available in a given Topic.)
Row-level security and topic access
# Example: Orders Overview topic
base_view: orders
group_label: Sales & Revenue
joins:
orders:
user: {}
inventory_items:
products: {}
required_access_grants: [ marketing, finance ]
access_filters:
- field: products.brand # field to use in the filter, specifies the product's brand
user_attribute: customer # user attribute to match to
Access grants and access filters work together to define what data a user can analyze and how much of it they can see.
Access grants act as the first gate. They determine which Topics, views, or fields are available to a user based on user attributes. If a user doesn’t pass that test, that data is completely out of scope.
Once the user passes that gate, access filters apply more granular rules to the data itself. They restrict which rows are available based on user attributes or values in the data, like region, account, or ownership. These filters are enforced every time AI runs a query, before any analysis runs.
In the example above, I’ve limited access to the Orders Overview Topic to only folks in Marketing or Finance. In addition, for users in Marketing or Finance, they can only see the rows of data pertaining to their brand. This setup is particularly important for customer-facing analytics, where you want systematic controls that guard against data leakage.
Filters
# Filter values are visible to users
default_filters:
users.region:
is: Midwest
# Filter values are NOT visible to users
always_where_filters:
users.region:
is: Midwest
# Filter values are NOT visible to users; uses SQL syntax instead of filter syntax
always_where_sql: ${order_items.region} != 'Midwest'
Filters enable you to scope a Topic’s data down to a subset of rows. They operate like WHERE clauses in a SQL statement, automatically removing any rows that match the condition before any SELECT statement is run. Unlike row-level security rules, filters are best used for removing data for everyone using the Topic, not just based on user attributes.
Omni has a few parameters to achieve this:
-
default_filters applies filters that are visible to the user when they run a query or when AI returns an answer, without the user needing to specify anything. For example, when querying a “Midwest Sales” Topic, the query UX or AI result would always show that it has filtered for users.region is Midwest. This is helpful when you want users to be aware that the filter was applied, often to ensure total clarity in the answer they receive.
-
always_where_filters applies filters but does not show the filter value to the user. This is useful in cases when the user doesn’t need to know that a filter was applied, but the filter is necessary to return a correct answer. For example, the data team might apply an always_where_filter for opportunity.is_closed is FALSE on an “Open Pipeline" Topic because, by definition, the Topic should filter out any closed opportunities.
- always_where_SQL operates the same way, except you can use SQL syntax instead of Omni’s filter syntax in the YAML definition. (This is typically used in edge cases when you need to inject some fancy complex SQL instead of filtering based on a single field.)
Dimensions & measures
# Example fields parameter
fields: [all_views.*, -user_facts.*, user_facts.lifetime_value]
ai_fields: [ all_views.*, -tag:dont_use_for_AI, -distribution_centers.* ]
By default, all fields from a Topic’s base view and joined views are included in a Topic. To curate this list, the fields parameter lets you specify which fields will be available for users to select in the field picker and for AI to use. This can be handy when you have wide tables that you use for many purposes and want to cull for use in a given Topic. It’s also useful for excluding fields from a “bridge” table used mainly to join two tables, but you don’t want AI to use them for analysis.
As an additional layer of curation: if you have fields you want included in the Topic but not used for AI, you can use ai_fields. In combination with tags, you can get highly custom with what fields AI uses for analysis.