Skip to main content
The property system allows you to add configurable, static values to your nodes that appear in the properties panel.

Properties vs Inputs

Critical Distinction:
  • @Property = Static configuration (set once in UI, not connected)
  • @Input = Dynamic data flow (connected to other nodes)
  • @Output = Computed results (generated during execution)
class ExampleNode extends Node {
  // Property: Configuration in properties panel
  @Property({ type: 'string', label: 'API URL' })
  apiUrl: string = 'https://api.example.com';

  // Input: Data from connections
  @Input({ type: 'string', label: 'Query' })
  query: string = '';

  // Output: Result of execution
  @Output({ type: 'any', label: 'Data' })
  data: any;

  execute() {
    // Use both property and input
    this.data = await fetch(`${this.apiUrl}?q=${this.query}`);
  }
}

Property Types

String Properties

@Property({
  type: 'string',
  label: 'API Endpoint',
  defaultValue: 'https://api.example.com',
  required: true,
  description: 'The API endpoint URL'
})
endpoint: string = 'https://api.example.com';

Number Properties

@Property({
  type: 'number',
  label: 'Timeout (seconds)',
  defaultValue: 30,
  min: 1,
  max: 300,
  step: 5,
  description: 'Request timeout in seconds'
})
timeout: number = 30;
Options:
  • min - Minimum allowed value
  • max - Maximum allowed value
  • step - Increment step for UI controls

Boolean Properties

@Property({
  type: 'boolean',
  label: 'Enable Caching',
  defaultValue: true,
  description: 'Cache responses for improved performance'
})
enableCaching: boolean = true;

Select Properties

@Property({
  type: 'select',
  label: 'HTTP Method',
  defaultValue: 'GET',
  options: [
    { value: 'GET', label: 'GET' },
    { value: 'POST', label: 'POST' },
    { value: 'PUT', label: 'PUT' },
    { value: 'DELETE', label: 'DELETE' }
  ],
  description: 'HTTP request method'
})
method: string = 'GET';

Property Options

type
'string' | 'number' | 'boolean' | 'select'
required
The property type
label
string
required
Display label in the properties panel
defaultValue
any
Default value for the property
required
boolean
default:"false"
Whether the property is required
description
string
Help text shown in the UI
options
Array<{value: any, label: string}>
Options for select type (required for select type)
min
number
Minimum value (number type only)
max
number
Maximum value (number type only)
step
number
Step increment (number type only)

Property Inheritance

Properties are inherited from base classes:
// Base class with common properties
class BaseApiNode extends Node {
  @Property({
    type: 'boolean',
    label: 'Enable Logging',
    defaultValue: false
  })
  enableLogging: boolean = false;

  @Property({
    type: 'number',
    label: 'Timeout (ms)',
    defaultValue: 30000,
    min: 1000,
    max: 120000
  })
  timeout: number = 30000;

  @Property({
    type: 'number',
    label: 'Retry Count',
    defaultValue: 3,
    min: 0,
    max: 10
  })
  retryCount: number = 3;
}

// Child inherits all properties
@defineNode({
  type: 'api.users',
  label: 'Fetch Users',
  category: 'API'
})
class FetchUsersNode extends BaseApiNode {
  // Inherits: enableLogging, timeout, retryCount

  @Property({
    type: 'string',
    label: 'Endpoint',
    defaultValue: '/users'
  })
  endpoint: string = '/users';

  @Output({ type: 'any[]', label: 'Users' })
  users: any[];

  async execute() {
    if (this.enableLogging) {
      console.log(`Fetching from ${this.endpoint}`);
    }

    // Use inherited properties
    const response = await this.fetchWithRetry(
      this.endpoint,
      this.timeout,
      this.retryCount
    );

    this.users = response.data;
  }
}

Complex Property Examples

Configuration Node

@defineNode({
  type: 'config.settings',
  label: 'Configuration',
  category: 'Config'
})
class ConfigNode extends Node {
  @Property({
    type: 'string',
    label: 'Environment',
    defaultValue: 'development'
  })
  environment: string = 'development';

  @Property({
    type: 'boolean',
    label: 'Debug Mode',
    defaultValue: false
  })
  debugMode: boolean = false;

  @Property({
    type: 'number',
    label: 'Max Connections',
    defaultValue: 10,
    min: 1,
    max: 100
  })
  maxConnections: number = 10;

  @Property({
    type: 'select',
    label: 'Log Level',
    defaultValue: 'info',
    options: [
      { value: 'debug', label: 'Debug' },
      { value: 'info', label: 'Info' },
      { value: 'warn', label: 'Warning' },
      { value: 'error', label: 'Error' }
    ]
  })
  logLevel: string = 'info';

  @Output({ type: 'any', label: 'Config' })
  config: any;

  execute() {
    this.config = {
      environment: this.environment,
      debugMode: this.debugMode,
      maxConnections: this.maxConnections,
      logLevel: this.logLevel
    };
  }
}

HTTP Client Node

@defineNode({
  type: 'http.client',
  label: 'HTTP Client',
  category: 'Network'
})
class HttpClientNode extends Node {
  @Property({
    type: 'string',
    label: 'Base URL',
    defaultValue: 'https://api.example.com',
    required: true
  })
  baseUrl: string = '';

  @Property({
    type: 'select',
    label: 'Method',
    defaultValue: 'GET',
    options: [
      { value: 'GET', label: 'GET' },
      { value: 'POST', label: 'POST' },
      { value: 'PUT', label: 'PUT' },
      { value: 'PATCH', label: 'PATCH' },
      { value: 'DELETE', label: 'DELETE' }
    ]
  })
  method: string = 'GET';

  @Property({
    type: 'number',
    label: 'Timeout (seconds)',
    defaultValue: 30,
    min: 1,
    max: 300
  })
  timeout: number = 30;

  @Property({
    type: 'boolean',
    label: 'Follow Redirects',
    defaultValue: true
  })
  followRedirects: boolean = true;

  @Property({
    type: 'boolean',
    label: 'Verify SSL',
    defaultValue: true
  })
  verifySSL: boolean = true;

  @Input({ type: 'string', label: 'Path' })
  path: string = '';

  @Input({ type: 'any', label: 'Body', required: false })
  body?: any;

  @Output({ type: 'any', label: 'Response' })
  response: any;

  @Output({ type: 'number', label: 'Status Code' })
  statusCode: number;

  async execute() {
    const url = `${this.baseUrl}${this.path}`;
    
    const controller = new AbortController();
    const timeoutMs = this.timeout * 1000;
    const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

    try {
      const res = await fetch(url, {
        method: this.method,
        body: this.body ? JSON.stringify(this.body) : undefined,
        headers: this.body ? { 'Content-Type': 'application/json' } : {},
        signal: controller.signal,
        redirect: this.followRedirects ? 'follow' : 'manual'
      });

      this.statusCode = res.status;
      this.response = await res.json();
    } finally {
      clearTimeout(timeoutId);
    }
  }
}

Property Validation

Validate properties in the execute method:
execute() {
  // Validate required properties
  if (!this.apiUrl) {
    throw new Error('API URL is required');
  }

  // Validate format
  if (!this.apiUrl.startsWith('http')) {
    throw new Error('API URL must start with http:// or https://');
  }

  // Validate ranges
  if (this.timeout < 1 || this.timeout > 300) {
    throw new Error('Timeout must be between 1 and 300 seconds');
  }

  // Proceed with execution
  this.result = await this.callApi(this.apiUrl);
}

UI Integration

Properties appear in the PropertyPanel component:
import { PropertyPanel } from '@crystalflow/react';

<PropertyPanel
  node={selectedNode}
  onChange={(propertyName, value) => {
    // Update property value
    selectedNode[propertyName] = value;
  }}
/>
The PropertyPanel automatically renders appropriate controls:
  • Text input for string
  • Number spinner for number
  • Checkbox for boolean
  • Dropdown for select

Best Practices

Always provide default values that work out of the box.
Include description fields to help users understand each property.
Use min/max/step for numbers to prevent invalid values.
Always validate property values before using them.

Common Patterns

API Configuration

class BaseApiNode extends Node {
  @Property({ type: 'string', label: 'API Key', required: true })
  apiKey: string = '';

  @Property({ type: 'string', label: 'Base URL', defaultValue: 'https://api.example.com' })
  baseUrl: string = 'https://api.example.com';

  @Property({ type: 'number', label: 'Timeout', defaultValue: 30, min: 1, max: 300 })
  timeout: number = 30;
}

File Operations

@Property({
  type: 'select',
  label: 'Encoding',
  defaultValue: 'utf-8',
  options: [
    { value: 'utf-8', label: 'UTF-8' },
    { value: 'ascii', label: 'ASCII' },
    { value: 'base64', label: 'Base64' }
  ]
})
encoding: string = 'utf-8';

Data Processing

@Property({
  type: 'number',
  label: 'Batch Size',
  defaultValue: 100,
  min: 1,
  max: 10000,
  step: 100
})
batchSize: number = 100;

Next Steps