# Event Filters

Events can be _filtered_ in Hatchet, which allows you to push events to Hatchet and only trigger task runs from them in certain cases. **If you enable filters on a workflow, your workflow will be triggered once for each matching filter on any incoming event with a matching scope** (more on scopes below).

## Basic Usage

There are two ways to create filters in Hatchet.

### Default filters on the workflow

The simplest way to create a filter is to register it declaratively with your workflow when it's created. For example:

#### Python

```python
event_workflow_with_filter = hatchet.workflow(
    name="EventWorkflow",
    on_events=[EVENT_KEY, SECONDARY_KEY, WILDCARD_KEY],
    input_validator=EventWorkflowInput,
    default_filters=[
        DefaultFilter(
            expression="true",
            scope="example-scope",
            payload={
                "main_character": "Anna",
                "supporting_character": "Stiva",
                "location": "Moscow",
            },
        )
    ],
)
```

#### Typescript

```typescript
export const lowerWithFilter = hatchet.workflow({
  name: 'lower',
  // 👀 Declare the event that will trigger the workflow
  onEvents: ['simple-event:create'],
  defaultFilters: [
    {
      expression: 'true',
      scope: 'example-scope',
      payload: {
        mainCharacter: 'Anna',
        supportingCharacter: 'Stiva',
        location: 'Moscow',
      },
    },
  ],
});
```

#### Go

```go
func LowerWithFilter(client *hatchet.Client) *hatchet.StandaloneTask {
	return client.NewStandaloneTask(
		"lower", accessFilterPayload,
		hatchet.WithWorkflowEvents(SimpleEvent),
		hatchet.WithFilters(types.DefaultFilter{
			Expression: "true",
			Scope:      "example-scope",
			Payload: map[string]interface{}{
				"main_character":       "Anna",
				"supporting_character": "Stiva",
				"location":             "Moscow"},
		}),
	)
}
```

#### Ruby

```ruby
EVENT_WORKFLOW_WITH_FILTER = HATCHET.workflow(
  name: "EventWorkflow",
  on_events: [EVENT_KEY, SECONDARY_KEY, WILDCARD_KEY],
  default_filters: [
    Hatchet::DefaultFilter.new(
      expression: "true",
      scope: "example-scope",
      payload: {
        "main_character" => "Anna",
        "supporting_character" => "Stiva",
        "location" => "Moscow"
      }
    )
  ]
)

EVENT_WORKFLOW.task(:task) do |input, ctx|
  puts "event received"
  ctx.filter_payload
end
```

In each of these cases, we register a filter with the workflow. Note that these "declarative" filters are overwritten each time your workflow is updated, so the ids associated with them will not be stable over time. This allows you to modify a filter in-place or remove a filter, and not need to manually delete it over the API.

### Filters feature client

You also can create event filters by using the `filters` clients on the SDKs:

#### Python

```python
hatchet.filters.create(
    workflow_id=event_workflow.id,
    expression="input.should_skip == false",
    scope="foobarbaz",
    payload={
        "main_character": "Anna",
        "supporting_character": "Stiva",
        "location": "Moscow",
    },
)
```

#### Typescript

```typescript
hatchet.filters.create({
  workflowId: lower.id,
  expression: 'input.ShouldSkip == false',
  scope: 'foobarbaz',
  payload: {
    main_character: 'Anna',
    supporting_character: 'Stiva',
    location: 'Moscow',
  },
});
```

#### Go

```go
_, err = client.Filters().Create(
	context.Background(),
	rest.V1CreateFilterRequest{
		WorkflowId: uuid.MustParse("bb866b59-5a86-451b-8023-10d451db11d3"),
		Expression: "true",
		Scope:      "example-scope",
	},
)
if err != nil {
	return err
}
```

#### Ruby

```ruby
HATCHET_CLIENT.filters.create(
  workflow_id: EVENT_WORKFLOW.id,
  expression: "input.should_skip == false",
  scope: "foobarbaz",
  payload: {
    "main_character" => "Anna",
    "supporting_character" => "Stiva",
    "location" => "Moscow"
  }
)
```

> **Warning:** Note the `scope` argument to the filter is required **both when creating a
>   filter, and when pushing events**. If the scope on filter creation does not
>   match the scope provided when pushing events, the filter will not apply.

Then, push an event that uses the filter to determine whether to run. For instance, this run will be skipped, since the payload does not match the expression:

#### Python

```python
hatchet.event.push(
    event_key=EVENT_KEY,
    payload={
        "should_skip": True,
    },
    scope="foobarbaz",
)
```

#### Typescript

```typescript
hatchet.events.push(
  SIMPLE_EVENT,
  {
    Message: 'hello',
    ShouldSkip: true,
  },
  {
    scope: 'foobarbaz',
  }
);
```

#### Go

```go
skipPayload := map[string]interface{}{
	"shouldSkip": true,
}
skipScope := "foobarbaz"
err = client.Events().Push(
	context.Background(),
	"simple-event:create",
	skipPayload,
	v0Client.WithFilterScope(&skipScope),
)
if err != nil {
	return err
}
```

#### Ruby

```ruby
HATCHET_CLIENT.event.push(
  EVENT_KEY,
  { "should_skip" => true },
  scope: "foobarbaz"
)
```

But this one will be triggered since the payload _does_ match the expression:

#### Python

```python
hatchet.event.push(
    event_key=EVENT_KEY,
    payload={
        "should_skip": False,
    },
    scope="foobarbaz",
)
```

#### Typescript

```typescript
hatchet.events.push(
  SIMPLE_EVENT,
  {
    Message: 'hello',
    ShouldSkip: false,
  },
  {
    scope: 'foobarbaz',
  }
);
```

#### Go

```go
triggerPayload := map[string]interface{}{
	"shouldSkip": false,
}
triggerScope := "foobarbaz"
err = client.Events().Push(
	context.Background(),
	"simple-event:create",
	triggerPayload,
	v0Client.WithFilterScope(&triggerScope),
)
if err != nil {
	return err
}
```

#### Ruby

```ruby
HATCHET_CLIENT.event.push(
  EVENT_KEY,
  { "should_skip" => false },
  scope: "foobarbaz"
)
```

> **Info:** In Hatchet, filters are "positive", meaning that we look for _matches_ to the
>   filter to determine which tasks to trigger.

## Accessing the filter payload

You can access the filter payload by using the `Context` in the task that was triggered by your event:

#### Python

```python
@event_workflow_with_filter.task()
def filtered_task(input: EventWorkflowInput, ctx: Context) -> None:
    print(ctx.filter_payload)
```

#### Typescript

```typescript
lowerWithFilter.task({
  name: 'lowerWithFilter',
  fn: (input, ctx) => {
    console.log(ctx.filterPayload());
  },
});
```

#### Go

```go
func accessFilterPayload(ctx hatchet.Context, input EventInput) (*LowerTaskOutput, error) {
	fmt.Println(ctx.FilterPayload())
	return &LowerTaskOutput{
		TransformedMessage: strings.ToLower(input.Message),
	}, nil
}
```

#### Ruby

```ruby
EVENT_WORKFLOW_WITH_FILTER.task(:filtered_task) do |input, ctx|
  puts ctx.filter_payload.inspect
end
```

## Advanced Usage

In addition to referencing `input` in the expression (which corresponds to the _event_ payload), you can also reference the following fields:

1. `payload` corresponds to the _filter_ payload (which was part of the request when the filter was created).
2. `additional_metadata` allows for filtering based on `additional_metadata` sent with the event.
3. `event_key` allows for filtering based on the key of the event, such as `user:created`.
