actualbudget/actual · error

Filter name is required

Error message

Filter name is required

What it means

createFilter requires a non-empty filter name. If filter.state.name is falsy (empty string, null, undefined), the guard `if (item.name)` fails and the code throws 'Filter name is required' instead of attempting the duplicate-name check. Saved transaction filters are addressed by name in the UI, so a name is mandatory.

Source

Thrown at packages/loot-core/src/server/filters/app.ts:126

  return keys1.every(key => opt1[key] === opt2[key]);
}

async function createFilter(filter): Promise<TransactionFilterEntity['id']> {
  const filterId = uuidv4();
  const item = {
    id: filterId,
    conditions: filter.state.conditions,
    conditionsOp: filter.state.conditionsOp,
    name: filter.state.name,
  };

  if (item.name) {
    if (await filterNameExists(item.name, item.id, true)) {
      throw new Error('There is already a filter named ' + item.name);
    }
  } else {
    throw new Error('Filter name is required');
  }

  if (item.conditions.length > 0) {
    const condExists = conditionExists(item, filter.filters, true);
    if (condExists) {
      throw new Error(
        'Duplicate filter warning: conditions already exist. Filter name: ' +
          condExists,
      );
    }
  } else {
    throw new Error('Conditions are required');
  }

  // Create the filter here based on the info
  await db.insertWithSchema('transaction_filters', filterModel.fromJS(item));

  return filterId;

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Provide a non-empty name in filter.state before calling createFilter.
  2. Validate the name client-side (trim and check length > 0) and disable submit until valid.
  3. If the name came from user input, trim it and re-check; whitespace-only strings are still falsy after trim only if you trim yourself.

Example fix

// before
await aql.query('filter-create', { state: { name: '', conditions, conditionsOp: 'and' } });
// after
const name = userInput.trim();
if (!name) throw new Error('Filter name is required');
await aql.query('filter-create', { state: { name, conditions, conditionsOp: 'and' } });
Defensive patterns

Strategy: validation

Validate before calling

const name = (state.name ?? '').trim();
if (!name) throw new Error('Filter name is required before calling filter-create');

Type guard

function hasFilterName(state: { name?: string | null }): state is { name: string } {
  return typeof state.name === 'string' && state.name.trim().length > 0;
}

Try / catch

try {
  await aql.query('filter-create', { state });
} catch (e) {
  if (e.message === 'Filter name is required') {
    // surface a required-field error to the user
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the filter-create mutator with state.name === '' or missing; a UI form submitting before the user typed a name; a script/plugin constructing the filter payload programmatically and omitting the name field.

Common situations: Automated budget setup scripts that build filter payloads; custom UIs built on the Actual API forgetting the name field; empty-string names after trimming whitespace in custom frontends.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/ce359fbfca3bffaf. Report an issue: GitHub.