Repository navigation
Filtering posts and stories
Filters download only the posts or story items that match a condition, for example posts from a certain year, posts with a hashtag, or only videos. A filter is a small expression in Python syntax that is checked for every item.
- Using a filter
- Examples
- Variables
- The expression language
- Working with dates
- Working with text and lists
- Story filters
- Quoting
- When a filter fails
- Filters and fast updates
InstaloaderSharp.exe --post-filter "likes > 1000" profilename
--post-filter EXPRESSION (or its other name --only-if) is checked for every post, tagged post and reel. Posts for which it is false are not downloaded and are reported as skipped:
Only download posts with property "likes > 1000".
[ 1/812] <Post DdwalmrkTSK> skipped
[ 2/812] instagram\2026-10-07_18-17-32_UTC.jpg [Who IS she? …] json
--storyitem-filter EXPRESSION does the same for story items (--stories, --highlights, :stories).
The program checks the expression before it starts: a syntax error, or a name that is not a property, stops it with exit code 2 (Invalid filter: ...).
| Filter | Downloads |
|---|---|
"likes > 1000" |
Posts with more than 1000 likes |
"not is_video" |
Only pictures (posts that are not videos) |
"is_video" |
Only videos |
"typename == 'GraphSidecar'" |
Only posts with several pictures or videos |
"date_utc >= datetime(2024, 1, 1)" |
Posts from 2024 and later |
"datetime(2024, 1, 1) <= date_utc < datetime(2025, 1, 1)" |
Posts from 2024 |
"date_utc.year == 2023 and date_utc.month == 12" |
Posts from December 2023 |
"(datetime.utcnow() - date_utc).days < 30" |
Posts from the last 30 days |
"'travel' in caption_hashtags" |
Posts with the hashtag #travel
|
"'travel' in caption_hashtags and likes > 100" |
Posts with #travel and more than 100 likes |
"caption is not None and 'giveaway' not in caption.lower()" |
Posts whose caption does not mention "giveaway" |
"'friend.name' in tagged_users" |
Posts in which friend.name is tagged |
"owner_username == 'profilename'" |
Posts of a particular account, for example in :feed or a hashtag |
"is_video and video_duration < 60" |
Videos shorter than a minute |
"viewer_has_liked" |
Posts you liked (logged in), useful with :feed
|
"not is_pinned" |
Posts that are not pinned to the profile |
Story filters:
| Filter | Downloads |
|---|---|
"not is_video" |
Only picture stories |
"date_utc >= datetime(2026, 10, 1)" |
Story items from October 2026 on, for example in highlights |
The variables of a post filter are the post properties, and those of a story filter the story item properties. The most useful ones:
| Variable | Meaning |
|---|---|
date_utc |
When the post was published, in UTC |
date_local |
The same, in your computer's time zone (with a time zone, see below) |
likes, comments
|
Number of likes and comments |
is_video |
Whether the post is a video |
typename |
'GraphImage' (picture), 'GraphVideo' (video) or 'GraphSidecar' (several pictures or videos) |
mediacount |
Number of pictures and videos in the post |
caption |
The caption text, or None
|
caption_hashtags |
List of hashtags in the caption, lowercase, without #
|
caption_mentions |
List of usernames mentioned in the caption, lowercase, without @
|
tagged_users |
List of usernames tagged in the post, lowercase |
owner_username, owner_id
|
The account that posted it |
video_duration, video_view_count, video_play_count
|
For videos: length in seconds, number of views and plays |
viewer_has_liked |
Whether you liked the post (when logged in) |
is_pinned, is_sponsored
|
Whether the post is pinned to the profile, or a paid partnership |
location |
The post's location, or None; has .name, .lat, .lng and .id
|
shortcode, mediaid
|
The post's identifiers |
Some properties need an extra request to Instagram for each post, notably location and, for some posts, likes, comments and video_view_count. Filtering on them is slower. Properties that Instagram did not provide are None.
The syntax is that of a Python expression:
| Element | Examples |
|---|---|
Numbers, strings, True, False, None
|
1000, 2.5, 'text', "text"
|
| Comparisons, also chained |
likes > 100, likes != 0, 100 <= likes < 1000
|
| Boolean logic |
and, or, not
|
| Membership |
'cat' in caption_hashtags, 'x' not in caption
|
| Identity |
caption is None, location is not None
|
| Arithmetic |
+ - * / // % **, for example comments * 10 > likes
|
| Conditional expression | video_duration if is_video else 0 |
| Lists, tuples, sets, dictionaries | owner_username in ['a', 'b'] |
| Indexing and slicing |
caption[:10], caption_hashtags[0]
|
| Attributes and methods |
date_utc.year, caption.lower(), location.name
|
| f-strings | f'{likes}' |
| Dates |
datetime(2024, 1, 1), datetime.utcnow(), see below |
The only names available are the item's properties and datetime. Python functions such as len(), any() or str(), imports, and list comprehensions are not available: len(caption) > 0 stops with Invalid filter: len not a Post attribute. Use methods of the values instead, and caption as a truth value (caption and ...) to test for an empty caption.
datetime(year, month, day) and datetime(year, month, day, hour, minute, second) create a date, and datetime.utcnow() and datetime.now() the current time. Dates can be compared, and subtracting two dates gives a duration with .days and .total_seconds():
--post-filter "date_utc >= datetime(2024, 6, 1)"
--post-filter "(datetime.utcnow() - date_utc).days <= 7"
Dates have .year, .month, .day, .hour, .minute, .second, .weekday() (0 is Monday, 6 is Sunday), .isoweekday() (1 is Monday) and .strftime(...):
--post-filter "date_utc.weekday() >= 5" posts from weekends (in UTC)
--post-filter "date_local.hour < 6" posts made at night, in your time zone
--post-filter "date_utc.strftime('%m-%d') == '12-24'"
date_utc has no time zone, like datetime(...), so the two can be compared directly. date_local includes your time zone and cannot be compared with datetime(...); use date_utc for comparisons, or convert the date with .astimezone(): date_local >= datetime(2024, 1, 1).astimezone().
Captions are text (or None if the post has none) with Python's string methods: .lower(), .upper(), .startswith(...), .endswith(...), .find(...), .count(...), .split(), .strip(), .replace(...), .isdigit() and more. in tests whether a text contains another:
--post-filter "caption is not None and 'recipe' in caption.lower()"
--post-filter "caption and caption.startswith('New')"
caption_hashtags, caption_mentions and tagged_users are lists of lowercase names:
--post-filter "'cat' in caption_hashtags or 'cats' in caption_hashtags"
--post-filter "caption_mentions == []" posts that mention nobody
--storyitem-filter uses the story item properties: date_utc, date_local, expiring_utc, expiring_local, is_video, typename ('GraphStoryImage' or 'GraphStoryVideo'), caption, caption_hashtags, caption_mentions, owner_username, owner_id, shortcode, mediaid, url and video_url.
InstaloaderSharp.exe --login YOUR-USERNAME --stories --storyitem-filter "is_video" profilename
In the Windows command prompt, put the whole filter in double quotes and use single quotes for texts inside it:
InstaloaderSharp.exe --post-filter "'travel' in caption_hashtags" profilename
The form --post-filter="..." works too. In PowerShell, the same quoting works. In an options file, write the option and the filter on one line without the outer quotes:
--post-filter=likes > 1000 and not is_video
If comparing or calculating fails for a post, for example because the filter compares a missing value (None) with a number, that post is skipped with an error:
<Post DdwalmrkTSK> skipped. Filter evaluation failed: '>' not supported between instances of 'NoneType' and 'int'
The download continues with the next post, and the error is repeated at the end.
Calling a method or reading an attribute of a missing value, such as caption.lower() for a post without a caption, is not caught in this way and stops the program with an error (as in Instaloader). Always guard properties that can be missing with is not None:
--post-filter "caption is not None and 'recipe' in caption.lower()"
--post-filter "video_duration is not None and video_duration > 60"
Skipped posts do not stop a --fast-update: the program goes on to the next post, and a fast update stops only at a post that passes the filter and was downloaded before. With a filter that skips most posts, a fast update therefore still goes through many posts. --latest-stamps stops at the first post older than the last download, whether it passes the filter or not. See Keeping an archive up to date.
Getting going
Downloading
- Choosing what to download
- Keeping an archive up to date
- Filtering posts and stories
- File and folder names
- Interrupting and resuming
- Rate limits and connections
Reference
- Command-line options
- Properties reference
- File formats
- Interoperability with Instaloader
- Differences from Instaloader
- Library use
- Troubleshooting and FAQ
- Glossary
Project