Skip to main content
When actions modify data that users are viewing, Forest automatically reloads the current page. However, Related Data sections in Summary Views require explicit invalidation to refresh.

When to use invalidation

Use related data invalidation when your action:
  • Creates, updates, or deletes records in a related collection
  • Modifies data displayed in Related Data sections of Summary Views
  • Changes relationships between records
Forest handles most cases automatically, but Summary Views with Related Data sections need manual refresh to avoid displaying stale data.

Basic usage

Add an invalidated array to your success result specifying which relationships to refresh:
collection.addAction('Add new transaction', {
  scope: 'Single',
  execute: async (context, resultBuilder) => {
    const company = await context.getRecord(['id']);

    // Create a new transaction
    await createTransaction({
      companyId: company.id,
      amount: context.formValues.amount,
    });

    // Refresh the "emitted_transactions" Related Data section
    return resultBuilder.success('New transaction created', {
      invalidated: ['emitted_transactions'],
    });
  },
});

Multiple relationships

Invalidate multiple Related Data sections at once:
return resultBuilder.success('Data updated', {
  invalidated: ['transactions', 'invoices', 'payments'],
});

Example: Add comment

collection.addAction('Add comment', {
  scope: 'Single',
  form: [
    {
      type: 'String',
      label: 'Comment',
      widget: 'TextArea',
      isRequired: true,
    },
  ],
  execute: async (context, resultBuilder) => {
    const ticket = await context.getRecord(['id']);
    const comment = context.formValues.Comment;

    // Create comment
    await createComment({
      ticketId: ticket.id,
      text: comment,
      authorId: context.caller.id,
      createdAt: new Date(),
    });

    // Refresh comments section in Summary View
    return resultBuilder.success('Comment added', {
      invalidated: ['comments'],
    });
  },
});

Example: Update order items

collection.addAction('Add item to order', {
  scope: 'Single',
  form: [
    {
      type: 'Collection',
      label: 'Product',
      collectionName: 'products',
      isRequired: true,
    },
    {
      type: 'Number',
      label: 'Quantity',
      isRequired: true,
    },
  ],
  execute: async (context, resultBuilder) => {
    const order = await context.getRecord(['id']);
    const { Product, Quantity } = context.formValues;

    // Add item to order
    await createOrderItem({
      orderId: order.id,
      productId: Product[0],  // Collection returns array of IDs
      quantity: Quantity,
    });

    // Refresh order items and total
    return resultBuilder.success('Item added', {
      invalidated: ['order_items'],
    });
  },
});

Example: Assign task

collection.addAction('Assign task', {
  scope: 'Single',
  form: [
    {
      type: 'Collection',
      label: 'Assignee',
      collectionName: 'users',
      isRequired: true,
    },
  ],
  execute: async (context, resultBuilder) => {
    const task = await context.getRecord(['id']);
    const assigneeId = context.formValues.Assignee[0];

    // Assign task
    await updateTask(task.id, {
      assignedTo: assigneeId,
      assignedAt: new Date(),
    });

    // Refresh both task assignee and user's assigned tasks
    return resultBuilder.success('Task assigned', {
      invalidated: ['assignee', 'assigned_tasks'],
    });
  },
});

With HTML result

Combine invalidation with HTML content:
return resultBuilder.success('Transaction created', {
  html: `
    <p>Transaction #${transactionId} created successfully</p>
    <p>Amount: $${amount}</p>
  `,
  invalidated: ['transactions', 'balance'],
});

With error result

Invalidation works with success results only. Errors don’t refresh data:
try {
  await createTransaction(data);
  return resultBuilder.success('Success', {
    invalidated: ['transactions'],
  });
} catch (error) {
  // No invalidation on error
  return resultBuilder.error(`Failed: ${error.message}`);
}

Finding relationship names

The relationship name in invalidated must match the field name in your schema:
  1. Open the Summary View in Layout Editor
  2. Find the Related Data section you want to refresh
  3. Note the relationship field name (e.g., emitted_transactions, comments, order_items)
  4. Use that exact name in the invalidated array

Limitations

  • Invalidation only works with success() result type
  • Only affects Summary Views with Related Data sections
  • Cannot invalidate data in other collections
  • Requires exact match of relationship field names

Alternative: Full page reload

If you need to refresh all data on the page, redirect to the current page:
const currentUrl = context.request.url;  // If available
return resultBuilder.redirectTo(currentUrl);
However, this is less efficient than targeted invalidation.