Reasoning Flows — Configurable Objects Overview
Configurable Objects Overview
Create interactive configuration interfaces for Reasoning Flows objects using HTML forms.
Purpose
In Reasoning Flows, you can create objects in PHP or Python with an optional interactive configuration interface. By adding a config.html file to your object, you enable users to configure parameters through a form instead of editing code directly.
Form values are passed directly to the startup script (index.py or index.php) at runtime, making it easy for non-technical users to adjust settings without modifying code.
Objects without a config.html file work exactly as before — opening in the standard code editor view.
Object Types
An object must use one of the following languages:
| Language | Startup File | Runtime |
|---|---|---|
| Python | index.py | Python 3.x |
| PHP | index.php | PHP 8.x |
The startup file is required and serves as the entry point for execution. It can import or include other files within the same object.
Configuration UI (config.html)
With config.html
When an object contains a config.html file:
- Reasoning Flows renders it as an interactive configuration form
- User inputs are passed directly to the startup script at execution time
- You can still access View Code Mode to see and edit the object's files
Without config.html
When config.html is absent:
- The object opens in Code Mode by default
- Users edit code and configuration files manually
How Form Values Reach Your Code
Each form field's name attribute becomes the key your startup script reads:
| Language | Values arrive as | Read with |
|---|---|---|
| Python | Environment variables | os.environ.get('field_name') |
| PHP | POST parameters | $_POST['field_name'] |
Security note: Form values are user input. Always validate them in your startup script before use — especially anything that ends up in a SQL statement. Use parameterized queries for values, and strict allowlists or pattern checks for identifiers such as table names, which cannot be parameterized.
Creating a Configuration Interface
Example: config.html
<!DOCTYPE html>
<html>
<head>
<title>Data Export Configuration</title>
<style>
form { max-width: 400px; margin: 20px; }
label { display: block; margin-top: 10px; font-weight: bold; }
input, select { width: 100%; padding: 8px; margin-top: 5px; }
button { margin-top: 20px; padding: 10px 20px; }
</style>
</head>
<body>
<h2>Data Export Settings</h2>
<form id="config-form">
<label for="source_table">Source Table:</label>
<select id="source_table" name="source_table">
<option value="sales_gold">sales_gold</option>
<option value="customers_gold">customers_gold</option>
<option value="inventory_gold">inventory_gold</option>
</select>
<label for="row_limit">Row Limit:</label>
<input type="number" id="row_limit" name="row_limit" value="1000" min="1" max="100000">
<label for="output_format">Output Format:</label>
<select id="output_format" name="output_format">
<option value="csv">CSV</option>
<option value="json">JSON</option>
</select>
<label for="include_headers">Include Headers (CSV):</label>
<select id="include_headers" name="include_headers">
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
<button type="submit">Export Data</button>
</form>
</body>
</html>
Tip: Offering the table name as a dropdown instead of a free-text field improves usability and security — users can't mistype, and your script can verify the value against the same fixed list.
Reading Config Values in Python (index.py)
import dkconnect
import os
import json
# --- Read form values (environment variables) ---
source_table = os.environ.get('source_table', 'sales_gold')
row_limit = int(os.environ.get('row_limit', 1000))
output_format = os.environ.get('output_format', 'csv')
include_headers = os.environ.get('include_headers', 'yes') == 'yes'
# --- Validate user input before use ---
# Table names cannot be parameterized in SQL, so verify against an allowlist.
ALLOWED_TABLES = {'sales_gold', 'customers_gold', 'inventory_gold'}
if source_table not in ALLOWED_TABLES:
raise ValueError(f"Invalid source table: {source_table}")
row_limit = max(1, min(row_limit, 100000)) # clamp to a sane range
# --- Connect and query ---
conn = dkconnect.connect()
cursor = conn.cursor()
# Identifier validated above; LIMIT value parameterized.
cursor.execute(
f"SELECT * FROM {source_table} LIMIT %s",
(row_limit,)
)
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()
# --- Output based on format selection ---
if output_format == 'json':
data = [dict(zip(columns, row)) for row in rows]
print(json.dumps(data, indent=2, default=str))
else: # csv (default)
if include_headers:
print(','.join(columns))
for row in rows:
print(','.join(str(val) for val in row))
cursor.close()
conn.close()
Reading Config Values in PHP (index.php)
<?php
require_once('datakubes-connect.php');
// --- Read form values (POST parameters) ---
$source_table = $_POST['source_table'] ?? 'sales_gold';
$row_limit = (int)($_POST['row_limit'] ?? 1000);
$output_format = $_POST['output_format'] ?? 'csv';
$include_headers = ($_POST['include_headers'] ?? 'yes') === 'yes';
// --- Validate user input before use ---
// Table names cannot be parameterized in SQL, so verify against an allowlist.
$allowed_tables = ['sales_gold', 'customers_gold', 'inventory_gold'];
if (!in_array($source_table, $allowed_tables, true)) {
http_response_code(400);
die('Invalid source table.');
}
$row_limit = max(1, min($row_limit, 100000)); // clamp to a sane range
// --- Connect and query ---
$dk_conn = dk_connect();
// Identifier validated above; LIMIT value parameterized.
$stmt = $dk_conn->prepare("SELECT * FROM $source_table LIMIT ?");
$stmt->bind_param('i', $row_limit);
$stmt->execute();
$result = $stmt->get_result();
// Get column names
$columns = [];
foreach ($result->fetch_fields() as $field) {
$columns[] = $field->name;
}
// --- Output based on format selection ---
if ($output_format === 'json') {
header('Content-Type: application/json');
$data = [];
while ($row = $result->fetch_assoc()) {
$data[] = $row;
}
echo json_encode($data, JSON_PRETTY_PRINT);
} else { // csv (default)
header('Content-Type: text/csv');
if ($include_headers) {
echo implode(',', $columns) . "\n";
}
while ($row = $result->fetch_row()) {
echo implode(',', $row) . "\n";
}
}
$stmt->close();
$dk_conn->close();
Common Use Cases
| Use Case | Description |
|---|---|
| Data Transformation Pipelines | Let users select data sources, filters, or transformation rules before processing |
| API Integration Configurations | Collect authentication keys, endpoints, and query parameters via form inputs |
| AI & Machine Learning Parameters | Adjust model settings, thresholds, or prompts before execution |
| Report Generation | Allow users to specify date ranges, departments, or output formats |
| Automation & Task Runners | Schedule or parameterize recurring backend tasks without code changes |
| Environment-Specific Settings | Switch between dev, staging, and production values dynamically |
Who Uses Configurable Objects
This feature has two distinct roles by design — the person who builds the object and the people who run it:
| Role | Side | How they use it |
|---|---|---|
| Platform / data engineers | Maker | Wrap recurring ETL, exports, and pipelines in a form so parameter changes stop being tickets |
| ML engineers | Maker | Expose thresholds, prompts, and model parameters for experimentation without code access |
| Analysts | Consumer | Run extracts and reports with their own filters, tables, and output formats |
| Operations / business staff | Consumer | Trigger parameterized recurring tasks without touching code |
| Team leads / security owners | Beneficiary | Grant run-with-parameters capability without granting code-write access |
Note: Every form field is a contract. Once consumers depend on a parameter, renaming or removing it is a breaking change — design forms with the same care as an API.
Comparison
With config.html | Without config.html |
|---|---|
| UI rendered for configuration | Shows file/code browser |
| Inputs sent directly to main file | Edit code/config manually |
| User-friendly for non-developers | Requires code editing |
Always starts at index.* | Always starts at index.* |
Benefits
Flexibility
Choose PHP or Python based on your workflow and team expertise.
Ease of Use
Non-technical users can configure objects via forms instead of editing code.
Predictable Execution
Always starts from a single entry file (index.py or index.php).
Modularity
Startup scripts can import helper files, libraries, and shared modules.
Backward Compatibility
Objects without config.html work exactly as before.
Best Practices
Keep forms simple. Focus on the parameters users actually need to change.
Provide sensible defaults. Pre-populate form fields with common values.
Validate inputs. Treat every form value as untrusted. Parameterize SQL values; allowlist identifiers like table or column names (see the examples above). Clamp numeric inputs to sane ranges.
Prefer constrained inputs. Dropdowns, number fields with min/max, and fixed option sets prevent invalid input at the source — and make server-side validation trivial.
Use clear labels. Make form fields self-explanatory for non-technical users.
Add help text. Include placeholders or descriptions to explain expected values.
Handle missing values gracefully. Always provide fallback defaults in your code.
Test both modes. Verify your object works correctly with and without the configuration form.
Related Documentation
-
App Droplet Object Overview
Deploy configured objects to production servers. -
Visual Object Overview
Create notebooks and visualization applications. -
AI Apps Overview
Orchestrate multiple objects into complete applications. -
Transform & Prepare
Use configurable objects for data transformation workflows.
