Quick Social SaaS documentation

Quick Social SaaS is a self-hosted Laravel application you run as a subscription business: customers sign up on your website, pick a plan, pay through one of seventeen gateways and publish to their social channels from their own workspace, while you run the plans, the payments and the site from the staff console. This guide covers installation, the three areas of the site, every screen and its controls, and how to connect each channel.

Requirements

PHP8.2 or newer with the extensions BCMath, cURL, Exif, Fileinfo, GD, Gettext, Mbstring, MySQLi, PDO, PDO MySQL and Zip. The installer checks every one of them.
DatabaseMySQL 5.7+ or MariaDB 10.3+. SQLite works for local development.
Web serverApache with mod_rewrite (the included .htaccess files handle the rewriting) or Nginx pointed at public/.
HTTPSRequired on a live server. The social platforms only accept https callback URLs for OAuth sign-in, and TikTok refuses plain http entirely, so the root .htaccess sends http requests to https. localhost is exempt, so a local copy runs over plain http. On Nginx, add the equivalent redirect to the server block.
FFmpegOptional. Needed by the video studio and for cover frames. Installed on the server and on the PATH, or set FFMPEG_PATH in .env.
CronOne entry that runs the Laravel scheduler every minute (see Scheduler). It publishes scheduled campaigns, works the queue that sends every campaign, reads the channels at night, and keeps the money side moving: it expires unpaid orders, reconciles subscriptions with the gateways and sweeps trials with their reminders. Without it nothing is published on schedule, trials never end and renewals are only noticed when a customer opens the site. Hosting without cron can use the web address shown on the Scheduler page from an external cron service.
WebhooksThe payment gateways call back to /payment/webhook/{gateway} over https. A site behind a password or on a local address never receives them: subscriptions then renew only when the nightly reconcile runs.

Installation with the installer

  1. Upload the contents of the quick-social-saas folder to your hosting. On cPanel-style hosting upload everything into a folder and point the domain (or subdomain) at that folder; the root .htaccess sends every request to public/. If you can choose the document root, point it at public/ directly.
  2. Create an empty MySQL database and a user with full rights on it (cPanel: MySQL Databases).
  3. Open the site in a browser. Until the application is installed every visit goes to the installer at /installer/. The application owns its domain or subdomain: the installer and the panel live at the root of that address, not in a sub-folder.
  4. Server requirements: every row must show Enabled. Missing extensions are enabled from the hosting control panel (Select PHP Version / PHP Extensions).
  5. Folder permissions: bootstrap/cache, storage and its app, framework and logs folders, resources and database need permission 0755 or more, and the application root must be writable so the installer can create .env. On Linux: chmod -R 755 storage bootstrap/cache resources database.
  6. Database and admin: the site's URL and name, the database name, user, password and host, and the name, email and password of the admin account you will run the site with. Every table in that database is replaced by the application's tables. Click Install.
  7. The installer creates the tables and the four plans a new site starts with (Free, Starter, Growth and Agency), adds your admin account, switches on the gateways that need no keys, writes .env with a fresh application key, links the storage folder and sends you to the console sign-in at /admin/login. It locks itself once storage/installed exists.
A fresh site holds no customers, no workspaces and no sample content: the first customer is the first person who signs up on your website. To show the site with content in it, as a live preview does, run php artisan db:seed - it adds sample workspaces, campaigns, posts, channel numbers and ideas - and php artisan qs:demo-accounts for the demo sign-ins.

Manual installation (developers)

composer install
cp .env.example .env            # fill in the database settings
php artisan key:generate
php artisan app:install         # database, storage link, the admin account (add --samples for the preview content)
php artisan db:seed --class=Database\\Seeders\\PlanSeeder   # the four plans and the gateway rows
npm install && npm run build
php artisan serve

app:install asks for the site owner's name, email and password (or takes --name, --email and --password; add --samples for the sample content) and marks the application installed, so the browser installer never shows. Open /admin/login and sign in with that email and password.

Later accounts: php artisan qs:admin adds an admin or changes one's password; php artisan account:password --email= sets a customer's password. Customers are never created from the console - they sign up on the website.

Scheduler (cron)

Scheduled campaigns, the nightly read of the channels for Feeds and Analytics, the clean-up of temporary media and everything the subscriptions need all run from the Laravel scheduler. Add one cron entry on the server:

* * * * * /usr/bin/php /home/USER/quick-social-saas/artisan schedule:run >> /dev/null 2>&1
qs:publish-dueEvery minute. Publishes the campaigns whose time has come, for every workspace.
qs:orders-expireEvery 15 minutes. Closes orders nobody paid, so a coupon or a plan is not held forever.
qs:trialsHourly, and once a night with --enforce. Sends the reminders three days and one day before a trial ends, then ends the ones that ran out.
qs:subscriptions-reconcileNightly. Asks the gateways what really happened to the renewing subscriptions, in case a webhook never arrived.
qs:check-channels, qs:collect-insights, qs:prune-*Nightly. The channel sign-ins, the numbers behind Feeds and Analytics, and the clean-up.

Settings, Scheduler shows this line with the server's own paths and a Copy button, whether the scheduler has run in the last five minutes, whether the queue worker has been seen, and the hour of the two nightly jobs (the channel read and the temporary media clean-up). The dashboard also shows the scheduler's state. Locally, php artisan schedule:work does the same job. The times of the nightly jobs are server time.

The queue

Every campaign is sent by a queued job, from the composer, the calendar, the dashboard and the scheduler alike, so a long video upload never runs inside a web request. With QUEUE_CONNECTION=database (the default) the cron line above also works the queue for most of every minute; a server that can keep a process running may run php artisan queue:work as a service instead, and after an update run php artisan queue:restart. With QUEUE_CONNECTION=sync there is no worker at all: a campaign is sent inside the request or the scheduler tick that started it.

A channel that fails for a passing reason (an outage, a rate limit) is tried again after 5, 15 and 45 minutes; a channel that refuses the sign-in is marked so on the company's page and in the campaign log. A run the server interrupted is closed after 45 minutes with what was recorded; nothing is sent twice.

Hosting without cron

Settings, Scheduler also shows a web address. An external cron service that opens it every minute runs the same tick the cron line would. The address carries a secret made for this installation; treat it like a password.

Backups

Back up the database, the storage/app/public folder (uploads, logos and generated media) and the .env file with your hosting's backup tool. The .env file holds the key that every saved token and API key is encrypted with: without it a restored database cannot read them, so never run php artisan key:generate on an installed site.

First sign-in

Sign in at /admin/login with the admin email and password you entered in the installer. The console dashboard opens on the numbers of a site that has not sold anything yet. Before the first customer arrives, four things are worth doing, in this order:

  1. Settings, Payment gateways: the currency, the tax rate and your seller details, then the gateway you want to be paid through with your own keys (see Payments and gateways). Until one gateway is ready, only the Free plan can be taken.
  2. Settings, SMTP: the mailbox the site sends from. Without it, sign-ups are trusted without confirming their email and no invitation, reset or trial reminder goes out.
  3. Plans: the four plans the installer created are yours to rename, reprice or retire (see Plans and limits).
  4. Settings, Branding: the name, logo, favicon and colours the website, the workspaces and the emails wear.

Forgot the password? Forgot password? on the console sign-in page emails a reset link once SMTP is set up. Without email, run php artisan qs:admin on the server to set a new one.

How the site is arranged

One installation serves three areas, each with its own address, its own sign-in and its own sidebar:

/The website: home, features, channels, pricing, contact and the legal pages, with sign-up and sign-in. This is what a visitor sees. See The public website.
/appA customer's workspace: the panel they publish from, described screen by screen from The sidebar onwards. They sign in at /login.
/adminThe staff console: plans, orders, payments, invoices, coupons, webhooks, the site's settings and the contact inbox. You sign in at /admin/login. See The staff console.

Who is who

Admins are the people who run the site. They live in their own table, sign in only at /admin/login, and every admin opens everything in the console. Add them with php artisan qs:admin.

Clients are your customers. They sign up on the website, own the workspaces their plan pays for, and are the only ones who see Plan and billing. Team members are the people a client invites into their workspaces: they do the same work inside a workspace but never see the plan, the billing or the invoices, and cannot add a workspace.

The two sides never mix: a console session is never a customer session, an admin cannot publish from a workspace, and a URL that names another account's campaign, channel or file is refused before it is ever loaded.

The public website

The site at / is part of the product, not a separate theme.

HomeHero with the app's own screenshot, the channel marquee, three steps, eight features, showcase sections, a numbers band, the plan cards with a monthly/yearly switch, FAQ and a closing call to action.
FeaturesSix long sections, each with a screenshot of the workspace.
ChannelsAll fourteen channels and what each one accepts: text, image, video, native upload.
PricingThe plan cards and a comparison table drawn from the plans' own limits. Start a trial goes to /register?plan= with that plan chosen.
ContactTopics, a honeypot and rate limiting. Every message is stored and shown in the console under Messages, and mailed to the seller address once SMTP is set.
/p/about, terms, privacy, refunds, cookiesTemplate texts to edit before you open the doors. Sign-up and checkout link to terms and privacy from their consent boxes.
sitemap.xml, robots.txtWritten by the application from the pages above.

Changing the words

Everything the website says lives in config/site.php: the hero, the steps, the features, the showcase sections with their screenshots (from public/assets/site), the numbers band, the FAQ and the footer. :brand in any of those texts is replaced by the name set under Branding. The legal pages are Blade files in resources/views/site/legal. A signed-in client sees Open the app in the header instead of the sign-up buttons.

Plans and limits

Plans in the console lists every plan with its price, its trial and how many accounts are on it. New plan and a click on a row open the same form:

Name, address name, taglineThe name customers see, the word used in links (/register?plan=growth), and the line under the name on the pricing page.
Monthly price, yearly priceIn the store currency. A price left at zero makes that term unavailable; a plan with both at zero is the free plan.
Free trial (days)0 for none. One trial per account, whichever plan it is taken on.
Order, highlight, activeWhere the card sits, whether it is drawn as the popular one, and whether it is offered at all. Retiring a plan leaves the accounts already on it alone.
LimitsWorkspaces, channels per workspace, team members per workspace, posts a month, AI credits a month, library storage per workspace (MB), analytics history (days). An empty box means unlimited.
FeaturesImage studio, video studio and Google Drive: on or off.

Where a limit is felt

Limits are checked where the customer acts, and the reason is always shown in words: adding a workspace, connecting a channel (and saving one by hand), inviting a team member, publishing or scheduling a campaign, every scheduled run, asking the AI for text, an image or a video, uploading to the library, opening a studio, and the range on the analytics pages. Posts and AI credits count per calendar month in UTC; a failed AI call gives its credit back.

Trials, downgrades and cancellations

A trial is a subscription like any other, with the gateway trial. Reminders go out three days and one day before it ends. When it ends the account falls back to the free plan, and qs:trials --enforce is what does it.

A downgrade never deletes anything: the workspaces over the new limit - the newest first - are locked read-only until the customer upgrades again or removes one. A cancelled subscription keeps working until the end of the term it was paid for.

A site with no plans at all limits nothing: that is what makes the application behave like the single-site edition.

Payments and gateways

Settings, Payment gateways holds the store side and the gateways.

The store

Currency, tax rate and whether prices include it, the seller name and address that goes on invoices, and the invoice prefix and next number. These are used by every order.

The gateways

PayPal, Stripe, Braintree, Square, Mollie, Paystack, Flutterwave, PayU, SSLCommerz, AamarPay, ToyyibPay, Skrill, GoCardless, Amazon Pay, VR Pay and Cryptomus, plus bank transfer, which needs no account: the customer uploads a receipt and an admin confirms it. Open one to switch it on, choose test or live, paste your keys, and use Test connection. The page shows the webhook address to register with that gateway, and Register webhook where the gateway's API allows it. Saved keys are encrypted and never shown again: paste a new value to replace one. PayPal also has Sync plans, which mirrors your plans into PayPal so a subscription can renew there.

A gateway in test mode takes test cards only. Switch it to live and re-enter the live keys before you open the doors.

Orders, payments and invoices

Orders lists everything bought, with its status, the plan and term, the gateway and the total; search by number, customer or email. An order opened shows its items, the payments against it, the customer's billing details and, for a bank transfer, the receipt with Mark as paid. Refund sends the refund through the gateway where it supports it, or records one you made by hand; either way a credit note is written.

Payments is the money itself, with the last 30 days totalled at the top. Invoices lists every invoice with its number and customer; an invoice opens as a printable page (print to PDF from the browser). Coupons takes a code, a percentage or a fixed amount, the plans it applies to, how many times it may be used and when it expires. Webhooks shows what each gateway sent, whether it was processed, and Retry for the ones that failed.

Customers and workspaces

Signing up

A visitor signs up at /register, with a plan already chosen when they came from the pricing page. They name their first workspace, accept the terms and confirm their email - unless the site cannot send email, in which case the address is trusted. Their first workspace is created with them as its owner.

Team members

A client invites people from Team in their workspace, by email or a link they copy. An invitation lasts seven days and can be resent or withdrawn. The person who accepts becomes a team member: they publish, schedule and use the studios in that workspace, but they never see Plan and billing, cannot add workspaces and cannot invite anyone. An email that already belongs to a client is refused - a client cannot become someone else's team member.

Suspending and removing

A suspended account cannot sign in and nothing it owns is published. Removing a member from the last workspace they belonged to signs them out; a new invitation lets them back in. A client deleting a workspace deletes its channels, campaigns and reports with it.

The staff console

The sidebar at /admin: Dashboard; under Billing, Plans, Orders and Payments (with Invoices, Coupons and Webhooks under it); under Site, Messages; and Settings.

Dashboard

Monthly recurring revenue, the last 30 days' revenue, new customers, running trials and churn across the top; a twelve-month revenue chart, new sign-ups over 30 days and accounts by plan; Needs a look for the things waiting on you (a bank transfer to confirm, a webhook that failed, a trial about to end); then the recent orders and the newest customers.

Settings

One card per area: AI providers (the keys and the model per feature, shared by every workspace), SMTP, Scheduler (the cron line for this server, whether it has run, and the nightly hours), Firebase (browser push), Payment gateways and Branding. Saved keys are encrypted; a saved secret is never shown again.

Messages

Everything sent through the website's contact form, newest first, with the topic and the sender, and delete when it is dealt with.

The sidebar follows the order the work happens. Dashboard sits on top. Publish holds Social campaigns (Create campaign, All campaigns, Campaign log), Publish video (Create video, All videos, Video log), the Calendar and Ideas. Measure holds Feeds and Analytics (Overview, Campaign analytics, Video analytics). Create holds the Media library and the Studio (Image studio, Video studio). Settings closes the list. A group opens on click and remembers its state; the sidebar collapses to icons with the button in the top bar, and a collapsed group shows its pages on hover.

Three pages belong to every campaign and video: the composer where it is made, the archive where it is kept, edited or deleted, and the log where each publish run is recorded. Their numbers live under Analytics.

Dashboard

Compose

The quick composer at the top: pick a format (Auto, Link post, Image post, Video post, Publish video), type the message or attach media with Image and Video, and click Compose. Auto asks the AI to plan the post for the channels you have connected; the result opens in the full composer.

Needs attention

Failed or partly failed runs (with Retry), channels that need a new sign-in (Sign in), drafts that are not scheduled (Open), and the scheduler status.

Channels

One tile per connected channel, in its colour: the account, its followers with the change over the week (its reach where the channel reports no followers), the posts, reach and engagements of the last seven days, and a line of the engagements day by day. The row scrolls sideways. A channel that only publishes (a LinkedIn profile, Telegram, WhatsApp, Discord) shows what is stored about its posts.

Last 7 days

New posts, reach, engagements, engagement rate and followers, read from the channels. Read the channels now fetches fresh numbers.

Ideas for the week

Five AI-written post ideas for the coming days. Compose opens one in the composer, Save keeps it in Ideas, Dismiss hides it, Suggest again asks for a new set.

This week and Best times

The week's scheduled campaigns with links to the calendar, and a heat map of the best hours to post per channel, from the channel's own numbers.

Social campaigns: Create campaign

Social campaigns holds three pages: Create campaign (the composer), All campaigns (the archive) and the Campaign log (the record of publish runs).

The composer publishes link posts, image posts and video posts to the social channels.

ControlWhat it does
Format tabsLink post, Image post, Post a video. Each format lists only the channels that accept it.
Channel chipsClick to select a channel. Grey chips are channels that are not connected yet; hover them for the reason.
MessageThe text of the post; the first link in it is the post's link, shown as the channel shows a shared link. Media opens the media library, Emoji inserts an emoji, Write with AI drafts the text from a short brief.
PreviewOne card per channel, in that channel's look, with the character counter where the platform enforces one. Each card has its own Publish button.
Save draftKeeps the campaign without sending it. Drafts open again from the calendar and the dashboard.
SchedulePick a date and time; the scheduler publishes it. The campaign appears on the calendar.
Publish allShows the selected channels as they stand, a check for each one that is ready and what the others still need; Publish now sends the open format to every one of them, Back returns to the composer. The results appear under the composer with the post links or the error per channel.

Channel-specific options appear when their channel is selected: subreddit and title for Reddit, the WhatsApp template.

Channels, the row at the top of the dashboard, carries one tile per signed-in channel: its followers with the week's change, its posts, reach and engagements over the last seven days, and a line of its daily engagements. The row scrolls sideways.

Opportunities lists the channels a recent campaign did not go to although they are signed in and take its format, each drawn as that channel would show the post: a row on the dashboard, and the full list under Social campaigns, Opportunities (/app/opportunities), by channel, twenty at a time with Load more. On each card, Publish on … sends the campaign to that channel at once and records it on the campaign; Edit opens the composer with the campaign's content and that channel picked; the cross dismisses the card for good. Refresh reads the list again.

Social campaigns: All campaigns

The archive: every campaign, newest first, with its type, the channels it went (or will go) to, its status and date. Filter by Published, Planned (drafts, scheduled and missed) or Failed, or search by title. Edit opens a planned campaign in the composer and a sent one on its record page. The bin removes a campaign together with its runs (a campaign being published right now waits until it finishes). All videos under Publish video is the same archive for video uploads.

The record page

A sent campaign's page holds everything it carried: the Shared message (or a video's title, description and tags), the media, and one card per channel drawn the way that channel shows the post, as in the composer. Edit the shared fields and press Update all channels, or press Edit on a card to open that channel's own text (or title, description and tags) and settings inside it, then its own Update; the change is saved on the record and sent to that channel alone. Use shared text puts the shared fields back. Each card says what its channel still lets change after publishing: the text on Facebook, Telegram, Discord and Reddit (text posts); title, description, link and board on Pinterest; title, description, tags, thumbnail and settings on YouTube; title, description, thumbnail and privacy on Vimeo. TikTok, Instagram, X, LinkedIn, Threads, Bluesky and WhatsApp keep what they were sent, so their cards carry no Update. Write with AI under a field drafts it for the channels of the record, or for one channel alone on its card. On a video, the Cover strip under the player takes a new thumbnail from this computer, the library or Google Drive; it is sent to YouTube and Vimeo with the next Update, once each.

Social campaigns: Campaign log

Every publish run of a social campaign, newest first, with the channels it went to, how many were sent and how many failed. Filter by status, search by title. Video uploads have their own Video log under Publish video. Open a run to see each channel's result: the post ID and link for successes, the error text for failures, and Retry to send a failed channel again (the retry is recorded as a new run). The activity log lists what happened step by step.

Publish video: Create video, All videos, Video log

Create video is the native video composer; All videos is the archive of uploads, each opening on its record page (see All campaigns); the Video log is the record of upload runs. Native video uploads with each platform's own settings: YouTube (visibility, schedule, category, language, playlist, made for kids, notify subscribers, first comment, Short or video), Vimeo (privacy), TikTok (who can view, comments, duet, stitch), Instagram Reels (also show on the profile feed), Facebook Reels and Pinterest video pins (board, destination link).

Add the video by dropping a file, choosing one from the library or pasting a hosted URL; add an optional cover image or pick the frame time used as the cover. Generate a video with AI creates a short clip from a description and puts it in the library.

Calendar

Week and month views of scheduled, published and missed campaigns, plus the drafts list. Drag a scheduled campaign to another day to reschedule it. Click a campaign for its actions: Edit (opens it in the composer), Publish now, Back to draft, Duplicate, Delete. The connected channels are shown in the strip at the top.

Ideas

Every idea you saved from the dashboard, with the day it was suggested for and why. Compose opens it in the dashboard composer, Remove moves it to the Removed list, from where Restore brings it back and Delete removes it for good. The lightbulb in the top bar opens the same lists as a drawer on any page.

Feeds

The posts on your channels as the channels report them, all together or one channel at a time, with their numbers. Reuse opens a post's text and media in the composer. Some channels are read on demand to respect their API budgets; the button on the channel's card reads it.

Analytics: Overview

Reach, engagements, followers and posts over the last 7, 30 or 90 days, per channel and in total, with the best times heat map. Read the channels now fetches fresh numbers; the scheduler reads them every night.

Analytics: Campaign and Video analytics

Campaign analytics lists every campaign that went out with the audience and engagement it has gathered so far and its engagement rate; Video analytics does the same for video uploads. Open one for its page.

A campaign's page is its scorecard. The band at the top shows the channels with their outcome, when it went out and when the channels were last read, and the two figures that matter: the audience (impressions, views or reach, whichever the channel gives) and the engagements (likes, comments, shares, saves and clicks), each with its trend and what the last read added. Growth since it went out draws the audience day by day with the engagement each nightly read added; By channel shows each channel's share; below, one card per channel shows the post it made, the numbers that channel reports and a link to the post. A failed channel shows the reason with Retry, or Sign in when the channel needs it. Publish again opens a fresh draft with the same content. Channels that do not report numbers (Telegram, WhatsApp, Discord, a LinkedIn member profile) say so on their card.

Media library

Everything uploaded or generated. Add media uploads files (or drop them anywhere on the page); filter by images or videos, search by name. A file's side panel shows its size and links to use it in the composer, the publisher or the studios, and Delete removes it. The cap per file comes from the server's PHP limits; an optional total cap is set with MEDIA_LIBRARY_LIMIT_MB.

Image and video studio

Image studio: pick a size (post, story, cover, thumbnail), start from a library image or an AI-generated one, add text layers with fonts, colours and effects, and save the result to the library.

Video studio: open a library video, trim it, add captions and a cover frame, and render it on the server with FFmpeg. The render lands in the library, ready to publish.

Workspaces

A customer publishes for more than one brand, client or project by adding workspaces - companies, as the screens call them - as many as the plan allows. Each has its own social channels, campaigns, calendar, campaign log, ideas, feeds, analytics and team. The AI keys, the SMTP mailbox and the branding are the site's, set once in the console.

The workspace switcher

The top bar shows the company you are working in. Click it to switch: every page then shows that company's channels and campaigns. The choice is remembered. Add company and Manage companies sit at the bottom of the list.

Managing companies

Settings, Companies (/app/settings/companies) lists the account's workspaces with its channel and campaign counts. Click a card to open the company: its name, logo, contact details (website, email, phone and address, all optional) and brand voice (the description the AI writer, the planner and the ideas of the week write from, and Tailor text per channel with AI: with it on, the composer writes each selected channel its own version of the message, shown in its preview card and editable there) on the left, and its social channels on the right. Opening a company makes it the current one. Remove deletes a company with all of its channels, campaigns and reports after a confirmation; the last company cannot be removed.

The scheduler publishes every workspace's campaigns on the whole site, each with that workspace's own channels, and the nightly channel read covers them all. A workspace locked by a downgrade is read-only: its scheduled runs stop with that reason in the log.

Team

Team (/app/team) lists the people in the workspace and the invitations waiting. The owning client invites by email or a copied link, resends, withdraws and removes; a member can leave. The plan sets how many members a workspace may have.

Plan and billing

/app/billing is the customer's own page, and only the client who pays can open it.

The plan card at the top names the plan, what it costs, when it renews or ends, and carries Upgrade, Switch plan, Or pay now during a trial, Add a month for a prepaid term and Cancel renewal. Under it, usage meters for the month (posts, AI credits, storage) and Your workspaces against the plan's limit, with any locked by a downgrade marked as read-only.

Plans shows the cards with a monthly/yearly switch; choosing one opens checkout: billing details, the coupon box, tax and the total, the payment methods you allow, and the terms box. PayPal's buttons, Braintree's form and Amazon Pay are drawn inline; bank transfer asks for a receipt and waits for an admin. Orders and invoices lists everything the account has bought, with a printable invoice for each, and Billing details keeps the name, company, tax number and address that goes on them.

Team members do not see this page at all: every checkout route answers them with 403.

Settings

/app/settings shows one card per area: Companies, AI providers, SMTP, Scheduler, Branding and Notifications. Social channels live on each company's page (/app/settings/companies/{id}/edit); AI settings (/app/settings/ai) and SMTP settings (/app/settings/smtp) are shared by every company. Save settings in the page header saves the open page. Values are encrypted before they are stored; a saved secret is never shown again, paste a new value to replace it.

Social channels

On the right of a company's page. One card per channel with its status, the account it publishes as, and the buttons next to the title: Sign in with … for channels that connect through OAuth, Disconnect once connected. Keys opens the credentials of the channel (client IDs, secrets, tokens) and shows the Redirect URI to register in the platform's developer app. Every company keeps its own keys and sign-ins. See Connecting each channel.

AI providers

A key per provider (OpenAI, Anthropic, Google Gemini, OpenRouter). A pasted key is checked with the provider straight away. Under AI features, choose the provider and model for text, image generation, image editing and video generation; the model lists come from the provider. The brand voice the AI writes with is set per company, see Companies.

SMTP

The mailbox that sends password reset emails: host, port, encryption, username, password and the from address. Send a test email sends one to your account.

Profile, branding, notifications

From the account menu at the bottom of the sidebar:

Profile: name, email, avatar, time zone (used for scheduling and the calendar) and password.

Branding: the name, logo, favicon, colours, fonts, footer text and light/dark theme of the panel. The brand voice used by the AI belongs to each company.

Notifications: everything the app tells you about (runs, sign-ins that expired, channels connected), with per-event switches and browser notifications. The bell in the top bar shows the unread ones.

Connecting each channel

Every channel is connected under Settings, Channels (the company's channel cards). Open a card's Keys to paste what the platform gives you, then press the card's Sign in button where there is one. Channels that use OAuth need an app in the platform's developer portal, and that app must carry this redirect URI exactly (it is also shown under the card's Keys, with your own domain filled in):

https://your-domain.com/app/settings/connect/{provider}/callback

where {provider} is facebook, twitter, linkedin, tiktok, youtube, pinterest, reddit, threads or vimeo. The platforms only accept https redirect URIs, so the application must be online with a valid certificate before a channel can be signed in. The steps below are what each developer portal asks for; the portals change their wording now and then, but the pieces stay the same.

Facebook Page and Instagram

  1. Go to developers.facebook.com/apps and press Create app. Choose the Business type (or "Other" then "Business"), name it after your brand, and pick the Business portfolio that owns your Page if you have one.
  2. In the app's dashboard add the Facebook Login for Business product (plain Facebook Login also works). Under its Settings, paste the redirect URI for facebook into Valid OAuth Redirect URIs and save.
  3. Open App settings, Basic. Copy the App ID and the App secret (press Show) into the Facebook card's Keys as App ID and App secret, and save.
  4. Press Sign in with Facebook on the card. Facebook asks which Pages the app may manage; allow the Page you post to. The application asks for the permissions pages_show_list, pages_manage_posts, pages_read_engagement, pages_read_user_content, read_insights and business_management.
  5. Pick the Page from the list. The application stores a permanent Page token and the Page ID itself; if the Page has a professional Instagram account linked to it, the Instagram card connects at the same time.
  6. While the app is in Development mode only people with a role in the app (Roles, under App settings) can sign in, which is fine for your own Pages. To let other people connect their Pages, submit the same permissions for App review and switch the app to Live.

Prefer a pasted token? Generate a long-lived Page access token in the Graph API Explorer (choose your app, your Page and the same permissions), then paste it with the Page ID into the card instead of signing in. Instagram then needs the Instagram account ID (from the Page's Instagram settings, or the Graph API field instagram_business_account).

X (Twitter)

  1. Sign in at developer.x.com and open the Developer Portal. On a first visit, choose a plan (Free covers posting from your own account) and create a Project, then an App inside it.
  2. Open the app's Settings and press Set up under User authentication settings. Choose Read and write for the permissions, Web App, Automated App or Bot for the type, paste the redirect URI for twitter as the Callback URI, put your site's address as the Website URL and save.
  3. Open Keys and tokens. Under OAuth 2.0 Client ID and Client Secret copy both into the X card's Keys as OAuth 2.0 Client ID and Client Secret, and save.
  4. Press Sign in with X and allow the access. The application asks for tweet.read, tweet.write, users.read, media.write and offline.access, and renews the token itself from then on.

LinkedIn

  1. Go to linkedin.com/developers/apps and press Create app. Give it a name, a LinkedIn Page it belongs to (every app needs one; create a Page first if you have none), a logo and the legal agreement.
  2. On the app's Products tab request Share on LinkedIn and Sign In with LinkedIn using OpenID Connect. Both are granted at once. To post as a company page and read its statistics, also request the Community Management API; LinkedIn reviews that one and asks what the app does.
  3. On the Auth tab paste the redirect URI for linkedin under Authorized redirect URLs for your app. Copy the Client ID and the Primary Client Secret into the LinkedIn card's Keys, and save.
  4. Press Sign in with LinkedIn and allow the access. The application asks for openid, profile and w_member_social, and for w_organization_social and r_organization_admin as well once the Community Management API is on the app. After signing in, pick Post as: your profile or one of the pages you administer. Posting to a page also lets Feeds and Analytics read it.

TikTok

  1. Sign in at developers.tiktok.com, open Manage apps and press Connect an app. Fill in the name, category and description, and add your site's Terms of Service and Privacy Policy addresses, which TikTok requires.
  2. Under Add products add Login Kit and Content Posting API. In Login Kit paste the redirect URI for tiktok; in Content Posting API turn on Direct Post.
  3. Under Scopes add user.info.basic, user.info.stats, video.upload, video.publish and video.list, then submit the app for review; TikTok asks for a short screen recording of the sign-in and the posting.
  4. Copy the Client key and Client secret from the app's credentials into the TikTok card's Keys, save, and press Sign in with TikTok.
Until TikTok approves the app, every post it makes is visible only to you (privacy Only me); the composer says so on the TikTok options. Approval lifts that.

YouTube

  1. Open console.cloud.google.com and create a project (or pick one). Under APIs & Services, Library enable YouTube Data API v3 and YouTube Analytics API.
  2. Under APIs & Services, OAuth consent screen (Google Auth Platform) set the app name, the support email and the developer email, choose External, and add your Google account under Test users. In testing, only test users can sign in, which is fine for your own channel; publishing the app to production needs Google's verification for the YouTube scopes.
  3. Under Credentials press Create credentials, OAuth client ID, type Web application. Add the redirect URI for youtube under Authorized redirect URIs.
  4. Copy the Client ID and Client secret into the YouTube card's Keys and save.
  5. Press Sign in with Google on the YouTube card and allow the access; the application asks for youtube.upload, youtube.force-ssl, youtube.readonly and yt-analytics.readonly.

Pinterest

  1. Convert the account to a business account if it is not one (Settings, Account management on pinterest.com). Then open developers.pinterest.com/apps and press Connect app; describe what the app does and accept the developer terms.
  2. In the app's configuration add the redirect URI for pinterest under Redirect URIs. Copy the App ID and the App secret into the Pinterest card's Keys and save.
  3. New apps start in trial access, which allows posting to your own account; request standard access from the same page when other people's accounts need to connect.
  4. Press Sign in with Pinterest and allow the access (boards:read, pins:read, pins:write, user_accounts:read). Enter the Default board ID pins land on (the number in the board's address on pinterest.com, or leave it empty and pick the board in the composer, which lists your boards once you are signed in).

Telegram

  1. In Telegram open a chat with @BotFather, send /newbot, and answer with a display name and a username ending in bot. BotFather replies with the bot token; paste it into the Telegram card's Keys.
  2. Open your channel (or group), go to Administrators and add the bot as an administrator with the right to post messages.
  3. Enter the Channel / chat ID: for a public channel its handle, such as @yourchannel; for a private channel or group its numeric ID, such as -1001234567890 (forward a message from it to @userinfobot or @getidsbot to read the ID). Save; the application checks the bot and the channel as it saves.

Reddit

  1. Signed in as the account that posts, open reddit.com/prefs/apps and press create another app at the bottom.
  2. Choose web app, give it a name and a description, and paste the redirect URI for reddit as the redirect uri. Press create app.
  3. The Client ID is the short string under the app's name (below "web app"); the secret is shown next to it. Copy both into the Reddit card's Keys, save, and press Sign in with Reddit (identity, submit, read, mysubreddits, history).
  4. Set the Default subreddit; the composer lets you choose another per post. The account must be allowed to post in it.

WhatsApp Business

  1. In a Meta app (the one from the Facebook steps works) add the WhatsApp product. Its API Setup page shows a test number to start with; to send from your own number, add it under Phone numbers and verify it (the number must not be registered in the WhatsApp app on a phone).
  2. On the same page copy the Phone number ID and the WhatsApp Business Account ID into the WhatsApp card's Keys.
  3. The temporary token on that page expires within a day. For a lasting token open Business settings, System users, add a system user (Admin), assign it the WhatsApp app and the WhatsApp account, and Generate new token with the permissions whatsapp_business_messaging and whatsapp_business_management, expiry Never. Paste it as the Access token.
  4. Enter the Recipients: the phone numbers the campaign is sent to, one per line in international format. Save; the application checks the token and the number as it saves.
WhatsApp only delivers messages people have opted into. Outside a 24-hour customer window a message must use an approved template (Meta Business Suite, WhatsApp Manager, Message templates); the composer lets you pick a template per campaign.

Threads

  1. Go to developers.facebook.com/apps and create an app; when asked what the app should do choose Access the Threads API (Threads apps are separate from Facebook apps).
  2. Under Use cases, Access the Threads API press Customize and add the permissions threads_basic, threads_content_publish and threads_manage_insights. Under Settings paste the redirect URI for threads under Redirect Callback URLs and add your site under Uninstall and Delete callback URLs (any page of your site is accepted).
  3. Open App settings, Basic and copy the Threads App ID and Threads App Secret (they differ from the Facebook ones) into the Threads card's Keys, and save.
  4. Under Roles, Threads testers invite the Threads account that posts, and accept the invitation in the Threads app (Settings, Account, Website permissions). Press Sign in with Threads and allow the access.

Bluesky

  1. In Bluesky open Settings, Privacy and security, App passwords and press Add app password. Name it after this application and copy the password shown once.
  2. Enter the Handle (such as yourname.bsky.social, or your own domain if the account uses one) and the App password in the Bluesky card's Keys. Save; the application signs in as it saves to check them. Never use the account's main password here.

Discord

  1. In your Discord server open the channel's settings (the cog next to the channel name), go to Integrations, Webhooks and press New Webhook. Give it a name and an avatar; they show as the author of every post.
  2. Press Copy Webhook URL and paste it as the Webhook URL in the Discord card's Keys. Save; the application checks the webhook as it saves.

Vimeo

  1. Go to developer.vimeo.com/apps and press Create an app. Name it, describe it, and paste your site's address as the app URL.
  2. In the app, under Authentication, paste the redirect URI for vimeo under Your callback URLs. Copy the Client identifier and the Client secret into the Vimeo card's Keys, and save.
  3. Under Permissions request Upload access; Vimeo reviews it (usually within a few days) and uploads fail until it is granted. Then press Sign in with Vimeo (public, private, upload, edit).

Checklist

ChannelWhat goes in the cardSign-in buttonPortal
FacebookApp ID, App secret (or a pasted Page token and Page ID)Sign in with Facebook, then pick the Pagedevelopers.facebook.com
InstagramNothing when the Page is connected; else an access token and the Instagram account ID—Linked to the Facebook Page
X (Twitter)OAuth 2.0 Client ID, Client SecretSign in with Xdeveloper.x.com
LinkedInClient ID, Primary Client SecretSign in with LinkedIn, then Post aslinkedin.com/developers
TikTokClient key, Client secretSign in with TikTokdevelopers.tiktok.com
YouTubeClient ID, Client secretSign in with Googleconsole.cloud.google.com
PinterestApp ID, App secret, Default boardSign in with Pinterestdevelopers.pinterest.com
TelegramBot token, Channel / chat ID—@BotFather
RedditClient ID, Client secret, Default subredditSign in with Redditreddit.com/prefs/apps
WhatsApp BusinessAccess token, Phone number ID, Business Account ID, Recipients—Meta app, WhatsApp product
ThreadsThreads App ID, Threads App SecretSign in with Threadsdevelopers.facebook.com (Threads use case)
BlueskyHandle, App password—Bluesky settings, App passwords
DiscordWebhook URL—Channel settings, Integrations
VimeoClient identifier, Client secretSign in with Vimeodeveloper.vimeo.com

A channel whose token expires or is revoked shows Sign in again on its card and on the dashboard, and the scheduler stops sending to it until you do. The hourly channel check keeps this current.

AI providers

Keys are optional per provider. A feature runs on the provider chosen for it under AI features; if that provider has no key, the feature explains what is missing when you use it. Image editing needs a provider that supports it (OpenAI, Gemini or OpenRouter with a capable model); video generation needs Gemini (Veo) or OpenAI (Sora).

SMTP

Password reset emails need a mailbox. Enter the SMTP details under Settings, SMTP, and send a test email. Without SMTP, set a new password from the server with php artisan account:password.

Firebase push notifications

With Firebase Cloud Messaging set up under Settings, Firebase, every notification (a campaign published or failed, a channel that needs a sign-in, AI media that is ready) also reaches the browser the moment it happens: the bell updates at once, a system notification shows even when the panel is in another tab or closed, and a page that is following a publish run refreshes right away. The application must run over https for the browser to allow this.

  1. At console.firebase.google.com create a project (or open one) and add a Web app to it (the </> icon on the project overview).
  2. The console shows the app's firebaseConfig: Project settings, General, Your apps. Copy each value into the Firebase Cloud Messaging fields, or press Upload config file with a JSON file of them.
  3. Under Project settings, Cloud Messaging, Web configuration press Generate key pair and copy the key into Web Push certificate (VAPID key).
  4. Under Project settings, Service accounts press Generate new private key. Press Upload config file under Firebase Admin SDK and pick the downloaded file; the fields fill in.
  5. Save. The browser asks to allow notifications on the next page; allow them, then press Send a test to see one arrive. Each account allows notifications once per browser; the toggle under Notifications does the same.

Both files are kept in storage/app/firebase; the web app config is served to the page and to firebase-messaging-sw.js, the service account never leaves the server. Every push and every failure is written to notification_logs.

Google Drive

Each company has its own Drive, signed in on the company's page under Brand voice. With a Drive signed in, the Media library and every media strip in the composer carry a Google Drive tile: pick pictures and videos from the Drive and they come into the library, ready to post. A library file's Save to Google Drive puts it in a Quick Social folder in that Drive. One Drive serves the whole panel.

  1. In the Google Cloud project used for YouTube (or a new one) enable the Google Drive API under APIs & Services, Library.
  2. Create an OAuth client of type Web application (or reuse the YouTube one) and add the redirect URI shown on the company's page.
  3. Paste the Client ID and Client secret and press Sign in with Google. The application asks for drive.readonly (to list and import) and drive.file (to save).

Demo mode

Set DEMO_MODE=true in .env (APP_ENV=demo does the same) to show the site without letting visitors change anything: every save, publish, delete and checkout answers with a demo notice, while signing in still works. Nothing reaches the channels either: no post is sent, no feed or number is read, no sign-in is checked or renewed, and scheduled campaigns are not sent; the pages show what is stored. No payment is taken and no order is created.

DEMO_LOGIN=true adds the Explore the demo panel to both sign-in pages: one click signs a visitor in as the demo client, the team member or the admin. php artisan qs:demo-accounts creates those three accounts (password demo1234) and refreshes them. Keep both off on a site that sells.

Updating

  1. Back up the database and the storage/ folder.
  2. Upload the new files over the old ones, keeping your .env, storage/ and public/storage.
  3. Run php artisan migrate --force and php artisan optimize:clear.
  4. Never run php artisan key:generate on an installed site: the gateway keys, the channel tokens and the AI keys in the database are encrypted with the key in .env.

Troubleshooting

SymptomCause and fix
The installer says a folder is not writableSet permissions: chmod -R 775 storage bootstrap/cache, and make sure the web server user owns the application folder.
Uploaded images do not showThe storage link is missing. Run php artisan storage:link, or create public/storage as a link to storage/app/public.
Scheduled campaigns stay scheduledThe cron entry is missing or not running. Add schedule:run every minute; the dashboard shows when it last ran.
A channel says "Sign in again"Its token expired or was revoked. Open Settings and sign in to the channel again.
OAuth sign-in fails with a redirect errorThe redirect URI in the platform's app does not match the one shown under Keys. Copy it exactly, including https.
Instagram or TikTok refuse a local fileThose platforms download media from a public URL. The application must be reachable online with a public APP_URL.
Video studio cannot renderFFmpeg is not installed or not found. Install it on the server or set FFMPEG_PATH.
500 error after moving serversRun php artisan optimize:clear and check that .env has the right database details.
A payment succeeded but the plan did not changeThe gateway's webhook did not arrive. Console, Webhooks: open the event and press Retry. Check the webhook address registered with the gateway, and that the site is reachable over https. The nightly qs:subscriptions-reconcile catches it either way.
Checkout says no payment method is availableNo gateway is switched on and ready. Console, Settings, Payment gateways: switch one on and save its keys.
A customer says a workspace is read-onlyTheir plan allows fewer workspaces than they own, so the newest are locked. They upgrade or remove one; nothing was deleted.
Trials never endThe cron entry is missing: qs:trials --enforce runs from the scheduler.
Sign-ups are not asked to confirm their emailSMTP is not set up, so the site trusts the address instead of locking people out. Console, Settings, SMTP.

Changelog

1.0.0: first release.