March 15, 2026·10 min read
Privacy nutrition labels: don’t wing it the night before submit
The questionnaire is boring until legal asks questions. Your job is usually to list SDKs honestly, tie them to actual data collected, and match what the running app does—not what marketing wishes it did. Think of labels as a contract with users: they should be able to read them and recognize the app on their phone.
Inventory SDKs
Crash, analytics, ads, auth, maps—each vendor has a data sheet. If you init it, you own the disclosure implications.
Remove dead SDKs before filing; fewer dependencies means fewer checkbox arguments.
First-party vs third-party
Data you send to your own API still counts if it identifies users. Be consistent between App Store, Play Console, and your privacy policy.
If you don’t know what an SDK sends, wireshark or vendor docs beat guessing.
Updates when you add a SDK
Treat a new analytics hook like a release checklist item: update labels, update policy page, screenshot the answers for the next audit.
Regulators care about drift between claims and behavior.
Who does what on the team
Engineering owns the SDK inventory and default initialization paths. Product or compliance usually owns the customer-facing policy wording—but engineering should sanity-check that “we don’t sell data” matches SDK defaults.
Before submit, run the app fresh install and note every permission prompt and network domain touched in the first minute. That slice is what reviewers and curious users experience first.
Mapping SDKs to data categories
Start from binaries: enumerate native modules and JS packages that initialize network clients on launch. For each, read the vendor’s data safety or privacy disclosure template—most major SDKs publish them. Map collected data types to store questionnaire categories: contact info, location, identifiers, usage data, diagnostics. Be honest about whether data is linked to the user or used for tracking per platform definitions. If you are unsure, open a ticket with the vendor or inspect traffic in a controlled environment—guessing invites inconsistency. Remove SDKs you no longer call; dead code still counts if linked. Document SDK versions in privacy review notes so you can diff changes between releases.
First-party data and your backend
Data you collect directly into your own APIs is still subject to disclosure if it identifies users. Align mobile labels with web and backend processing agreements. If analytics pipelines join mobile events with account records server-side, say so consistently across policies. Pseudonymous identifiers are not magic shields if they can be re-linked easily—disclose realistically. DPIAs or similar assessments may be required for sensitive verticals; involve legal early. Engineering should provide data flow diagrams—not marketing guesses.
Release discipline when SDKs change
Treat SDK additions like database migrations: versioned checklist, stakeholder sign-off, updated labels before rollout. Hotfixing a new analytics SDK on Friday without label updates is how teams get flagged. Automate diffing `package.json` and native dependency lockfiles between releases to highlight changes for privacy review. Keep screenshots of store questionnaires for audit trails. Train support to answer common privacy questions with approved language, not ad-libbed promises.
Children, sensitive permissions, and regional laws
Kids’ apps have stricter rules; analytics and profiling may be disallowed or tightly constrained. Health and financial data elevate scrutiny. Regional laws like GDPR or state privacy laws may require consent banners, data subject requests, or opt-out links—mobile labels are only one piece. Coordinate with web teams on unified consent management when users cross platforms. Translation errors in privacy UI can create liability—QA localized strings carefully.
Communicating with non-technical stakeholders
Translate technical collection into plain language for product and legal. Avoid jargon like ‘device fingerprint’ without context. Provide examples: ‘We send crash stack traces without names’ is clearer than ‘diagnostics.’ When product wants a new integration, supply upfront the privacy impact and alternatives. Push back when marketing requests conflict with technical reality—better a delayed launch than a store rejection or regulatory letter.
Incident response for mislabeling
If you discover inaccurate disclosures, prioritize correction: update labels, ship app updates if needed, and revise policies. Notify leadership and legal per your playbook. Postmortem: how did the gap happen? Missing SDK review, unclear ownership, rushed release? Fix process, not just copy. Regulators and stores care about timely remediation and good faith.
Ongoing monitoring
Quarterly, re-verify SDK behaviors against disclosures. Watch for SDK auto-updates in minor releases that expand data collection. Monitor community forums for vendor changes. Privacy is not a checkbox at launch—it is continuous alignment between code and claims.
Shipping and reliability habits (1)
Deep links are a cross-team system: marketing URLs, hosted association files, entitlements, router params, and analytics query preservation. Debug with structured logging of raw URLs (scrub secrets) and reproduce cold-start races with auth hydration. Staging and production should be obviously separated—accidentally opening prod from a QA link erodes trust and pollutes data.
Push notifications walk a line between helpful and intrusive. Prime users with context, respect notification channels on Android, and measure opt-outs after campaigns—spikes mean copy or frequency problems. Payload design affects background behavior; test killed and locked-device states. Tokens belong server-side with rotation strategies; never treat the client as authoritative for subscription state.
Platform differences worth rehearsing (2)
Keyboard and form UX separate polished apps from ‘works on desktop simulators.’ Platform differences in soft input modes matter; test smallest phones and Android gesture navigation. Primary actions must remain reachable when the keyboard is visible—scroll containers and keyboard controllers exist because this problem is universal.
Type-safe navigation pays off when routes multiply. Keep param lists near navigators, validate external URLs, and avoid serializing non-JSON-safe values through params. Renaming routes is a cross-cutting change—update analytics, push payloads, and E2E selectors in the same release train.
Security, privacy, and data handling (3)
Hermes versus JSC is not a lifestyle choice—profile your app. Hermes usually wins on startup; some libraries still assume JSC quirks. Engine toggles are not substitutes for fixing quadratic renders in your own code. Upgrade notes matter: Intl support and debugging tooling evolve.
Reanimated and gesture libraries earn their place when profiling proves UI-thread work and your team can maintain native upgrades. Worklets have constraints—read errors carefully. Respect reduced motion and test Android timing differences—identical JS does not guarantee identical feel.
Performance and measurement discipline (4)
Environment variables should be classified: public-by-design, sensitive-with-mitigations, or never-on-device. `EXPO_PUBLIC_` values are extractable—treat them that way. Align env handling across EAS profiles and local dev; fail fast when keys are missing instead of shipping undefined behavior.
Project structure should make ownership obvious: routes as backbone, feature folders for product areas, thin screens, and shared infrastructure that is deliberately named. Refactor in vertical slices with device-tested releases—big-bang rewrites without tests are how teams lose weeks.
Team process and long-term maintenance (5)
Monorepos amplify both leverage and failure modes: duplicate React versions cause mysterious hook errors, and Metro misconfiguration blocks local packages from resolving. Invest in workspace discipline—single React version, documented `watchFolders`, and lint rules preventing packages from importing app navigators accidentally. CI must mirror local installs; ‘works on my laptop’ with different package managers is a time bomb.
Native modules are product decisions disguised as engineering tasks. You inherit Xcode and Gradle upgrades, store review scrutiny, and security obligations. Prefer maintained Expo modules and config plugins before writing JNI or Swift glue from scratch. When you must go native, budget pairing time with platform specialists and write runbooks for on-call—crashes in native code bypass many JS safeguards.
Shipping and reliability habits (6)
E2E tests should protect revenue paths, not every permutation. Stable selectors (`testID`) beat text that marketing rewrites weekly. Flake management is a feature: quarantine, fix root causes, and keep smoke suites green on CI devices. Five reliable tests beat fifty flaky ones that everyone ignores.
OTA updates are powerful and risky: runtime compatibility, rollback plans, and user-visible behavior changes need governance. Channels should map to release maturity—staging versus production—with access controls on publish credentials. Large assets over cellular need care; silent failures erode trust more than a frank ‘update failed, retry’ message.
Platform differences worth rehearsing (7)
Privacy nutrition labels and Play Data Safety forms should reflect actual SDK behavior—inventory dependencies each release and remove dead code. Drift between claims and telemetry is legal and store risk, not just embarrassment. Involve legal early when adding analytics or ads.
Expo SDK upgrades are integration projects: `expo doctor`, aligned community packages, regenerated native projects, and device smoke tests for camera, push, and IAP. Freeze unrelated native refactors during the upgrade window and keep rollback paths hot. Document surprises for the next upgrade while memory is fresh.
Security, privacy, and data handling (8)
ScrollView versus FlatList is a data-volume question. Small static content belongs in ScrollView; long feeds belong in virtualized lists. Nested scrollables need explicit height contracts—redesign beats fighting physics. Document intentional choices so future refactors do not ‘optimize’ blindly.
Metro cache issues masquerade as logic bugs. Establish a documented reset ladder: dev server restart, cache flags, Watchman, derived data, then dependency reinstalls. Compare platforms when only one breaks—native steps diverge. Keep CI caches deterministic with lockfiles and pinned toolchains.
Performance and measurement discipline (9)
Accessibility is compatibility. Labels, focus order, and dynamic type are not polish—they determine whether users can complete tasks at all. Test with VoiceOver and TalkBack on hardware; simulators miss focus bugs. When designs prioritize minimalism, negotiate text alternatives for icon-only controls. Accessibility regressions often follow navigation redesigns—add checklist items to those PRs specifically.
Internationalization is a product feature, not a string swap. Plural rules, RTL layout, and locale-aware formatting change behavior—not just copy length. Pseudolocale helps find clipping early, but real Arabic and German QA catches nuance. Avoid concatenating translated fragments; context matters. Document glossary terms so translators do not invent inconsistent product names.
Team process and long-term maintenance (10)
Storage is not a database. AsyncStorage and MMKV excel at key-value preferences; SQLite or remote APIs belong elsewhere for relational data. Migrations should be incremental, logged, and non-blocking for UI. Secure tokens need secure storage when your model demands it—speed is not a substitute for correctness on auth material.
WebViews are untrusted browsers inside your app. Validate `postMessage` payloads, lock navigation to expected hosts, and prefer system-browser auth flows when OAuth security demands it. Third-party JavaScript can change without your deploy—treat XSS in web as bridge compromise risk. Clear storage on logout and rate-limit message handlers.
Shipping and reliability habits (11)
Native modules are product decisions disguised as engineering tasks. You inherit Xcode and Gradle upgrades, store review scrutiny, and security obligations. Prefer maintained Expo modules and config plugins before writing JNI or Swift glue from scratch. When you must go native, budget pairing time with platform specialists and write runbooks for on-call—crashes in native code bypass many JS safeguards.
Deep links are a cross-team system: marketing URLs, hosted association files, entitlements, router params, and analytics query preservation. Debug with structured logging of raw URLs (scrub secrets) and reproduce cold-start races with auth hydration. Staging and production should be obviously separated—accidentally opening prod from a QA link erodes trust and pollutes data.
Platform differences worth rehearsing (12)
WebViews are untrusted browsers inside your app. Validate `postMessage` payloads, lock navigation to expected hosts, and prefer system-browser auth flows when OAuth security demands it. Third-party JavaScript can change without your deploy—treat XSS in web as bridge compromise risk. Clear storage on logout and rate-limit message handlers.
E2E tests should protect revenue paths, not every permutation. Stable selectors (`testID`) beat text that marketing rewrites weekly. Flake management is a feature: quarantine, fix root causes, and keep smoke suites green on CI devices. Five reliable tests beat fifty flaky ones that everyone ignores.
Security, privacy, and data handling (13)
Splash screens and launch gates should reflect honest readiness: fonts, theme, session, critical remote config—not every SDK under the sun. Infinite splash is worse than a slightly longer branded hold. Match native and JS background colors to avoid flashes; respect reduced motion for animations.
JWT and session refresh flows need single-flight refresh, clear logout semantics, and secure storage for refresh tokens when appropriate. Parallel 401s should not stampede refresh endpoints. Clock skew and biometrics policies belong in explicit product decisions, not accidental implementation details.