This interface allows a Filter to describe their impact on record schema to Valence, much like an Adapter can with SchemaAdapter.

This is a nice-to-have that we strongly encourage you to implement, as it makes life much better for Users. Implementing this allows them to understand which fields they see on the records came from which Filters, and why.

To learn more about how Valence uses schema information, check out our Schema page.


 * A "schema-aware" TransformationFilter is able to describe its affect on the records that it interacts with. It
 * explains how it might modify the record, what fields it depends on, what fields it adds, etc.
global interface SchemaAwareTransformationFilter extends TransformationFilter {

         * Help us understand how your Filter interacts with Records. Given a specific LinkContext (with included Mappings and configurations),
         * describe what source and target fields you would read, create, change, and write to.
         * @param context Details about a potential Link run
         * @return An instance of FilterSchema, describing fields this Filter reads, changes, creates, writes to, etc
        FilterSchema describeSchema(LinkContext context);

Supporting Classes

You can see above that the return type is an instance of FilterSchema, which is a convenience class that makes it easier to describe what your Filter does.

Below are the signatures from that class.

// constructor
global FilterSchema();

global FilterSchema addTouch(Touch newTouch);

global void createsSourceField(Field newField);

global static Touch buildSourceTouch(String operation);

global static Touch buildTargetTouch(String operation);

// inner class valence.FilterSchema.Touch
global class Touch {

        global Touch onField(Mapping mapping); // give credit to this mapping and attach to the source or target side of it (depending on how you built it)

        global Touch onField(List<String> path); // attach to a source or target field independent of a mapping

        global Touch creditMapping(Mapping mapping); // give credit to a mapping for a field that is not actually the source or target of that mapping (derivative)

Example - Always Read Same Field

In this simple example a Filter depends on a specific field that it always reads from, regardless of context.

valence.FilterSchema describeSchema(valence.LinkContext context) {
        // always reads 'flavor' field no matter what the Link is or what Mappings exist on it
        return new valence.FilterSchema()
                .addTouch(valence.FilterSchema.buildSourceTouch('READ').onField(new List<String>{'flavor'}));

Example - Write To a Field That Isn’t Part of A Mapping

Here’s a slightly more complex example. Let’s say your Filter is configured per-mapping, and what it does is create an additional field on the target side in addition to the target field the mapping is normally going to write to (this is exactly how our native RelationshipFilter works).

schema.addTouch(FilterSchema.buildSourceTouch('READ').onField(mapping)); // reads the source side of the mapping
schema.addTouch(FilterSchema.buildTargetTouch('WRITE').onField(new List<String>{config.targetFieldName}).creditMapping(mapping)); // write to a separately-configured field but give credit to this mapping for why you wrote there

Example - Create Fields Based on Configurations

There’s some additional special things that happen when your Filter uses createsSourceField(). This will make your Field appear in the schema browser for Users, and they can actually build mappings and other configurations against it because they know to expect it. The fields you create can still be dynamic though and based on some configuration you have been set up with.


Any Field you declare with createsSourceField() will automatically have a “WRITE” Touch added to it.

* Let's say this Filter implements ConfigurablePerLinkFilter and had some configurations set that hold fieldNames and values for writing constants.

private List<FilterConfiguration> configs = new List<FilterConfiguration>();

public void setFilterConfigurations(valence.LinkContext context, List<String> configurationData) {
        for(String configData : configurationData) {

public void process(valence.LinkContext context, List<valence.RecordInFlight> records) {

        // do work on the records using our configurations
        for(valence.RecordInFlight record : records) {
                for(FilterConfiguration config : configs) {
                        record.setOriginalPropertyValue(config.fieldName, config.value);

private static FilterConfiguration interpretConfigData(String data) {
        return String.isBlank(data) ? new FilterConfiguration() : (FilterConfiguration)JSON.deserialize(data, FilterConfiguration.class);

private class FilterConfiguration {
        String fieldName;
        String value;

valence.FilterSchema describeSchema(valence.LinkContext context) {

        valence.FilterSchema schema = new valence.FilterSchema();

        for(FilterConfiguration config : configs) {
                .withDescription(String.format('A static field created by the Constants Filter whose value will always be <strong>{0}</strong>', new List<Object>{config.value})).build());
        return schema;


This last example comes from a real Filter you can go look at on GitHub.