Install Appcues without rework
What to decide before you write code, and what breaks silently if you don't.
Table of Contents
Read this before you start. It covers the decisions that are cheap now and expensive to reverse, so your team gets a working install in one pass instead of three.
The install guides have the code. This page has the reasoning: what each choice costs you later, and which parts need someone outside your team. It covers both web and mobile.
Its companion page, Brief your developer before install, is where whoever asked for this records what to send and which elements your content will attach to. If it hasn't been filled in, start there rather than guessing.
Prerequisites
- A filled-in installation plan from whoever asked for this. See Brief your developer before install.
- Your Appcues Account ID, from Settings > Account ID
- For mobile, an App ID for each app, from Settings > Apps & Installation
- Access to your application's codebase
Start from the installation plan
Appcues only knows what you send it, and only your team knows what they intend to target on. That decision belongs upstream of the code, in the installation plan.
A filled-in plan tells you:
- Which user and account properties to send, what each one is for, and whether it comes from your code or from an integration
- Which events to instrument, and which ones can be created without code
- For mobile, the screen names to report through
screen(), plus where deep links and Embed containers need to exist - Which elements in your interface content will attach to, so you can give them stable identifiers while you're already in that code
- The account details you need: hosting region, environments, and who owns the Content Security Policy
If you don't have one, ask for it before you start. Guessing at properties and events is the single most common reason an install gets done twice. An hour spent filling in the plan together costs less than a second round of instrumentation.
What the install actually involves
The code is small. The coordination determines your timeline.
Web
| Work | Owner | Typical effort | Waits on |
|---|---|---|---|
| Add the SDK | Frontend | Minutes | Your deploy cycle |
identify() and group()
|
Frontend | 1–2 hours | Access to your user object |
track() calls |
Frontend | Roughly 15 minutes per event | Which events your team agreed to send |
| Stable selectors on target elements | Frontend | 1–3 hours | Which elements the team wants to point at |
| Content Security Policy allowlist | Security or infra | Minutes to change | A separate ticket, on their schedule |
| Identity verification, if required | Backend | Half a day | Security review |
If your application enforces a Content Security Policy, budget for a second ticket and someone else's queue. Plan for more than one deploy.
Mobile
| Work | Owner | Typical effort | Waits on |
|---|---|---|---|
| Register apps and add the SDK | Mobile | 1–2 hours | Per framework setup |
identify() and group()
|
Mobile | 1–2 hours | Access to your user object |
screen() calls |
Mobile | Depends on how many screens | Your screen list |
track() calls |
Mobile | Roughly 15 minutes per event | Which events your team agreed to send |
| URL scheme and deep link support | Mobile | 2–4 hours | Your app's existing routing |
| Element recognition for Anchored Tooltips | Mobile | Varies by framework | Which elements the team wants to point at |
| FrameViews for Embeds | Mobile | 1–2 hours per container | Where Embeds should appear |
| Reaching users | Release management | Not your call | Your next app store release |
Three decisions that are expensive to reverse
Pick a User ID you will never change
Pass a unique identifier that stays the same for the life of the account. Use whatever value you prefer, though an opaque one like a UUID is the better default, because it doesn't change when a user updates their details and it isn't guessable.
When a User ID changes, Appcues treats it as a new person. Their history forks into two profiles and does not merge. Everything else in this list can be corrected by shipping a fix; this one cannot.
If you use Appcues integrations, the User ID may need to match a value in the other system. Check Match User IDs across integrations before you choose, not after.
Know what gets backfilled and what doesn't
Events you send with track() start from the moment the call ships. There is no history behind them.
Click to Track events behave differently. Appcues records raw UI interactions in the background, so an event created in the Builder is backfilled with matching past activity going back to the date raw event recording started on your account. For accounts created after July 1, 2026, that is your install date.
Installing earlier therefore preserves history your team hasn't decided it wants yet. It also means your non-technical teammates can add many events later without a code change, so you don't need to instrument everything now. Start with the events that need validation logic before they count.
Decide on identity verification before security review does
Identity verification signs your identify() calls so only users you vouch for can receive Appcues content. It needs a backend endpoint and a shared secret.
Teams that skip this question usually meet it again as a security finding after launch, which means reworking a shipped install. Answer it now, even if the answer is no.
Install on web
Each guide covers one step and ends with a Debugger check. Do them in order, working from the plan's User properties, Account properties, and Events tabs.
- Install the Appcues SDK. Script snippet or NPM package.
- Identify users and groups. Who the current user is and what account they belong to.
- Track events in your web app. What users do.
Three things that are easy to miss:
enableURLDetection is a window.AppcuesSettings global, not an argument to Appcues.setup(), and it has to be set before setup runs.
EU accounts load a different host. On the snippet, the source is https://fast.eu.appcues.com/YOUR_APPCUES_ID.js. On the NPM package, pass bundleDomain: 'https://fast.eu.appcues.com' to Appcues.setup(). See Appcues US and EU hosting environments.
Expect a CSP change if your application enforces one. The console shows a Content Security Policy error and the SDK never initializes. See Configure Content Security Policies for the domains to allow.
Install on mobile
Mobile has more required parts than web, and the docs are explicit that all of them are needed for full functionality. Start at Installing Appcues in mobile applications, which links the per-framework detail for iOS, Android, React Native, Flutter, and Ionic.
| Component | Why it's needed |
|---|---|
| SDK and initialization | Connects the app to your account, using your Account ID and App ID |
identify() and group()
|
Targeting and segmentation |
screen() |
Screen-based targeting. Without it you can't target by location in the app. |
track() |
Behavioral targeting and measurement |
| URL scheme and deep links | Debugging, previewing, and screen capture all depend on it |
| Element recognition | Anchored Tooltips attach to specific elements |
| FrameViews | Containers that mobile Embeds render into. Not supported on Ionic. |
The plan's Screens & containers tab tells you which screens to report, which deep links to support, and where Embed containers need to exist. If those rows are empty, get them filled in before you start, because adding a container later costs another release.
Two decisions worth making before you write code:
How many App IDs. A separate App ID per app instance isolates Flows and metrics between your development and production builds. One shared ID means your team builds Flows once and targets them with a property like App Name or App Version. See Register your mobile app.
Whether deep linking already exists. If your app has no URL scheme yet, this is the component most likely to take longer than expected, and previewing content is blocked until it works.
Check your progress at any point with the Mobile Debugger. Scan the QR code on your installation settings page, open https://mobilebuilder.appcues.com/?type=debugger&appId=[AppID] on the device, call debug(), or run appcues-[AppID]://sdk/debugger from your dev console.
Make your UI targetable
This is the step teams skip, and it's the one that generates repeat work.
Web: stable selectors
Appcues content attaches to your UI by CSS selector. Flows, Pins, Launchpads, Embeds, and Click to Track events all reference selectors in your markup. When the Builder auto-detects a selector on a React or Ember app, it often lands on a generated class like .css-1a2b3c4 that changes on your next build.
Nothing errors when that happens. The content stops appearing, and someone notices weeks later.
Add dedicated attributes to the elements your team will want to point at:
<button data-appcues="upgrade-cta">Upgrade</button>- Use
data-*attributes so they survive CSS refactors and don't affect styling. - Put the attribute on the element users actually click, not on a wrapping
div. - Keep names readable, so a non-technical teammate can recognize them in the Builder:
data-appcues="profile-save-button", notdata-appcues="el-7". - Use the same attributes in staging and production.
- Keep the DOM around those elements stable. Frequent re-renders that replace nodes make content unreliable.
You don't need to cover the whole application. The plan's Targeting readiness tab names the elements to start with, drawn from the first few things your team wants to launch. If it's empty, that's the one section worth pushing back on before you begin. See Prepare your frontend for element targeting for the full pattern.
Mobile: recognizable elements and containers
The mobile equivalent is element recognition. Anchored Tooltips place themselves against a specific element in the captured screen, which is what keeps them attached across screen sizes and layout changes. Elements your team wants to point at need to be recognizable, per your framework's guidance.
FrameViews are the same idea for Embeds: a container has to exist in the app before content can render into it. Adding one later means another release, so put them in the screens the plan names while you're already in the code.
Rules that fail silently
None of these throw an error. They surface later as content reaching the wrong people.
Keep types consistent. 599 is a number, "599" is a string. String comparison sorts "10" before "2".
Pick one date format per property. A non-fractional Unix timestamp, an ISO 8601 string, or a formatted string. Seconds and milliseconds are both accepted; a value with a decimal point is rejected. Never mix formats for the same property.
Omit missing values. Send null or leave the property out. An empty string is treated as a real value and matches targeting rules.
Don't send a property named userId or user_ID. The first argument to identify() is already labeled User Id. A custom property with a similar name creates a duplicate field and confusing targeting.
Call identify() before group() and before track(). Calls made earlier are held until a user is identified, then attributed to that user, so they can land on the wrong profile.
Property names appear in the UI. days_since_signup renders as "Days Since Signup". Avoid property_1.
Flatten objects. Only strings, numbers, and booleans are stored. Send totalInvoices: invoices.length, not the array.
String and event values are case sensitive. "Admin" and "admin" are different values, and Form Submitted and form submitted are two separate events.
Running both web and mobile? Use different event names per platform. Matching names pool together regardless of source. Keep universal user properties carrying the same value on both platforms even when the key names differ, and send device-specific properties and events separately.
Two more that depend on your setup:
Exclude internal and test users from your identify() call. Every unique identified user in a rolling 30-day window counts toward your MAU total, whether or not they see anything. See Exclude a subset of users.
Call Appcues.reset() on logout if you don't use anonymous users. It clears session data and in-progress Flows. If you do use anonymous users, reset also clears the generated anonymous ID, which can cause Flows to show a second time.
What you own after install
Installing Appcues creates one ongoing obligation: the things your content attaches to live in your code, and refactors break them silently.
On web, before you ship a change that renames classes or restructures the DOM, check whether Appcues references what you're changing. Ask Appcues AI, or query the Appcues MCP server from your editor:
Before I remove the
.feature-cardclass, does Appcues use it for anything?
Both return every experience and Click to Track event referencing matching selectors, with status. If something depends on it, update the Appcues content and publish that first, then ship your code. See Check if Appcues uses a selector before changing your code.
Adding that query to your frontend review checklist costs nothing per change and removes a recurring class of bug report.
On mobile the same applies to renamed screens, removed elements, and relocated FrameViews, with a longer feedback loop: a break ships with a release and takes another release to fix. Screen names in particular are worth treating as a contract rather than an implementation detail.
Definition of done
These are the outcomes to verify. Record progress on the plan's Install checklist tab rather than here, so whoever asked for this install can see where it stands without asking you. That tab tracks the steps; this list confirms they worked.
Web
- Debugger shows Installed, Connected to Appcues, and Tracking Pages green
- Tracking Pages stays green across in-app navigation without a manual refresh
- Every user and account property on the plan appears in the Debugger with the expected type
- Every event on the plan appears on the Events page after being triggered
- CSP updated and deployed, if your application enforces one
- Stable attributes added to every element on the plan's Targeting readiness tab
Mobile
- Mobile Debugger opens and shows the user, group, screens, and events
- Every screen on the plan is reported with
screen() - Deep linking confirmed, so previewing and screen capture work
- Element recognition in place for the elements the plan lists for Anchored Tooltips
- FrameViews added wherever the plan places an Embed
- Release scheduled, with a date your team knows
Both
- Internal and test users are excluded from
identify() - Selector and screen checks added to your review process
- Verified on staging and production
If something isn't working
Start with Test and debug your Appcues installation for web, or the Mobile Debugger for mobile. If you're still stuck, email support@appcues.com with:
- Your Appcues Account ID, and App ID for mobile
- The URL or screen where the issue occurs
- A screenshot of your browser console, or your device logs
- A screenshot of the Debugger
- Your installation method: direct snippet, NPM, Segment, Google Tag Manager, or which mobile framework