justinholtweb / craft-tokr
TikTok feeds for Craft CMS — connect an account once, keep its videos in your own database, and drop a configurable, cached feed into any template.
Package info
github.com/justinholtweb/craft-tokr
Type:craft-plugin
pkg:composer/justinholtweb/craft-tokr
Requires
- php: ^8.2
- ext-json: *
- craftcms/cms: ^5.3.0
README
TikTok feeds for Craft CMS 5.
Connect a TikTok account once, and Tokr keeps a local copy of its profile and videos in your database. Front-end requests read from that copy, so your pages never wait on TikTok's API — and never break when it's slow.
Requirements
- Craft CMS 5.3 or later
- PHP 8.2 or later
- A TikTok for Developers app with Login Kit and Display API added
Installation
composer require justinholtweb/craft-tokr php craft plugin/install tokr
Or find Tokr in the Craft Plugin Store.
Tokr is a single paid edition, $99 one-off. There is no free tier and nothing is gated — the plugin you install is the whole plugin.
Setting up the TikTok app
- Create an app at developers.tiktok.com.
- Add the Login Kit and Display API products.
- Under Login Kit, add a redirect URI. Tokr shows the exact value it expects at
Settings → Plugins → Tokr; it looks like
https://example.com/tokr/oauth/callback. TikTok requires HTTPS and an exact match. - Request the scopes you need:
user.info.basicandvideo.listare required,user.info.profilegets you the bio and username, anduser.info.statsgets you follower and like counts. - Copy the client key and secret into Tokr's settings. Both fields accept
environment variables —
$TIKTOK_CLIENT_SECRET— which is the right way to handle the secret.
While your app is in TikTok's sandbox, only accounts you've added as target users can be connected.
Connecting an account
Tokr → Accounts → Connect an account sends you through TikTok's
authorization screen and back. Tokr stores the access and refresh tokens
encrypted with your securityKey and renews the access token on its own.
TikTok refresh tokens last a year. When one expires the account is flagged Needs reconnecting on the Accounts screen and you'll need to run through authorization again.
Feeds
A feed is a saved set of display options. Create one under Tokr → Feeds, give it a handle, and render it:
{{ craft.tokr.render('homepage') }}
That's the whole integration. Everything else — layout, column counts, captions, click behavior — is configured in the control panel.
Building your own markup
If you'd rather write the HTML yourself:
{% set feed = craft.tokr.feed('homepage') %}
{% set account = feed.account %}
<h2>{{ account.displayName }} — {{ account.followerCount|number }} followers</h2>
<ul>
{% for video in feed.videos %}
<li>
<a href="{{ video.url }}">
<img src="{{ video.coverImageUrl }}" alt="{{ video.caption }}">
</a>
<p>{{ video.captionHtml }}</p>
<p>{{ video.likeCount|number }} likes · {{ video.durationFormatted }}</p>
</li>
{% endfor %}
</ul>
Overriding the bundled template
Create _tokr/feed.twig in your own templates folder and Tokr will render it
instead of its own, passing feed, account, and videos. Copy
vendor/justinholtweb/craft-tokr/src/templates/_frontend/feed.twig as a starting
point.
Twig reference
| Call | Returns |
|---|---|
craft.tokr.render('handle') |
The rendered feed markup |
craft.tokr.render('handle', { … }) |
Same, with extra template variables |
craft.tokr.feed('handle') |
A Feed model |
craft.tokr.feeds() |
Every feed |
craft.tokr.videos('handle') |
A feed's Video models |
craft.tokr.account('@username') |
An Account model, by username or ID |
craft.tokr.accounts() |
Every connected account |
Video properties: videoId, caption, captionHtml, title,
description, coverImageUrl, url, embedUrl, duration,
durationFormatted, aspectRatio, width, height, likeCount,
commentCount, shareCount, viewCount, postedAt, account.
Account properties: name, username, handle, displayName,
avatarUrl, bioDescription, profileUrl, isVerified, followerCount,
followingCount, likesCount, videoCount, isConnected, dateRefreshed.
Keeping feeds fresh
By default, the first front-end request after a feed goes stale queues a background refresh job, and the page renders from what's already stored.
The more predictable option is cron:
*/30 * * * * cd /path/to/project && php craft tokr/refresh
php craft tokr/refresh— refresh every stale accountphp craft tokr/refresh --force— refresh everything regardlessphp craft tokr/refresh/account @username— refresh one account
If you go the cron route, turn off Refresh in the background in the settings so front-end requests never queue anything.
About thumbnail expiry
TikTok's cover_image_url values are signed and stop working roughly six hours
after they're issued. This is a TikTok constraint, not a Tokr one. Keep the
refresh interval below six hours (Tokr caps it at five) so thumbnails are always
replaced before they die.
Settings
Settings live at Settings → Plugins → Tokr, or in config/tokr.php:
<?php return [ 'clientKey' => '$TIKTOK_CLIENT_KEY', 'clientSecret' => '$TIKTOK_CLIENT_SECRET', 'refreshInterval' => 3600, 'refreshOnRequest' => true, 'videosPerRefresh' => 40, 'keepRemovedVideos' => false, 'includeCss' => true, 'scopes' => [ 'user.info.basic', 'user.info.profile', 'user.info.stats', 'video.list', ], ];
scopes is only settable from the config file. Only request scopes your TikTok
app has actually been approved for — Tokr drops fields it wasn't granted rather
than failing the request, but asking for an unapproved scope fails
authorization outright.
Styling
Tokr registers one stylesheet that scopes everything under .tokr and reads a
handful of custom properties, so you can restyle without overriding much:
.tokr { --tokr-accent: #000; --tokr-radius: 0; }
Set Include Tokr's CSS to off to drop it entirely.
Permissions
Two permissions, both under Tokr in the user group settings:
- Manage TikTok feeds
- Connect and manage TikTok accounts
Documentation
Full docs at justinholt.com/plugins/craft-tokr/docs.
License
See LICENSE.md. Tokr is commercial software licensed per production environment.