social_share_kit
Share text, links and files directly to a chosen app — X, WhatsApp, Instagram, Facebook, Threads, Telegram, LinkedIn, Messenger, TikTok, SMS, mail — or to the system share sheet.
Two things set it apart from a share-sheet wrapper:
- It tells you what will work before you try. What each app accepts differs sharply by platform. X takes text and up to four images on Android, but only text on iOS. Instagram strips pre-filled captions everywhere.
SocialShareKit.capabilities()reports all of it up front, so you can disable a button instead of discovering the limit after the user taps it. - It bundles no vendor SDKs by default. Sharing goes through published URL schemes, Android intents and system composers, so the base install needs no app id, no client token and no
AppDelegatewiring. Where a vendor SDK genuinely buys something, it is a separate opt-in package:social_share_kit_facebookgives you the native Facebook Share Dialog on both platforms. You pay for it only if you want it.
final result = await SocialShareKit.shareTo(
ShareTarget.x,
ShareContent.text('Shipping today', url: 'https://example.com'),
);
switch (result.status) {
case ShareStatus.success: // the composer opened
case ShareStatus.appNotInstalled: // hide the button next time
case ShareStatus.unsupportedContent: // result.message says why
default: break;
}
Install
dependencies:
social_share_kit: ^0.2.0
Android needs nothing. The <queries> declarations and the FileProvider ship in the plugin's own manifest and merge into your app.
iOS needs two Info.plist keys. iOS reports every app as missing unless its scheme is listed, so this is not optional:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>twitter</string> <!-- X -->
<string>whatsapp</string>
<string>instagram</string>
<string>instagram-stories</string>
<string>barcelona</string> <!-- Threads -->
<string>fb</string>
<string>facebook-stories</string>
<string>fb-messenger</string>
<string>tg</string> <!-- Telegram -->
<string>linkedin</string>
<string>snssdk1233</string> <!-- TikTok -->
</array>
<!-- Only if you share to Instagram feed or Reels -->
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Saves the media you share so Instagram can open it.</string>
Trim the list to the targets you offer; Apple caps it at 50 entries. The Facebook add-on needs two more entries and some app-id wiring — see its README.
What each target accepts
✓ supported · clip accepted but the app strips it, so it goes to the clipboard · — not supported
| Target | Android | iOS |
|---|---|---|
x |
text · link · 4 media | text · link |
whatsapp |
text · link · files | text · link · 1 image |
instagramFeed |
clip · 10 media | clip · 1 media |
instagramReels |
clip · 1 video | clip · 1 video |
instagramDirect |
text · link | text · link |
instagramStory |
story | story |
facebook |
clip · link · 10 media | — † |
facebook + add-on |
link · 6 photos or 1 video · cancel reported | link · 6 photos or 1 video · cancel reported |
facebookStory |
story | story |
messenger |
text · link · media | link only |
telegram |
text · link · files | text · link |
threads |
text · link · 10 media | clip |
linkedinFeed |
text · link · 1 image | clip + link |
linkedinDirect |
text · link · 1 image | text · link |
tiktok |
clip · 35 media | — ‡ |
sms |
text · link · files | text · link · files · cancel reported |
email |
text · link · files · subject | text · link · files · subject · cancel reported |
systemSheet |
everything | everything · cancel reported |
clipboard |
text | text |
† Fixed by the Facebook add-on. Without it, Facebook's iOS app publishes no sharing URL scheme, so there is nothing to open.
‡ Needs TikTok's OpenSDK. No add-on for it yet — use fallbackToSystemSheet: true.
Read this at runtime rather than from the table:
final caps = SocialShareKit.capabilities(ShareTarget.x);
caps.supportsFiles // false on iOS, true on Android
caps.maxFiles // 4 on Android
caps.textLimit // 280
caps.prefillsText // false for Instagram, Facebook, TikTok
caps.note // the caveat, in prose
Facebook is the one target where the base package hits a wall it cannot argue with: the Facebook iOS app publishes no sharing URL scheme. There is no intent system on iOS and no deep link to open, so ShareTarget.facebook returns ShareStatus.unsupportedPlatform there. On Android it works as an ordinary intent, though Facebook strips any pre-filled caption.
Adding social_share_kit_facebook replaces that with the native Share Dialog on both platforms:
dependencies:
social_share_kit: ^0.2.0
social_share_kit_facebook: ^0.1.0
void main() {
SocialShareKitFacebook.register(); // the only line you add
runApp(const MyApp());
}
// unchanged — now routed through the SDK
await SocialShareKit.shareTo(
ShareTarget.facebook,
ShareContent.files(['/path/photo.jpg']),
);
| base package | with the add-on | |
|---|---|---|
| Android | plain intent | native Share Dialog |
| iOS | unsupportedPlatform |
native Share Dialog |
| Photos | via intent | up to 6 |
| Video | via intent | 1 |
| Link | via intent | attached as a proper link |
| Cancellation | not detectable | ShareStatus.cancelled |
| Setup | none | Facebook app id + client token |
Cancellation reporting is the part worth noting: SharingDelegate and FacebookCallback fire on the real outcome, so success means the dialog actually completed rather than merely opened. No URL-scheme target can tell you that.
The cost is the Facebook app registration the base package avoids — an app id and client token in Info.plist and strings.xml, plus some manifest and AppDelegate wiring. The add-on's README has the exact steps. Drop the dependency and everything reverts; no call site changes either way.
ShareTarget.facebookStory does not need the add-on. The story composer works through a documented pasteboard and intent handoff, so it stays in the base package.
Writing your own add-on
Implement ShareTargetHandler, declare which targets it claims, and call SocialShareKit.registerHandler. A handler that widens what a target accepts must say so in capabilitiesFor — core validates content against the capability table before delegating, so a handler that does not advertise its wider surface will never be reached.
Usage
Text, links and files
// text only
await SocialShareKit.shareTo(ShareTarget.whatsapp, ShareContent.text('Hello'));
// files only
await SocialShareKit.shareTo(
ShareTarget.instagramFeed,
ShareContent.files(['/path/photo.jpg']),
);
// both
await SocialShareKit.shareTo(
ShareTarget.telegram,
ShareContent.textWithFiles(text: 'Look', files: ['/path/a.png', '/path/b.png']),
);
// a link as a distinct field — required by the LinkedIn feed composer on iOS,
// which ignores URLs embedded in body text
await SocialShareKit.shareTo(
ShareTarget.linkedinFeed,
ShareContent.link('https://example.com', text: 'My commentary'),
);
File paths must be absolute paths to files your app can read. On Android they are handed over as content:// URIs from the plugin's own FileProvider, which covers your cache, files, and external directories.
Falling back
await SocialShareKit.shareTo(
ShareTarget.x,
ShareContent.files(['/path/clip.mp4']),
fallbackToSystemSheet: true, // X takes no files on iOS — use the sheet there
);
The fallback triggers when the target cannot take this content on this platform. It does not trigger on empty content, which is a caller bug rather than a platform limit.
Stories
The only calls in the base package needing a Facebook app id — both composers require one for attribution and reject the share without it. No SDK involved.
await SocialShareKit.shareStory(
ShareTarget.instagramStory,
StoryContent(
appId: '<your facebook app id>',
backgroundImage: '/path/bg.jpg',
stickerImage: '/path/sticker.png', // transparent PNG
attributionUrl: 'https://example.com',
text: 'Copied to the clipboard for pasting',
),
);
A story needs a background image, a background video, or both gradient colours. Neither composer accepts pre-filled text, so text rides along on the clipboard.
Checking what is installed
final installed = await SocialShareKit.installedTargets();
if (installed[ShareTarget.whatsapp] == true) { /* show the button */ }
A target can read as missing purely because your app cannot see it — Android 11+ hides packages absent from <queries> (handled for you), iOS hides schemes absent from LSApplicationQueriesSchemes (yours to add).
Fitting text to a composer
Nothing is truncated automatically; ShareText is there when you want it.
ShareText.stripHtml('<p>Rich <b>text</b></p>'); // 'Rich text'
ShareText.truncateForTarget(
longPost,
ShareTarget.x,
keepSuffix: 'https://example.com', // reserves room so the link survives
);
What success means
That the target's composer opened. Not that the user posted — no mainstream social app reports that back, and any package claiming otherwise is guessing.
Only sms, email and systemSheet distinguish cancelled from success, because only they hand control back to your app. Everything else is a one-way URL or intent handoff. Adding the Facebook add-on puts facebook in that first group too — the SDK reports the real outcome. ShareCapabilities.reportsCancellation tells you which is which, and it follows whatever add-ons are registered.
Errors
Calls complete with a ShareResult rather than throwing — a missing app is an ordinary outcome. ArgumentError is thrown only for programming mistakes: passing a story target to shareTo, or a non-story target to shareStory.
Contributing
flutter test # 73 unit tests
cd example && flutter test # widget tests
cd example && flutter test integration_test # on a device
The capability table in lib/src/models/share_capabilities.dart is mirrored by enums in android/…/ShareTargets.kt and ios/…/ShareTarget.swift. Adding a target means touching all three; the integration test fails if the native side omits one.
Licence
MIT. See LICENSE.
Libraries
- Share text, links and files to specific social apps from Flutter.