Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
The SDK supports create, update, and delete (CUD) operations for custom tables and columns, optional solution association, plus retrieve and list table definitions.
Let's look at example code for working with a custom table.
# Create a custom table, including the customization prefix value in the schema names for the table and columns.
table_info = client.tables.create("new_Product", {
"new_Code": "string",
"new_Description": "memo",
"new_Price": "decimal",
"new_Active": "bool"
})
# Create with custom primary column name and solution assignment
table_info = client.tables.create(
"new_Product",
columns={
"new_Code": "string",
"new_Price": "decimal"
},
solution="MyPublisher", # Optional: add to specific solution
primary_column="new_ProductName", # Optional: custom primary column (default is "{customization prefix value}_Name")
)
# Get table information
info = client.tables.get("new_Product")
print(f"Logical name: {info['table_logical_name']}")
print(f"Entity set: {info['entity_set_name']}")
# List all tables
tables = client.tables.list()
for table in tables:
print(table)
# Add columns to existing table (columns must include customization prefix value)
client.tables.add_columns("new_Product", {"new_Category": "string"})
# Remove columns
client.tables.remove_columns("new_Product", ["new_Category"])
# List all columns (attributes) for a table to discover schema
columns = client.tables.list_columns("account")
for col in columns:
print(f"{col['LogicalName']} ({col.get('AttributeType')})")
# List only specific properties
columns = client.tables.list_columns(
"account",
select=["LogicalName", "SchemaName", "AttributeType"],
filter="AttributeType eq 'String'",
)
# Clean up
client.tables.delete("new_Product")
Supported column types
The following type strings are accepted by create() and add_columns().
| Type | Accepted aliases |
|---|---|
string |
text |
memo |
multiline |
int |
integer |
decimal |
money |
float |
double |
bool |
boolean |
datetime |
date |
file |
— |
For optionset (choice) columns, pass an IntEnum subclass (or an Enum whose members have integer values) directly as the column type value instead of a string. The SDK uses the class members to define the optionset values.
from enum import IntEnum
class Priority(IntEnum):
LOW = 1
MEDIUM = 2
HIGH = 3
table_info = client.tables.create("new_Task", {
"new_Title": "string",
"new_Priority": Priority, # optionset column
})
Set column constraints
To set constraints such as length, numeric range, precision, format, required level, or display name, pass a dictionary instead of a bare type string. The type key holds the column type, and the remaining keys set the constraints.
| Key | Applies to | Description |
|---|---|---|
max_length |
string, memo |
Maximum number of characters. |
min_value, max_value |
int, decimal, money, float |
Allowed numeric range. |
precision |
decimal, money, float |
Number of decimal places. |
format |
string, int, datetime |
Format name for text columns (for example, Email, Url, or Phone), or the format for integer and date/time columns. |
required |
all | Required level: None, Recommended, or ApplicationRequired. |
display_name |
all | Display label shown in the maker portal and apps. |
# Pass a dict spec to set constraints; a bare type string still works for simple columns.
client.tables.create("new_Feedback", {
"new_Rating": {"type": "int", "min_value": 1, "max_value": 5},
"new_Comment": {"type": "memo", "max_length": 2000, "display_name": "Comment"},
})
You can use dict specs anywhere a column type is accepted, including add_columns() and batch column creation.
Update column definitions
Use update_column to change one column's constraints, or update_columns to change several in a single call. Both accept the same override keys as create. The SDK validates every specification before it sends any request, so an invalid entry fails the whole call without leaving earlier columns changed.
# Widen one column
client.tables.update_column("new_Feedback", "new_Comment", {"max_length": 4000})
# Update several columns at once
client.tables.update_columns("new_Feedback", {
"new_Rating": {"max_value": 10},
"new_Comment": {"display_name": "Customer Comment"},
})
An update retrieves the complete column definition, applies your changes, and sends the full definition back to Dataverse with the MSCRM.MergeLabels header. Labels in other languages are preserved, so changing one property (for example, max_length) leaves the rest of the column unchanged.
Read typed column metadata
By default, list_columns and get_column return the base attribute metadata. Pass typed=True to retrieve the type-specific definition in a single request, which includes properties such as MaxLength for text columns or MinValue and MaxValue for numeric columns.
# One column, with its type-specific fields
col = client.tables.get_column("new_Feedback", "new_Comment", typed=True)
print(col["MaxLength"]) # 4000
# All columns, each with type-specific fields
cols = client.tables.list_columns("new_Feedback", typed=True)
Note
The filter parameter applies only to the default (typed=False) listing. Combining filter with typed=True raises a ValueError.
TableInfo return object
The client.tables.create() method returns a TableInfo object. Access its properties directly, or use legacy dict-key notation for backward compatibility.
table_info = client.tables.create("new_Product", {"new_Code": "string"})
print(table_info.schema_name) # new_Product
print(table_info.logical_name) # new_product
print(table_info.entity_set_name) # new_products
print(table_info.columns_created) # ['new_Code', ...]
# Legacy dict-key access still works
print(table_info["table_schema_name"])
The add_columns() and remove_columns() methods return the list of column schema names that they create or remove. The get() method returns table metadata, or None if the table doesn't exist, which makes it useful for existence checks.
Alternate keys
An alternate key identifies a record by one or more business columns instead of a Dataverse-generated GUID. Alternate keys are required for upsert operations. Define them in the Power Apps maker portal under Table > Keys, or programmatically by using client.tables.create_alternate_key.
# Create an alternate key on the accountnumber column
key = client.tables.create_alternate_key(
"account",
"account_accountnumber_ak",
["accountnumber"],
display_name="Account Number",
)
print(f"Created key {key.schema_name} ({key.metadata_id}), status={key.status}")
# The key status transitions from Pending to Active asynchronously - poll before upserting
for k in client.tables.get_alternate_keys("account"):
if k.schema_name == "account_accountnumber_ak":
print(f"{k.schema_name}: {k.status}")
Important
The transition from Pending to Active isn't immediate. Check the key status right after creation and wait until it's Active before issuing upsert requests. Without an active alternate key, Dataverse rejects upsert requests with a 400 error.
Important
All custom column names must include the customization prefix value (for example, "new_"). This requirement ensures explicit, predictable naming and aligns with Dataverse metadata requirements.
For more information about working with custom table metadata:
tables.createreturns aTableInfoobject that describes the new table. It doesn't return record IDs.tables.getreturnsNonewhen the table doesn't exist, which makes schema setup idempotent.tables.add_columnsandtables.remove_columnsreturn the list of column names that changed.tables.list_columnsreturns raw attribute metadata dictionaries that use the Web API PascalCase property names, such asLogicalNameandAttributeType.
To create, read, update, and delete records in a table, see Work with data.