Skip to content
Back to skills

Activity And Task Patterns

ASecurity

Task and Event objects: polymorphic WhatId/WhoId, Activity object model, ActivityHistory vs OpenActivity, activity timeline customization, bulk task creation, Einstein Activity Capture boundaries. NOT for turning on EAC or calendar sync — use admin/einstein-activity-capture-setup. NOT for Email-to-Case — use admin/case-management-setup. Covers ActivitiesSettings metadata, enableActivities, shared Task/Event custom fields and FLS, TaskStatus value sets, Shared Activities and TaskRelation/Event...

  • 15 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 1, 2026
ai-agentspythongospringapidatabasesecurityperformance

Works with

  • api

Security analysis

A100/100

Pro scans all 7 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add PranavNagrecha/AwesomeSalesforceSkills --skill activity-and-task-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Activity And Task Patterns?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Activity And Task Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/pranavnagrecha-activity-and-task-patterns/badge)](https://www.skillsdirectory.com/skills/pranavnagrecha-activity-and-task-patterns)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: activity-and-task-patterns
description: "Task and Event objects: polymorphic WhatId/WhoId, Activity object model, ActivityHistory vs OpenActivity, activity timeline customization, bulk task creation, Einstein Activity Capture boundaries. NOT for turning on EAC or calendar sync — use admin/einstein-activity-capture-setup. NOT for Email-to-Case — use admin/case-management-setup. Covers ActivitiesSettings metadata, enableActivities, shared Task/Event custom fields and FLS, TaskStatus value sets, Shared Activities and TaskRelation/EventRelation, recurring and child activities, and partial-success bulk DML on Task."
category: admin
salesforce-version: "Spring '25+"
well-architected-pillars:
  - Reliability
  - Performance
tags:
  - task
  - event
  - activity
  - whatid
  - whoid
  - einstein-activity-capture
triggers:
  - "task whatid whoid polymorphic lookup soql"
  - "why are some activities missing from the record activity timeline"
  - "bulk create tasks from apex dml best practice"
  - "activityhistory openactivity difference"
  - "einstein activity capture data storage and reporting"
  - "custom fields on activity task event sharing"
  - "salesforce task whatid whoid polymorphic typeof"
  - "bulk task insert emailed every assignee an assignment notification"
  - "cannot enable shared activities by deploying activitiessettings"
  - "task subject or status filter is slow and cannot be custom indexed"
  - "update event failed only isreminderset allowed on child event"
  - "emailmessage activityid points at a deleted task"
  - "add a custom field to task but not event"
  - "activitiessettings metadata xml enablerecurringtasks enablegrouptasks"
  - "insert tasks with partial success instead of rolling back the batch"
inputs:
  - Objects requiring activity tracking
  - Volume of tasks/events generated per day
  - Einstein Activity Capture license status
  - Reporting requirements on activities
outputs:
  - Activity model decision (Task, Event, custom)
  - Polymorphic query patterns
  - Bulk creation pattern
  - Activity reporting approach
dependencies: []
version: 1.2.0
author: Pranav Nagrecha
updated: 2026-09-05
---

# Activity and Task Patterns

Activate when designing interactions with Salesforce Activities — Task and Event records that attach to other objects via polymorphic `WhatId` and `WhoId`. The Activity object model is unusual: `Activity` is a read-only abstract parent, `Task` and `Event` are concrete children, and `ActivityHistory` / `OpenActivity` are read-only related lists, not queryable in bulk.

## Before Starting

- **Understand the Activity object model.** `Activity` cannot be queried directly; query `Task` or `Event`. `ActivityHistory` and `OpenActivity` appear on related lists and subqueries only.
- **Know the polymorphic fields.** `WhatId` can reference any object enabled for activities; `WhoId` references Contact or Lead. Require `TYPEOF` or explicit type filters in SOQL.
- **Check Einstein Activity Capture.** EAC-captured emails/events are stored outside standard Task/Event and are not reportable the same way.

## Questions to Ask Before Configuring

Ask these before writing a line of Apex or opening Setup. Each one maps to a platform behaviour documented in `references/gotchas.md`; skipping them produces activity code that passes review and then misbehaves in production.

| Ask | Why it matters | What a good answer adds |
|---|---|---|
| "Is Shared Activities on in this org, and who turned it on?" | It cannot be enabled or disabled by a deploy — `allowUsersToRelateMultipleContactsToTasksAndEvents` has been read-only in every API version since v36.0 | Whether `TaskWhoIds`, `TaskRelation` and multi-contact relates exist at all, before code assumes them |
| "Who owns these activities, and should each one email its owner?" | Apex DML that creates or modifies a Task triggers the assignment notification by default | A decision to set `EmailHeader.triggerUserEmail`, and a backfill that doesn't mail thousands of people |
| "What is the read path — which field will the WHERE clause filter on?" | `Subject`, `TaskStatus` and `TaskPriority` are on the platform's can't-index list; `WhatId` / `OwnerId` / date ranges are not | Whether Task/Event survives the volume, or the design needs a custom `Interaction__c` |
| "Does this field make sense on both a Task and an Event?" | There is no Task-only custom field; Activity fields land on both children and share field-level security | The validation rule that scopes it, and the FLS entries for **both** objects in the same deploy |
| "Are any of these activities recurring, or occurrences of a series?" | Recurring Tasks can't be flipped after creation and reject `ActivityDate`/`Status` updates; child Events accept updates to `IsReminderSet` and `ReminderDateTime` only | A partition step before the bulk DML instead of a half-failed job |
| "Is Einstein Activity Capture on for anyone here?" | EAC-captured mail and events live outside the `Task`/`Event` tables, so reports and triggers silently miss them | An explicit source-of-truth statement in the data dictionary |
| "Can one bad row be allowed to fail on its own?" | The bare `insert` verb rolls the whole transaction back; `Database.insert(..., dmlOptions, ...)` does not | A partial-success write plus the `Database.SaveResult` loop that reports what didn't land |

What a proper configuration adds over just creating the records: the activity model matches the org's actual read path and volume, the shared Task/Event surface is deployed as one unit instead of half of it silently disabling FLS, and bulk writes fail one row at a time and quietly instead of rolling back a 200-record save while emailing everyone.

## Core Concepts

### Task vs Event

Task: to-do item with due date. Event: calendar appointment with start/end time. Both share the polymorphic `WhatId` / `WhoId` pattern and an IsTask discriminator on Activity queries.

### Polymorphic SOQL

```
SELECT Id, Subject, What.Type, TYPEOF What
  WHEN Account THEN Name, Industry
  WHEN Opportunity THEN Amount, StageName
END FROM Task
```

Without TYPEOF, only ID and Type are accessible via `What.Type`.

### ActivityHistory vs OpenActivity

`ActivityHistory`: closed activities (completed tasks, past events). `OpenActivity`: open activities (due, upcoming). Only queryable as subqueries from activity-enabled parents. Cannot create/update these objects directly.

### Einstein Activity Capture

EAC syncs emails and calendar events from Exchange/Gmail into Salesforce. Data lives in a separate EAC store — visible on timeline, but not in Task/Event tables. Reporting requires Activity Metrics or EAC-specific features.

## Common Patterns

### Pattern: Bulk task creation from trigger

```apex
List<Task> tasks = new List<Task>();
for (Opportunity o : Trigger.new) {
    tasks.add(new Task(WhatId = o.Id, Subject = 'Review',
                       ActivityDate = Date.today().addDays(7), OwnerId = o.OwnerId));
}

Database.DMLOptions dml = new Database.DMLOptions();
dml.optAllOrNone = false;                  // one bad WhoId must not roll back the save
dml.EmailHeader.triggerUserEmail = false;  // suppress "a task was assigned to you"

List<Database.SaveResult> results =
    Database.insert(tasks, dml, AccessLevel.SYSTEM_MODE);
```

Collect and write once — never loop-DML. The two `DMLOptions` lines are not optional polish: creating or modifying a Task is one of the events that fires `triggerUserEmail`, and the bare `insert` verb has nowhere to put either setting. Full version with result handling in `references/metadata-examples.md` section 5.

### Pattern: Custom Object for high-volume activity-like data

Past roughly 50k activities/day per object — UNVERIFIED (2026-09-05): a field heuristic, no activity-volume ceiling is published in the App Limits cheat sheet or the LDV guide — consider a custom `Interaction__c` with a real lookup instead of polymorphic Task. The grounded reason to switch is the read path, not the number: the LDV guide lists Activity `Subject`, `TaskStatus` and `TaskPriority` among the standard fields the platform can't index, so a query that must filter on any of them will not get selective on a large Task table.

### Pattern: Activity rollup via Lightning component or Apex

Activity count / last-activity-date rollups: use formula-friendly patterns (e.g., Salesforce's Activity Metrics, EAC Insights, or DLRS).

## Decision Guidance

| Need | Approach |
|---|---|
| Standard to-do with reminders | Task |
| Calendar appointment with participants | Event |
| Query must filter on activity subject or status at volume | Custom `Interaction__c` with an indexed field |
| Email tracking without EAC | EmailMessage + Task linking |
| Reporting on email activity | Activity Metrics or EAC Insights |

## Recommended Workflow

1. **Establish what the org already does.** Retrieve `Settings:Activities` and check whether Shared Activities is on — it is a read-only field no deploy can change, and `TaskWhoIds` / `TaskRelation` / `EventRelation.IsParent` only exist when it is. Note whether Einstein Activity Capture is enabled for anyone (`admin/einstein-activity-capture-setup`).
2. **Decide Task/Event vs custom `Interaction__c`** using the Decision Guidance table above and the tradeoff table in `references/well-architected.md`. Drive the decision from the read path and the sharing requirement, not from record count alone.
3. **Write the metadata from `references/metadata-examples.md`** — `Activities.settings-meta.xml`, `enableActivities` on every object that must be a valid `WhatId`, the custom field under `Activity` (never under Task), the `TaskStatus` value set if `IsClosed` semantics are changing, and FLS entries for Task **and** Event in the same permission set.
4. **Write the read path with `TYPEOF` or a `What.Type` filter**, and the write path with `Database.insert(records, dmlOptions, accessLevel)` carrying `optAllOrNone = false` and `EmailHeader.triggerUserEmail = false`. Partition out recurring Tasks and child Events (`IsChild = true`) before any bulk update.
5. **Lint the working tree** with `python3 skills/admin/activity-and-task-patterns/scripts/check_activity_and_task_patterns.py --manifest-dir force-app/main/default`. It flags `FROM Activity`, DML on `ActivityHistory`/`OpenActivity`, loop-DML on Task/Event, `What.<field>` without `TYPEOF`, bare `insert`/`update` of Task collections, `ActivitiesSettings` files that try to set the read-only Shared Activities flag, and permission-set FLS that names one child object but not the other.
6. **Read `references/gotchas.md` end to end before deploying** — the ten entries are the failures this skill exists to prevent, and several of them (the 500-row subquery cap, shared FLS, the EmailMessage task swap) are invisible in a sandbox with test-sized data.
7. **Verify in the target org** using the three checks at the end of `references/metadata-examples.md`: a Task insert against the newly activity-enabled object, a `Status`/`IsClosed`/`CompletedDateTime` query, and a `FieldPermissions` query that must return a row for both `Task.<field>` and `Event.<field>`.

## Review Checklist

- [ ] Activity volume estimated and matched to object choice
- [ ] Polymorphic SOQL uses TYPEOF or explicit type filters
- [ ] Bulk DML used for task/event creation
- [ ] ActivityHistory / OpenActivity queries only in subquery form
- [ ] EAC data strategy documented
- [ ] Custom fields on Activity limited (they propagate to both Task and Event)
- [ ] Sharing model for activities understood (inherits from WhatId parent)
- [ ] FLS deployed for `Task.<field>` **and** `Event.<field>` — a missing entry disables, it does not preserve
- [ ] Bulk writes use `Database.insert(..., dmlOptions, accessLevel)` with `optAllOrNone = false`
- [ ] `EmailHeader.triggerUserEmail` set deliberately on any job that creates or modifies Tasks
- [ ] Recurring Tasks and `IsChild = true` Events partitioned out before bulk update
- [ ] Shared Activities state confirmed in the org, not assumed from a deploy
- [ ] `WHERE` clauses filter on an indexable field, not `Subject` / `Status` / `Priority` alone
- [ ] `scripts/check_activity_and_task_patterns.py --manifest-dir …` run clean

## Salesforce-Specific Gotchas

1. **Custom fields on `Activity` propagate to both Task and Event.** You cannot add a field only to Task — architect with this in mind.
2. **Activities inherit sharing from the `WhatId` parent.** No independent sharing rules.
3. **`OpenActivity` and `ActivityHistory` cannot be modified.** They are projections of Task/Event — the Object Reference lists `describeSObjects()` as their only supported call (object_reference.txt L23515–L23516, L191459–L191460).
4. **Shared Activities is not deployable.** Its `ActivitiesSettings` field has been read-only in every API version since v36.0; a deploy that sets it succeeds and changes nothing.
5. **`Task.IsClosed` is never written directly.** It is derived from the `Status` value's `closed` flag in the `TaskStatus` standard value set, and `Event` has no `Status` at all.

The ten platform behaviours behind these — with what happens, when, and how to avoid — are in `references/gotchas.md`.

## Output Artifacts

| Artifact | Description |
|---|---|
| Activity model decision | Task vs Event vs custom object |
| SOQL pattern library | Polymorphic queries with TYPEOF |
| Bulk creation template | Apex trigger / batch pattern |

## Reference Files

| File | Read it when |
|---|---|
| `references/metadata-examples.md` | Writing the deployable XML — `ActivitiesSettings`, `enableActivities`, an Activity custom field and its FLS, `TaskStatus`, plus the bulk-safe Apex and the post-deploy verification queries |
| `references/gotchas.md` | Before deploying, and whenever activity behaviour differs between sandbox and production |
| `references/examples.md` | Two worked scenarios end to end: a polymorphic timeline query for an LWC, and bulk task generation from a record-triggered context |
| `references/llm-anti-patterns.md` | Reviewing AI-generated Apex or metadata that touches Task, Event or Activity |
| `references/well-architected.md` | Choosing between Task/Event and a custom `Interaction__c`, and citing the sources behind any claim in this skill |

---

## Related Skills

- `apex/apex-polymorphic-soql` — `TYPEOF`, `What.Type` and `instanceof` in depth
- `apex/apex-dml-patterns` — `Database.DMLOptions` beyond the email header, and partial-success result handling
- `apex/trigger-framework` — where activity-creating logic belongs in a handler
- `admin/einstein-activity-capture-setup` — EAC sync scope, and where EAC data actually lives
- `admin/case-management-setup` — case-related activity handling
- `admin/email-to-case-configuration` — the intake side of the `EmailMessage.ActivityId` task
- `admin/picklist-and-value-sets` — standard vs global value sets, for the `TaskStatus` change
- `admin/queues-and-public-groups` — `Task.OwnerId` accepts Groups of type Queue only
- `architect/large-data-volume-architecture` — when activity volume forces a custom object
- `data/data-archival-strategies` — what to do with `IsArchived` activities

Files in this skill

  • SKILL.md6.1 KB
  • references/examples.md762 B
  • references/gotchas.md706 B
  • references/llm-anti-patterns.md2.9 KB
  • references/well-architected.md1.2 KB
  • scripts/check_activity_and_task_patterns.py3 KB
  • templates/activity-and-task-patterns-template.md639 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…