User Defined Tables
User-Defined Tables Guide
Create and manage custom data tables for configuration, mappings, and dynamic data storage in DataMagik.
Table of Contents
- Overview
- Creating a Table
- Table Schema Design
- Managing Table Data
- Column Types
- Single Key vs Composite Key
- Using Tables in Scripts
- Data Import & Export
- Best Practices
- Common Use Cases
1. Overview
User-defined tables (UDTs) provide a flexible way to store structured data that can be accessed from scripts, automations, and document templates. Think of them as simple database tables you can create and manage without writing SQL.
Common Uses:
- Customer-specific configuration (templates, preferences, settings)
- Lookup mappings (product codes → descriptions, customer → ship-to addresses)
- Status tracking (order status, processing flags)
- Reference data (pricing tiers, discount rules, region codes)
2. Creating a Table
Step 1: Open User-Defined Tables
Navigate to Manufacturing → User Defined Tables in the main menu.
Step 2: Click "Create Table"
Click the Create Table button in the top-right corner.
Step 3: Configure Table Settings
| Field | Description | Example |
|---|---|---|
| Table Name | Unique identifier (lowercase with underscores) | customer_templates |
| Description | What this table is used for | Maps customers to their preferred templates |
| Key Type | Single Key or Composite Key | Single Key |
| Primary Key Name | Name of the main key column | customer_code |
| Key Description | What the key represents | Customer code from ERP |
| Value Column Name | Name of the value column | settings |
| Value Type | Type of data stored | object |
Step 4: Save the Table
Click Create Table to save. The table will appear in your list and is ready to receive data.
3. Table Schema Design
Naming Conventions
- Use lowercase letters with underscores for table names
- Make names descriptive but concise
- Prefix related tables (e.g.,
customer_templates,customer_addresses,customer_settings)
Good examples: customer_templates, product_mappings, region_pricing, order_status
Avoid: CustomerTemplates (use lowercase), ct (too short), customer-templates (use underscores)
Key Column Design
The key column(s) uniquely identify each row. Choose keys that:
- Are stable (don't change over time)
- Are meaningful (easy to understand what they represent)
- Are available in your scripts and automations
4. Managing Table Data
Viewing Data
- Click on a table in the list to select it
- Click the Data button to open the data manager
- View all entries in a searchable table format
Adding Entries
- Click Add Entry in the data manager
- Enter the key value(s)
- Enter the value data (depends on value type)
- Click Save
Editing Entries
- Find the entry in the data manager
- Click the Edit button (pencil icon)
- Modify the value data
- Click Save
Deleting Entries
- Find the entry in the data manager
- Click the Delete button (trash icon)
- Confirm the deletion
Warning: Deletion is permanent. Ensure no scripts depend on the entry before deleting.
5. Column Types
Value Types
When creating a table, choose what type of data the value column will hold:
| Type | Description | Example Use |
|---|---|---|
string | Simple text value | Status codes, names, identifiers |
number | Numeric value | Prices, quantities, thresholds |
boolean | True/false flag | Feature flags, enabled/disabled |
object | JSON object with multiple fields | Complex settings, nested data |
array | List of values | Multiple options, lists |
document_template | Reference to a template | Customer-specific templates |
printer | Reference to a printer | Default printer assignments |
Object Type (Most Flexible)
The object type is most commonly used because it can store multiple related values:
{
"template_id": "inv-template-001",
"default_printer": "Shipping-Zebra",
"notify_email": "orders@customer.com",
"priority": "high",
"discount_percent": 5
}Accessing Different Types in Scripts
// String type
const status = tables.getValue("order_status", orderId);
// status = "shipped"
// Number type
const threshold = tables.getValue("inventory_thresholds", productCode);
// threshold = 100
// Boolean type
const isEnabled = tables.getValue("feature_flags", "new_checkout");
// isEnabled = true
// Object type
const settings = tables.getValue("customer_settings", customerCode);
// settings = { template_id: "...", notify_email: "...", ... }
const templateId = settings.template_id;6. Single Key vs Composite Key
Single Key Tables
Use a single key when one identifier uniquely identifies each row.
Example: Customer Settings
| customer_code (Key) | settings (Value) |
|---|---|
| CUST001 | { template_id: "inv-001", ... } |
| CUST002 | { template_id: "inv-002", ... } |
| CUST003 | { template_id: "inv-001", ... } |
// Access with single key
const settings = tables.getValue("customer_settings", "CUST001");Composite Key Tables
Use composite keys when you need two values to uniquely identify a row.
Example: Customer Shipping Documents (by Customer + Ship-To)
| customer_code (Key 1) | ship_to_code (Key 2) | document_settings (Value) |
|---|---|---|
| CUST001 | SHIP-A | { template: "bol-001" } |
| CUST001 | SHIP-B | { template: "bol-002" } |
| CUST002 | SHIP-A | { template: "bol-001" } |
// Access with composite key
const settings = tables.getValue(
"customer_shipping_docs",
"CUST001",
"SHIP-A"
);When to Use Composite Keys
- Customer + Location — Different settings per shipping location
- Product + Variant — Settings per product size/color
- Date + Code — Historical data with date partitioning
- Region + Category — Regional pricing by product category
7. Using Tables in Scripts
Basic Read Operations
function main(context) {
// Get full entry (includes metadata)
const entry = tables.get("customer_settings", context.customerCode);
// entry = { key: "CUST001", value: { template_id: "..." }, created_at: "...", updated_at: "..." }
// Get just the value (simpler)
const settings = tables.getValue("customer_settings", context.customerCode);
// settings = { template_id: "..." }
// Check if exists
if (tables.exists("blocklist", context.customerCode)) {
return { success: false, error: "Customer is blocked" };
}
return { success: true, data: settings };
}Write Operations
function main(context) {
// Create or update (upsert)
tables.set("order_status", context.orderId, {
status: "processing",
updated_at: new Date().toISOString(),
updated_by: context.userId
});
// For composite keys, pass both keys
tables.set(
"customer_shipping_docs",
context.customerCode,
context.shipToCode,
{ template: "custom-bol", special_instructions: "..." }
);
return { success: true };
}Listing & Filtering
function main(context) {
// List all entries in a table
const allCustomers = tables.list("customer_settings");
// allCustomers = [{ key: "CUST001", value: {...} }, ...]
// For composite key tables, filter by first key
const customerLocations = tables.list(
"customer_shipping_docs",
context.customerCode
);
// Returns all ship-to locations for this customer
return {
success: true,
data: {
total_customers: allCustomers.length,
locations: customerLocations
}
};
}8. Data Import & Export
Exporting Data
- Open the Data Manager for your table
- Click the Export button
- Choose format (JSON or CSV)
- Download the file
Importing Data
- Open the Data Manager for your table
- Click the Import button
- Select your JSON or CSV file
- Review the preview
- Confirm the import
JSON Format for Import:
[
{
"key": "CUST001",
"value": {
"template_id": "inv-001",
"notify_email": "orders@customer1.com"
}
},
{
"key": "CUST002",
"value": {
"template_id": "inv-002",
"notify_email": "orders@customer2.com"
}
}
]CSV Format for Import (Simple Values):
key,value
CUST001,inv-001
CUST002,inv-002
CUST003,inv-0019. Best Practices
Table Design
- Keep tables focused — One table per concept (don't mix customer settings with product settings)
- Use descriptive names —
customer_document_preferencesis better thancust_prefs - Document your tables — Use the description field to explain what the table is for
- Plan for growth — Use object types when you might need to add more fields later
Performance
- Keep tables reasonably sized — For very large datasets (10,000+ entries), consider alternative storage
- Use specific keys — Avoid listing entire tables when you only need one entry
- Cache when appropriate — If you read the same entry multiple times in a script, store it in a variable
Data Integrity
- Validate before writing — Check that values are in the expected format before saving
- Use consistent key formats — If keys are customer codes, always use the same format (uppercase, trimmed)
- Handle missing entries gracefully — Always check if
tables.getValue()returns null
function main(context) {
const customerCode = context.customerCode.toUpperCase().trim();
const settings = tables.getValue("customer_settings", customerCode);
if (!settings) {
return {
success: true,
data: { template_id: "default-template" }
};
}
return { success: true, data: settings };
}10. Common Use Cases
Customer-Specific Document Templates
function main(context) {
const config = tables.getValue("customer_templates", context.customerCode);
const templateId = config?.invoice_template || "default-invoice";
documents.generate(templateId, {
customerName: context.customerName,
orderNumber: context.orderNumber,
items: context.items
});
return { success: true, script_message: "Document generated" };
}Feature Flags
function main(context) {
const flags = tables.getValue("feature_flags", context.customerCode) || {};
if (flags.new_checkout_enabled) {
// Use new checkout logic
} else {
// Use legacy checkout
}
return { success: true };
}Pricing Tiers
function main(context) {
const pricing = tables.getValue(
"regional_pricing",
context.region,
context.productCategory
);
const basePrice = context.basePrice;
const multiplier = pricing?.multiplier || 1.0;
const finalPrice = basePrice * multiplier;
return { success: true, data: { price: finalPrice } };
}Status Tracking
function main(context) {
tables.set("order_status", context.orderId, {
status: "shipped",
shipped_at: new Date().toISOString(),
tracking_number: context.trackingNumber
});
return { success: true };
}Blocklists / Allowlists
function main(context) {
if (tables.exists("ip_blocklist", context.ipAddress)) {
return {
success: false,
error: "Access denied",
notification_level: "error"
};
}
const isPremium = tables.exists("premium_customers", context.customerCode);
return {
success: true,
data: { premium_access: isPremium }
};
}