Skip to main content

Table Code Extensions

Code extensions allow you to add new methods and wrap existing methods on a table without modifying its source code. This is achieved through augmentation classes decorated with the [ExtensionOf] attribute, using the Chain of Command (CoC) pattern.

Creating a Table Extension Class

An extension class targets a specific table using the [ExtensionOf(tableStr(TableName))] attribute. The class must be declared final and must not inherit from any other class.

[ExtensionOf(tableStr(CustTable))]
final class SAMOCustTable_Extension
{
// New methods and wrapped methods go here
}

Naming Convention

ElementPatternExample
Extension class<Prefix><TableName>_ExtensionSAMOCustTable_Extension
Multiple extensions<Prefix><TableName>_<Feature>_ExtensionSAMOCustTable_Credit_Extension
warning

Only one [ExtensionOf] class per table per model is recommended. If you need multiple, differentiate with a feature suffix.

Chain of Command (CoC)

Chain of Command lets you wrap existing table methods — executing logic before and/or after the base implementation. The next keyword calls the next link in the chain (ultimately the base method).

Wrapping insert()

[ExtensionOf(tableStr(CustTable))]
final class SAMOCustTable_Extension
{
public void insert()
{
// Pre-insert logic
this.SAMOSetDefaultTier();

next insert();

// Post-insert logic
SAMOCustAudit::logCreation(this);
}
}

Wrapping validateWrite()

[ExtensionOf(tableStr(CustTable))]
final class SAMOCustTable_Extension
{
public boolean validateWrite()
{
boolean ret = next validateWrite();

if (ret && !this.SAMOCustTier)
{
ret = checkFailed("@SAMO:CustTierRequired");
}

return ret;
}
}

Wrapping validateField()

[ExtensionOf(tableStr(CustTable))]
final class SAMOCustTable_Extension
{
public boolean validateField(FieldId _fieldId)
{
boolean ret = next validateField(_fieldId);

switch (_fieldId)
{
case fieldNum(CustTable, SAMOCustTier):
if (ret && this.SAMOCustTier == SAMOCustTier::Premium
&& !this.CreditMax)
{
ret = checkFailed("@SAMO:PremiumRequiresCredit");
}
break;
}

return ret;
}
}

Wrapping modifiedField()

[ExtensionOf(tableStr(CustTable))]
final class SAMOCustTable_Extension
{
public void modifiedField(FieldId _fieldId)
{
next modifiedField(_fieldId);

switch (_fieldId)
{
case fieldNum(CustTable, SAMOCustTier):
this.SAMOApplyTierDefaults();
break;
}
}
}

Wrapping initValue()

[ExtensionOf(tableStr(CustTable))]
final class SAMOCustTable_Extension
{
public void initValue()
{
next initValue();

this.SAMOCustTier = SAMOCustTier::Standard;
this.SAMOOnboardingDate = DateTimeUtil::getSystemDate(
DateTimeUtil::getUserPreferredTimeZone());
}
}

Adding New Methods

Extension classes can define entirely new methods on the table. These are accessible from any code that has a buffer of that table type.

[ExtensionOf(tableStr(CustTable))]
final class SAMOCustTable_Extension
{
public SAMOCustTier SAMOCalculateTier()
{
SAMOCustTier tier;

if (this.CreditMax >= 100000)
{
tier = SAMOCustTier::Premium;
}
else if (this.CreditMax >= 10000)
{
tier = SAMOCustTier::Gold;
}
else
{
tier = SAMOCustTier::Standard;
}

return tier;
}
}

Methods That Can Be Wrapped

The following table methods support Chain of Command wrapping:

MethodCategoryDescription
insert()Data manipulationCalled when a new record is written to the database.
update()Data manipulationCalled when an existing record is saved.
delete()Data manipulationCalled when a record is removed.
initValue()InitialisationCalled to set default values for a new record.
validateWrite()ValidationCalled before insert/update to validate the record.
validateDelete()ValidationCalled before delete to validate the operation.
validateField(FieldId)ValidationCalled when a field value changes on a form.
modifiedField(FieldId)Field changeCalled after a field value changes on a form.
find()StaticStatic method for finding records by key.
exist()StaticStatic method for checking record existence.
renamePrimaryKey()Key managementCalled when the primary key value is changed.
caption()DisplayReturns the caption for the current record.
canSubmitToWorkflow()WorkflowDetermines if the record can be submitted to workflow.
postLoad()LifecycleCalled after a record is loaded from the database.
aosValidateInsert()Server validationServer-side insert validation.
aosValidateUpdate()Server validationServer-side update validation.
aosValidateDelete()Server validationServer-side delete validation.
aosValidateRead()Server validationServer-side read validation.
tip

If a method is not in this list, use event handlers instead of CoC to inject logic. Some kernel-level methods cannot be wrapped.

CoC Rules and Constraints

  1. Always call next. Every wrapped method must call next exactly once. Failing to call next breaks the chain and prevents the base method and other extensions from executing.
  2. Extension classes must be final. The compiler enforces this.
  3. No constructors. Extension classes cannot have constructors.
  4. No state fields. Extension classes should not declare instance variables — they share the table buffer's scope. Use SysExtensionSerializerExtensionMap or other patterns for persistent state.
  5. Method signatures must match exactly. The wrapping method must have the same name, return type, and parameter list as the base method.
  6. Access level cannot be more restrictive than the original method.