Soltis is a visual builder for websites and web apps. You design on a canvas, connect your data, add logic, and publish. Amanda, the built-in AI, can build and edit all of it with you.
What you can build
Anything from a landing page to a full app with accounts and live data: a portfolio, a SaaS site, a catalogue backed by your API, a dashboard behind a login, a booking flow. Every project can have as many pages as it needs, dynamic pages such as /book/my-first-book, and its own domain.
How it works
One engine everywhere. The canvas, the preview and your published site are rendered by the same engine. What you see while designing is what your visitors get.
Data and logic are built in. Variables, collections (your API or Supabase), events with steps, formulas, authentication. No plugins to install.
Amanda edits the real project. She works on your pages, styles and logic directly, and you can undo any of her recent answers in one click.
You own the result. Publish on Soltis in seconds, or export the site and host it wherever you want.
Create a projectFrom your dashboard, click New project, or describe your site in the box at the top and click Generate: Amanda builds a first version for you. The project saves itself as you work.
Add elementsOpen Add in the left rail. Drop a Section on the canvas, then a heading and a paragraph inside it. A section is a full-width band of the page: start every page with sections.
Style themSelect an element on the canvas. The Style tab on the right holds its typography, layout, size, spacing and effects. Change a value and the canvas updates at once.
Check every screen sizeSwitch the canvas to Tablet or Mobile at the top. A style you change there only applies to that size.
Try itClick Preview to use your page like a visitor: links, buttons, forms and events all run.
PublishClick Publish. Your site is online at its Soltis address in a few seconds. Add your own domain whenever you are ready (Custom domain).
Prefer to describe it? Open Amanda and write what you need in plain words, like a pricing section with three plans, the middle one highlighted. See Working with Amanda.
The editor
A tour of the zones you will spend your time in.
The zones
The top bar: the project name, the modes (Design, Preview, Settings, Database, Amanda), undo and redo, the canvas size, and Publish.
The left rail opens a panel: Add, Layers, Pages, Events, Data, Components, Auth, Colors, Settings.
The canvas in the middle: your page, edited directly.
The inspector on the right, for the selected element: Style, Settings and Events.
The modes
Mode
What it is for
Design
Build and style the page on the canvas.
Preview
Use the page like a visitor: events, forms, navigation and data all run. It takes the width of your window, shown at its top right; Show at 1440 px gives the width of the Desktop canvas.
Settings
The project and its pages: SEO, hosting, domain, fonts, versions.
Database
The tables of your Supabase project: their rows, their structure and who can access them. See Database.
The size switcher at the top of the canvas shows the page at Desktop (a 1440 px wide page, scaled to fit your window), Tablet (768 px, styles for screens up to 900 px) and Mobile (390 px, styles up to 600 px). A style you change on Tablet or Mobile is stored for that size only: the desktop design does not move.
The sizes add up: a Tablet style also applies to phones, unless Mobile changes it. To hide an element on one size only, use Shown on; to reuse the tablet and mobile work of one element on another, Copy and Paste responsive.
A phone or a tablet turned sideways
The button next to Mobile turns the Tablet or the Mobile sideways, on the canvas and in the preview: 1024 × 768 and 844 × 390. The frame says which styles the page shows at that width (a phone sideways shows the Tablet styles). What you change still goes to the size you picked: on Mobile sideways, the Style panel reminds you that the change is for Mobile, which you see once the phone is upright again.
The sizes go by the width of the screen, so a turned screen changes size. A phone turned sideways is 670 to 930 px wide: it gets the Tablet styles, and the largest phones (wider than 900 px) the Desktop ones. A tablet turned sideways (1024 px and more) gets the Desktop styles, like a 12.9 inch iPad held upright. The page follows the turn at once, with no reload. Sideways, a phone is only 360 to 430 px high: keep heights flexible, and avoid a section at the full height of the screen with a lot inside.
The Desktop canvas is a 1440 px screen, and the preview is your window. On a wider window, a row can fit side by side in the preview and wrap on the canvas: the canvas shows what a 1440 px laptop gets. To keep two columns on every desktop, give the side that keeps its size a width and flex: none, and the other flex: 1 with a minimum width of 0.
The page state
When you use the preview, variables change (a menu opens, a counter goes up). The canvas then shows that state. The ↻ button next to undo and redo brings the page back to its starting state.
Keyboard shortcuts
Keys
Action
Ctrl Z
Undo (Cmd Z on a Mac)
Ctrl Y or Ctrl Shift Z
Redo
Ctrl C · Ctrl X · Ctrl V
Copy, cut and paste the selected elements
Delete
Delete the selected elements
Ctrl + click in Layers
Select several elements
A · L · P · F · D
Open Add, Layers, Pages, Events, Data
Esc
Close the open window or editor
In Database, these shortcuts are off: they would act on the page you cannot see.
Canvas and layers
Direct manipulation on the canvas, with the structure of the page in Layers.
Selecting
Click an element on the canvas to select it: it is outlined, and highlighted in Layers. Clicking a layer selects the element on the canvas. The path above the inspector (page, section, box, image) shows where you are.
Layers
Reorder: drag a layer above, below or inside another one.
Rename: double-click a layer. A clear name makes the page easier to edit, and the export easier to read.
Several at once: Ctrl + click (Cmd on a Mac) to add layers to the selection, then copy or delete them together.
The eye: hides an element while you design. An element with a condition (Visible if) shows it in the eye too.
Adding and moving
Drop a block from Add onto the canvas or into a layer. Each family of Add folds, and your components have their own group, sorted in their folders. Copy and paste works across pages, and the copy keeps its styles, bindings and events.
An empty box has no height. The canvas draws a hatched area in it so that you can still select it and drop things inside.
Form, Input, Password input, Text area, Dropdown, Checkbox, Toggle, Date picker, Color picker, Range slider, OTP input, File upload, Rich text editor
Data
Repeat, Item text, Table, Chart
Navigation
Side menu, Bottom nav, Dropdown menu
Feedback
Popup, Accordion, Skeleton, Skeleton line
Sections, rows, columns and grids
A Section is a full-width band of the page; put sections at the top level and everything else inside them. A section and a div stack their children vertically. A row puts them side by side, a column stacks them, and a grid lays them out in columns (3 on desktop, 2 on tablet, 1 on mobile by default). A container centers its content with a maximum width.
Repeat
A Repeat shows its children once per item of a list: a variable, a collection, or a list inside a collection response (landing_page.hidden_gems). Inside, Item text shows one field of the current item, and any binding or formula can read $item and $index. See loading, empty and error states.
Dropdown from a list
A Dropdown shows the options you type (Small|Medium|Large), or the items of a list. Select it, then in Options from a list pick a variable, a collection or a list inside a response (author_books.data.books), and choose the Shown text (book.title). Give the dropdown a variable: it receives the chosen item, whole, so a text bound to $selected_book.metrics.total_views shows the views of the book picked. The variable receives can keep a single field instead (book.id). First line adds an empty choice (Choose a book…); Pick the first item chooses the first one while the variable is empty. When the list reloads, the dropdown stays on the same item.
Ready-made blocks
Under Add, Ready-made blocks drop a finished group of elements: hero, navbar, card, features, contact, the login, signup and password forms, social sign-in, sign in with a code, footer, and four layouts that come up all the time. Split screen: a visual side that gives way and a form side of 440 px; the visual side disappears on a phone. Sidebar and page: a 240 px sidebar and the page next to it; on a phone the sidebar becomes a bar of links. Card grid: as many columns of cards as fit. Tabs: a tab bar and one panel per tab, switched by the $active_tab variable it creates. A ready-made block takes the look of your app when you drop it: its buttons, cards, titles and texts.
Popup and side menu
A popup has no open button of its own: give it a Visible if condition on a boolean variable ($menu_open), then set that variable from a click event to open and close it.
Embeds and payments
iframe embed shows another page (a video, a map, a form) inside yours.
HTML embed is rendered as it is on your published site.
payment button opens a Stripe Payment Link: create the link in Stripe, paste it in the button settings.
The Style panel
Typography, layout, size, spacing and effects, for each state and each screen size.
States
The selector at the top of the panel chooses what you are styling:
Default: the element as it is.
Hover: when the pointer is over the element.
Hover (parent): when the pointer is over its parent. Use it to zoom an image or reveal a button when its card is hovered, without moving anything else.
To animate a hover, change Transform (scale, move) rather than the width or the height: a real size change moves the whole page around the element.
Screen sizes
With the canvas on Tablet or Mobile, every value you set is stored for that size only and overrides the desktop one. A value overridden on a smaller screen is marked in the panel: click the mark to go back to the desktop value.
Units and inherited values
Numbers are in pixels unless you add a unit: %, rem, em, vw, vh. An empty field is inherited: the grey value shown in it is the one that applies (from the parent, or the default of the element). Clearing a field removes your value; it is not the same as typing 0.
The sections of the panel
Section
What it holds
Typography
font, size, weight, color, line height, letter spacing, alignment, case, decoration
Layout
direction, alignment of the children, gap, wrapping, grid columns
Sizing
width, height, minimum and maximum, aspect ratio, image fit (Sizing and spacing)
static, relative, absolute, fixed or sticky, with offsets and layer order
Animation
an entrance animation when the element appears, with a delay to stagger cards
Loop
an endless motion: a row that scrolls forever (a wall of covers, logos), a rotation, a float; fade the edges (how)
Transitions
how a change (a hover) animates: duration and curve
Loop: endless motion
Select a row, a column or a box and choose a loop: defile_gauche or defile_droite scroll its children sideways forever, defile_haut and defile_bas vertically. The loop never jumps: the published page writes the children twice and slides by half. Loop duration sets one full turn (40s by default; slower is calmer), pause loop stops it under the mouse, and fade edges makes both ends disappear softly. A scrolling row cuts what overflows: it never pushes the elements next to it. rotation, flotte and pulse animate the element itself.
The canvas shows a loop still, so you can select and edit what is inside; Preview and your published site move. Visitors who ask their system to reduce motion see it still.
Position
The offsets (top, bottom, left, right) and the layer order only apply once the position is relative, absolute, fixed or sticky. Sticky with a top of 0 keeps a bar on screen while the page scrolls: the classic sticky navigation.
Values from a formula
Next to a style field, fx binds it to a formula instead of a fixed value: the style then follows your data. For example, a color that depends on a variable:
if($active, "#22c55e", "#71717a")
Bind the same variable in several places and you change all of them from one place. See Formulas.
Sizing and spacing
Width, height, padding and margin, and the rules that surprise people.
Units
px for fixed sizes, % relative to the parent, rem relative to the base font size (a good default for text and spacing), vw and vh relative to the window.
A maximum width always wins over the width: width 100% with max width 1100px fills small screens and stops at 1100 px.
A fixed width is the size of the element. When you set one, the builder turns off what would make the element bigger or smaller than that: a grow setting inside a row, a smaller maximum width, a larger minimum width. To let an element grow, set a maximum width instead (with width 100% to fill).
Two sides in a row (a split screen, a sidebar and its page): a child of a row never shrinks below its content, so a wide side (a row that does not wrap, a scrolling wall, a big image) pushes the other one. Give the side that keeps its size a fixed width, and the side that gives way a min width of 0 with overflow hidden.
A percentage padding or margin is relative to the parent's width, even vertically. That is how the web works, not a bug.
Padding, margin and gap
Padding is the space inside the element, margin the space outside it. The lock keeps the four sides linked; open it to set each side. To space the children of a box evenly, set a gap on the box rather than a margin on every child: it is simpler and it never collapses.
Images
For Cover and Contain to work, an image needs a height (or an aspect ratio). Cover fills the box and crops; Contain shows the whole image inside the box.
The panel shows the real size of the element as rendered (for example 1419 × 905). If a width still does not apply, compare with that size: the parent (a grid cell, a narrow column) is usually the reason.
Element settings
The Settings tab of the inspector: what the selected element shows and when.
This tab changes with the element: an image has its URL and alternative text, a link its address, a repeat its list, a chart its data and fields. Every field has an fx button to compute the value from a formula, for example an image whose address comes from the current item of a repeat: $item.cover.
Shown on
Three switches, Desktop, Tablet and Mobile, show or hide the element on each size, and only on that one: hidden on Mobile, it is still there on Desktop and Tablet. The dot marks the size shown on the canvas. On Tablet or Mobile, the eye in Layers does the same for that size. An element hidden on the size of the canvas is not on the canvas: select it in Layers.
The switch follows the screen as the visitor resizes or turns it. The element stays in the page, so its events and its data keep working; it is only not shown.
Visible if
A formula that decides whether the element is shown, the same on every device. Empty: always visible. Otherwise the element appears and disappears as soon as the formula changes. For what depends on the screen (a menu button only on phones), use Shown on: writing false here on Tablet or Mobile hides the element everywhere, and the builder offers to hide it on that size only.
$menu_open
auth.uid != ""
length($cart) > 0
A hidden element is not on the page at all, so its own events do not run.
Responsive: copy, paste, mobile to tablet
The Responsive section of Settings, and the right click in Layers and on the canvas:
Copy takes the tablet and mobile styles of the element and of everything inside it.
Paste puts them on another element, or on several (select them with Ctrl in Layers). Each element inside is matched with the one of the same type at the same place; an element more or less on one side does not shift the others. What has no match is skipped, and the window says which before anything changes. You choose Tablet and mobile, Tablet only, or Mobile only. The pasted styles replace those of the elements that match; the others keep theirs.
Mobile styles to tablet copies the mobile styles of the element and of what it contains to tablet, so the tablet looks like the mobile. Desktop and mobile do not change.
Ctrl Z takes a paste back in one step. The copy stays available when you change page or project.
Lists: loading, empty, error
A repeat that shows a collection has a States block: a skeleton while the first load runs, a message when the list is empty, and a message with a retry button when the request fails. Write the messages in the language of your site. See Collections.
Names
Give important elements a name (the hero, the main button, the navigation). Names make Layers readable, help Amanda find what you mean, and keep the exported code clear.
A name has 60 characters at most, whatever it names: an element, a page, a variable, a collection, a key, a component, an event, a folder. A page address has 80.
Pages
Add pages, give them their address, their SEO, and turn one of them into a page per item.
Adding a page
In the Pages panel, add a page and give it a route: pricing becomes /pricing on your site. The home page is index, served at /. A new page takes the background, text color and fonts of your site.
Title, description and image
Each page has its own title (under 60 characters, the main words first), its description (120 to 155 characters, what makes people click) and a social image. Mark a page Hide this page from search engines for a login screen or a private dashboard: it leaves the sitemap and search engines will not list it. See SEO.
Dynamic pages
One page can show any item of your data: a book, a product, a profile. In the page settings, Dynamic route & page parameters:
Declare a parameterFor example slug, with a sample value used while you design (my-first-book).
Set the pretty URLThe pattern /book/:slug gives addresses like /book/my-first-book on your published site.
Read itAnywhere in the page, ctx.params.slug is the value from the address: in a collection query, a page load event, a binding.
Link to itFrom a card in a list, a click event with the step navigate to "book" and the parameter slug = $item.slug. See Navigation.
SEO from data
So that search engines see a real title, description and image for every item, give the page an SEO source: the URL of your API for one item (with {slug} where the value goes) and the paths of the title, description and image in its answer. Item pages are refreshed at least once a day and at every publish; an item your API does not know is a 404. A Sitemap source lists every item address in your sitemap.
The paths are read in the answer of your API, not in your page: the edge calls the API before the page exists, so a page variable (book_data.title) is empty there. If the API answers {"data": {"title": "Dune"}}, the title path is data.title. Test with the sample value calls the API the way the edge will, with the sample value of the parameter, and shows what each path finds; when one finds nothing, it lists the paths of the answer and a click puts one in place.
Background, text color and fonts
A dark site starts with its page background and text color, otherwise the body stays white and light text disappears. Fonts are chosen for the whole project: one for headings, one for the text.
Instant navigation
Links between your pages open without a full reload: no blank flash, the session stays, back and forward return where you were. It is on by default, and off for a site that has code of its own; you will find it in the site settings as Instant page changes.
Languages
In the Pages panel, set the base language and add others (fr, de). Click a language to translate every text of the current page; a text left empty shows the base text. Preview language shows a language on the canvas. Publishing generates one version of each page per language (pricing.fr.html).
Components
Build a block once, use it everywhere, change it in one place.
Creating a component
Select an element and choose Save as component. It appears in the Components panel, and you insert it from the Add panel, on any page. Each copy is an instance.
Editing the master
Double-click an instance on the canvas, or click the component in the Components panel, to edit the master. Every change to its structure, styles or events applies to all its instances, on every page, at once. Back to page returns to the page.
Parameters
Parameters are what changes from one instance to another: a title, an image, a link, even a color. Declare them on the master and each instance fills them in from its Settings tab. An instance only stores the values that differ; the others come from the master.
A parameter that fills a text, an image or a link can also be a live value: give the instance $visitors (or $user.name, or $item.name inside a repeat) and it shows the current value of that variable, and follows it. A color or a size always takes a fixed value.
Typed parameters
In the Components panel, open the Settings panel of a component (the sliders button of its line), and give each parameter a label, a group and a type (text, long text, number, yes or no, color, choice, link, icon, image, formula): the instance shows the right control: a color picker with your project colors, a list of allowed choices, an icon picker, a switch. That is what makes a component safe to hand to a teammate.
Finding and sorting components
The Components panel shows, for each component, its parameters and how many times it is used. Search it from the top, and sort your components in folders: the folder button of the section creates one, then drag a component onto it or use Move to folder. The same folders group your components in the Add panel, where each folder folds.
A component cannot be inserted into itself, nor into a component that it contains.
My components
Your dashboard has a My components library shared by all your projects: pull components from a project, sort them in folders, and push them into any other project.
An instance is a single block on the canvas: to change what is inside, edit the master; to change what varies, use its parameters.
Your files
Your logo, your photos, a background video, your fonts: upload them once, use them in every project.
Uploading
Open Files on the left rail and click Upload files, or drop files on the panel. Images (PNG, JPEG, GIF, WebP, AVIF, SVG, ICO), videos (MP4, WebM, MOV), sounds (MP3, WAV, OGG, M4A), PDF, fonts (WOFF2, WOFF, TTF, OTF), JSON, CSV and text, up to 50 MB each. Soltis reads what a file really is, whatever its name says, and refuses an SVG that contains scripts. An iPhone photo in HEIC does not show in most browsers: export it as JPEG first (Settings, Camera, Formats, Most Compatible).
Using a file
From a field: under the address of an image, a video and its poster, a background image or video (Style, Live background), the favicons, the sharing images and the app icon, Choose from Files opens your library. Upload from there and the new file is chosen at once.
From the panel: click a file to use it for the selected image or video, insert it on the page, copy its link or open it.
In a formula: the Files tab of fx inserts the address of a file, in quotes, for a value that changes: if($dark, "…/logo-white.png", "…/logo.png").
With Amanda: she sees your library and uses your files by their name: “put my logo in the header”, “use the video Intro as the hero background”.
A file keeps its address for good: renaming it or moving it to a folder changes nothing on your pages. Deleting it does: every page and project that uses it shows nothing in its place, and the file panel warns you when the current project uses it (“In use”).
Video files
A video element plays a video file (.mp4, .webm, .mov) with the browser's own player, and a YouTube or Vimeo page with theirs. For a file, its settings add Autoplay (always muted: browsers refuse autoplay with sound), Loop, Muted, Controls, a Poster image shown before it starts, and Fit: Cover fills the box and crops the edges, for a background video. On a phone, a video plays in the page, not in full screen.
Space and plans
Free
Starter
Pro
File storage
50 MB
1 GB
5 GB
The panel shows the space used. On any paid plan, Account & billing, Add-ons, adds 10 GB for €5 per month. If your plan or the add-on ends, your files stay online and keep working; only new uploads above your limit are refused.
Your files are the files of your site. The files your visitors send (an avatar, a document) go to the Storage of your Supabase project, with the Files steps.
Colors and fonts
Name your colors once, choose your two fonts, and keep the whole site consistent.
Project colors
The Colors panel keeps the named colors of the project: your accent, your neutrals, your backgrounds. They appear in the color menu of every color field, so you never retype a hex code. Create them in the panel, or save any color from a color picker with its bookmark icon.
Fonts
In Settings, choose a font for the headings and one for the text, from Google Fonts. They apply to every page and load by themselves on your published site. Two families are enough for a site that looks designed.
Keeping it consistent
One background, one or two neutral tones, and one accent color used sparingly: the main button, a number, a link.
On a dark site, give every field and every button its colors: the default field is white and the default button is near-black.
To change a color everywhere at once, bind the style to a variable with fx (how).
Variables
The values your site keeps while a visitor uses it.
Create variables in the Data panel. Five types: text, number, boolean, list and object, each with an initial value. Read a variable anywhere with $name. When its value changes, everything bound to it updates by itself.
Sort your variables in folders: the folder button of the Variables section creates one, then drag a variable onto it. The fx menu shows them in the same folders.
Showing and editing a variable
Bound text shows a variable as text.
Any property or style can be computed from it with fx.
A field (input, checkbox, toggle, dropdown, slider) bound to a variable is two-way: typing in it changes the variable.
A step Set variable or Toggle variable changes it from an event.
Keeping a value
By default a variable starts again from its initial value when the page reloads. In the variable settings, turn on Preserve on navigation to keep its value while the visitor moves between pages, until the tab closes (a multi-step form), or Save in local storage to keep it in the browser (a cart, a theme choice, a draft). The value is saved and restored for you.
Derived lists
A derived list is computed from another list: a source, then any of keep (a filter), map (a transformation) and sort by. It recomputes whenever its source, or any variable it reads, changes. Ask Amanda for one (filter the products with the search field): she creates it for you.
That is live search without a single event: a field bound to $query, a repeat on the derived list, and the list filters as you type. Do not write to a derived list with a step: it is recomputed and the value would be lost.
In Data › Variables, open the list: Computed from a list shows its source, keep, turn and sort by, and says under each one what does not compile. The source is a formula, so a variable or a collection takes a $ ($books.data.items); keep, turn and sort read each item as item. A list whose formula does not compile is left out by your pages, which keep working; the builder says so, and Publish waits until it is fixed. Compute it from another list turns a list variable into a derived one.
Unused variables
A variable that nothing reads or writes (no page, component, collection, event or other variable) is marked not used. The bin at the top of Variables removes them all at once; Ctrl Z brings them back. Amanda sees the same marks and deletes the variables she created and no longer uses.
Collections
Data from outside: your API, or a Supabase table.
Create a collection in the Data panel, then show it with a repeat, a table or a chart, or read any value with $name. A collection keeps the whole answer of the API, whatever its shape.
An API (REST)
Method and URL: GET https://api.example.com/books. The URL can contain $variable and $env.KEY.
Query string: name and value pairs added to the URL (?language=en) and encoded for you. An empty value is left out.
Headers: for example authorization = Bearer $env.API_KEY. Content-Type: application/json is added for you when a POST, PUT or PATCH sends a JSON body: add headers only for authentication or special needs. A bearer token field is there for the common case.
Fields: the JSON body of a POST, PUT or PATCH, one key per row. Plain text stays text; a value that is one formula ($variable, ctx.params.slug, auth.token, 12, true) is computed with its type. A GET has no body: use the query string. For a body of another shape, write it whole in body (JSON).
Response path: when the list sits inside the answer ({"data":{"items":[…]}}), set data.items so the collection is that list.
Click Test request to see the real answer: its fields then appear in the fx menu everywhere.
The same rules apply to the API request (HTTPS) step of an event: query string, headers and fields work the same way. Addresses start with https://: a published site refuses http:// (except localhost while you test), and a browser blocks it on a secure page.
One answer, several lists
If an API returns several lists in one answer ({"most_popular":[…],"hidden_gems":[…]}), create one collection and point each repeat at a path: landing_page.most_popular, landing_page.hidden_gems. Never create two collections on the same URL.
Supabase
Give the project URL, the publishable (anon) key and the table, then optionally a filter (price=gte.10), an order (price.desc) and a limit. The publishable key is visible to visitors, which is expected: your protection is Row Level Security in Supabase, never the key.
When the source becomes Supabase, the collection takes the project of your site (the one of Auth) and its public key, and you pick the table in a list, with its columns below. Read as chooses who reads: the signed-in visitor (the default for a new collection) sends their session with the request, so the rules of Row Level Security for signed-in users apply and a visitor sees their own rows; anyone sends the public key only. The session only goes to the project used by Auth: a collection of another project always reads with its public key.
Loading, empty and error
Every collection gives you $name_chargement (true while it loads), $name_erreur (empty, or the error) and $name_statut (the HTTP status). For a list, use the States block of the repeat: a skeleton during the first load, a message when it is empty, a message with a retry button when it fails.
When data loads
A collection loads on the pages that read it: a page where nothing shows it, and whose events never use it, does not request it. A collection that sends something (POST, PUT, PATCH or DELETE) and that no page reads still loads on every page, in case its call does something on its own (a ping, a sync). In the builder, every collection loads.
During a visit, a page shows the last answer at once and refreshes it in the background, and the same request is sent only once even if several lists use it. After a successful POST, PUT, PATCH or DELETE to a server, the next load from that server fetches fresh data. There is no need for a page load event that reloads a collection.
The load setting of a collection chooses when it starts: on page load (the default), when the first element that uses it becomes visible, or manually with the step reload collection. cache (s) keeps an answer for that many seconds.
Reload, load more, pages
The step reload collection fetches again, for example after a save.
The step load more appends the next page. Set up the Pagination of the collection first: the query parameter that carries the page (page, offset, skip), the number per page, optionally the parameter for the page size, the first page, and whether it counts in pages (1, 2, 3) or items skipped (0, 20, 40). The list stops by itself when a page comes back shorter than the page size ($name_fin becomes true). A repeat can load more by itself when the visitor reaches its end.
The trigger collection loaded runs an event once the data has arrived.
The step collection: insert row writes a row to a Supabase table.
Stay up to date: a Supabase collection reloads by itself when its table changes (live updates), and any collection can reload every N seconds while the page is on screen.
Secret keys
Keys live in Data › Environment and are read as $env.NAME. A name keeps its capitals (API_KEY); a space, a dash or an accent becomes _. A key marked secret (🔒) never reaches the published page. A secret key works in a collection (its URL, headers, query or fields) and in the http step of an event, through the Soltis proxy: once published, the step calls our proxy, which adds the key and calls your API. The proxy does not see the page, so the step's address, headers, bearer and body must not depend on it (text, quoted strings and $env keys); put the values of the page ($search, auth.uid, a page parameter) in its fields or its query string. In a formula or a text, a secret key is empty on the published site. When a step cannot go through the proxy, publishing tells you which part depends on the page.
POST "https://hooks.example.com/sync"
header authorization: "Bearer " + $env.SYNC_TOKEN
field user_token: auth.uid
Keys anyone can read
A key is readable in your published site when its value is written in a collection or an API request (a key pasted in a header, a Bearer eyJ… typed by hand, a webhook address), or when a key that looks like a token has no lock: keys without a lock are written into every page. The publish window lists them, and Data › Secrets protection › Find keys anyone can read shows where each value is written.
Protect does the work: the key gets the lock (or is created, named after the address), every place where its value was written now reads $env.NAME, and the Soltis proxy is turned on. A key is protected only when every request that reads it can go through the proxy; otherwise the list says what to change first, for example a request that sends a form (the proxy sends JSON: set its body to fields) or a body written as one text built from page values (write it as fields). Ctrl Z undoes it.
Secrets protection, in the Data panel, chooses how the collections and http steps that use a secret key are published:
No proxy: a collection that uses a secret key blocks publishing, and an http step gets the key empty (unlock it to publish it readable).
Soltis proxy (Starter and Pro): nothing to set up. Your site calls our proxy, which adds the key on our side and calls your API. Starter includes 50,000 calls per month and Pro 1 million, then €10 per extra million (details).
My own proxy: export the proxy as a standalone Cloudflare Worker, deploy it on your account with your secrets, and paste its address. You can switch to it at any time: no lock-in.
Soltis stores the value of a locked key encrypted. It goes back in clear only to the builder of your account and to the proxy, never into a published page.
A published site is made of public files: never paste a secret directly in a URL, a header or a formula. Put it in $env, marked secret.
Formulas
The small expressions behind fx, Visible if, and every step.
What a formula can read
Write
To read
$name
a variable or a collection; $user.name walks into an object, $list[0] takes an item
$item, $index
the current item and its position, inside a repeat
ctx.params.slug
a parameter of a dynamic page
ctx.query.q
a value of the address (?q=…)
ctx.page.route
the current page (also ctx.page.titre, ctx.page.nom)
ctx.nav.url, ctx.nav.segment
the full address, and its last part
ctx.ecran.largeur
the window width (and ctx.ecran.hauteur)
ctx.scroll.y
how far the page is scrolled (and ctx.scroll.pourcent)
auth.uid, auth.email, auth.token
the signed-in visitor (empty when signed out)
$env.NAME
an environment key
result
the answer of the previous http step, in the same event
events.load_book.result
the last result of another event
Reading is safe: $book.author.name is simply empty while $book is not loaded. Text goes between double quotes: "Hello".
Operators
== != < <= > >=
and or not
+ - * / mod
Values: numbers, "text", true, false, null. A boolean is tested as it is: $open, not $open == "true".
When this happens, do that: a trigger, then steps.
An event belongs to an element (a click on a button), to a page (when it loads), or to the whole app. Open the Events tab of the inspector, or the Events panel, add an event, choose its trigger, then add its steps.
Triggers
Family
Triggers
Basics
on click, on hover, becomes visible
Forms
on change (the new value is $valeur), on submit, focus gained, focus lost
long press, swipe left, swipe right, pull to refresh, back button, pinch, orientation, online, offline
Page
page load, URL change (back, forward, a new parameter)
An event can be limited to desktop, tablet or mobile with its device selector.
Steps
Family
Steps
Basics
Set variable, Toggle variable, Condition (if), Alert, Navigate, Set URL parameter
Flow
For each (loop), Try / catch, Wait, Call event, Return value
Lists
Add, Remove, Replace, Clear, Sort, Filter, Map
Network
API request (HTTPS), Reload collection, Load more, Collection: insert row
Database
Get rows, Add row, Update rows, Delete rows, Add or update row, Call function: the tables of your Supabase project, see Database
Auth
Log in, Sign up, Log out, Reset password, Set new password
Elements
Measure element, Focus element, Blur
Steps run in order, and each one waits for the previous one: an http step holds the next steps until its answer arrives. Its answer is result for the following steps, and events.<event_name>.result (or .error) for any later event.
Page and app events
A page event runs on page load, or on page load and when the URL changes: once at load, then again on every back, forward or parameter change, so that the page can read the new values of ctx.params and ctx.query.
A page or app event can also run when a row of a table changes or when your server sends an event, as it happens: see Live updates.
App events run on every page load, before the page events.
Global events are reusable: give them parameters, then run them from any event with the step Call event. Inside, a parameter is read as $name. Their names use lowercase letters, digits and _.
Finding and organizing events
The Events panel lists every event of the project in one place:
App events and Global events, which you can sort in folders: the folder button of the section creates one, then drag an event onto it, or use Move to folder in its menu. Drag it onto the title of the section to take it out of its folder.
Pages and elements: page by page, the events of the page, then those of its elements, in the order of the page. Each line says which element and what triggers it: Button “Buy now” · On click.
In components: the events of the elements inside your components.
Click an event to open it: the builder goes to its page (or opens its component), selects its element and opens the event. Show on the canvas, in the menu of the line, selects the element without opening the event. The search at the top looks through the names, the elements, the pages and the triggers.
Testing
The event editor runs an event for you with Test, and Trace shows each step with its result. The last results of each event are shown in the fx menu, so you can bind their fields directly.
Handling errors
Wrap risky steps in try / catch. After a login or a sign-up, always check the result and show a message when it failed.
Navigation and URLs
Moving between pages, passing values, and keeping state in the address.
The navigate step
The step navigate has three choices. Page: a page of your site, chosen in the list. Web address: another site, typed as it is (https://example.com; mailto: and tel: links work too); it opens in the same tab. Formula: an address computed from your data ($next_page), with the formula panel.
Passing parameters
When the target page has parameters (a dynamic page), the step offers them: for a card in a list, slug = $item.slug. On your published site, a page with a pretty URL opens at /book/my-first-book; any other parameter is added as ?name=value.
State in the address
The step set URL parameter writes ?page=2 or ?q=shoes without reloading: ctx.params and ctx.query follow, and the address can be shared or bookmarked. Use it for pagination, filters and search. An empty value removes the parameter.
Instant page changes
On your published site, a link to another of your pages opens without a full reload: the session, the variables you keep and the data already loaded stay. Back and forward return to the same place on the page.
Clean addresses
Published on Soltis, your pages have clean addresses: / for the home, /pricing, /book/my-first-book. A value that contains a dot, a slash or a % (an email, vol.3) goes after a question mark instead, on the same page: /book?slug=vol.3. A downloaded export keeps .html files so that it works on any host.
Protecting a page
To keep a page for signed-in visitors (a dashboard), add a page load event: conditionauth.uid == "", then navigate to your login page. Hide what must stay private with Visible ifauth.uid != "", and protect the data itself on your server (Row Level Security for Supabase). See Authentication.
Drag and drop
Let visitors move cards between lists, or reorder a list.
How it works
The element you drag gets a drag start event. Its payload (set on the event, not in a step) is what travels: for a card in a repeat, item($tasks, $source_index).
A target gets a drop event, and if you want live feedback a drag over event, which runs when the pointer enters it or the insertion point changes. A target can be a box or a whole repeat.
In those events: $charge is the payload, $source_index the position of the dragged card in its list, $cible_index where it would be inserted in the target list.
The gesture shows by itself: a copy of the element follows the pointer, and the target under the pointer is outlined. It works with a mouse and with a finger; on a touch screen, an element you can drag does not scroll the page, so leave some space around the cards to scroll from.
Moving a card to another list
On the repeat that receives the cards, a drop event with two steps:
list: add variable done value $charge
list: remove variable todo index $source_index
Give an empty list a minimum height, so that it can receive a drop.
When cards can go both ways (both lists accept drops), remember where the card comes from: in the drag start event of each card, a step that sets a variable drag_from to "todo"; in the drop event of the other list, put the two steps inside an if step with the condition $drag_from == "todo". Otherwise a card released on its own list would be added twice.
Reordering a list
On the repeat itself, a drop event:
list: remove variable tasks index $source_index
list: add variable tasks value $charge
position if($cible_index > $source_index, $cible_index - 1, $cible_index)
It works with a column, a row and a grid.
Never put an alert in drag start, drag over, pointer move, mouse wheel or size change: they run during the gesture and the dialogs would break it. Show feedback on the page instead, with a variable.
Authentication
Accounts for your visitors: sign up, log in, private content.
Connecting a provider
Open the Auth panel and choose:
Supabase: your project URL and its publishable (anon) key. The panel checks that it answers. Or connect your Supabase account and pick the project in Database: the address and the key are filled in for you.
Custom (HTTP): your own endpoints for login, sign-up, reset and new password (POST), and optionally refresh and sign in with a provider, with the headers your backend needs. Login and sign-up receive {email, password} and must return JSON with an access_token (a JWT with sub and email).
The session is renewed automatically before it expires.
Headers for your own backend
With Custom (HTTP), Headers (under the addresses) are sent with every call: login, sign-up, reset, new password and refresh. Use them for what your backend asks of each request, like an API key or an app id. A value is text, or reads a key of Data › Environment:
A key without a lock, or plain text, is written in your published pages: anyone can read it. The panel says so under the header, and Move it to a locked key puts a token in a locked key for you.
A locked key (🔒) never goes into your pages. With Soltis proxy (hosted) in Secrets protection, the sign-in of your published site goes through the proxy, which adds the header and calls your backend. The email and password travel in the body of the request, never in its address. Publishing is refused while a header reads a locked key without the Soltis proxy, since the sign-in would fail.
The header that carries the visitor's session (Authorization: Bearer, for the new password) is added by your site: do not set it here.
Sign in with Google, GitHub, Apple…
Visitors sign in with an account they already have; the first time, it creates their account, so there is no separate sign-up. Turn the provider on, then add the button.
With Supabase: in the Auth panel, Sign in with Google, GitHub… lists the providers of your project, on or off. Set up opens a window with the link to create the app at the provider, the callback address to paste there (the address of your project followed by /auth/v1/callback, ready to copy), and two fields for its client ID and secret. Turn on sends them to your Supabase project: Soltis does not keep the secret, and it never reaches your site. Allow my site's addresses adds the addresses of your published site to the ones Supabase may send visitors back to. Both need the Soltis app to have the Auth permission on your Supabase organization; without it, the panel links to the same settings in Supabase.
With your own server: in Endpoints, Sign in with a provider is the address of your server that signs visitors in, with {provider} where the name of the provider goes, for example https://api.you.com/oauth/{provider}. Your site sends the visitor there with redirect_to (an address of your site) and state. Your server signs them in with the provider, then sends them back to redirect_to#access_token=…&state=… with the same state.
Then the button: Social sign-in in Add (Continue with Google, Continue with GitHub), or the step Auth: social sign-in on any button, with the provider and, if you want, the page to come back to. The visitor comes back signed in. When they cancel or the provider refuses, auth.error says why: the Social sign-in block shows it under the buttons. In the builder the button stays on the page; try it on your published site.
Sign in without a password
With Supabase, visitors can sign in with what they receive, no password to remember; the first time, it creates their account.
A sign-in link: the step Auth: email sign-in link emails a link; the visitor clicks it and comes back to your site signed in (Come back to picks the page). Add the address of your site to Authentication, URL Configuration, Redirect URLs in Supabase.
A code: Auth: send a sign-in code sends a one-time code by email, or by text message (SMS, international format: +33…); Auth: check the code signs the visitor in with the code they typed. For a code by email, the Magic Link email template of your Supabase project must contain {{ .Token }} (Authentication, Email Templates). Text messages need a phone provider in Supabase (Authentication, Sign In / Providers, Phone), billed by that provider.
Sign in with a code, in Add, is a ready form. After each step, resultat.ok is true when it worked, and resultat.msg says why otherwise (a wrong or expired code). In the builder, the sign-in link is refused (it would bring the visitor back to the builder): try it on your published site; codes work everywhere.
The visitor's profile
Auth: update the profile saves data of the signed-in visitor (name, avatar, preferences) and, if you give them, a new email or phone number (Supabase asks for a confirmation). Profile data is an object: {"full_name": $name, "avatar": $avatar_url}. Read it everywhere with auth.meta: auth.meta.full_name. Data that other visitors must see, like a public author page, belongs in a table of your database instead.
The steps
Use the event steps Auth: log in, sign up, social sign-in, log out, reset password and set new password, with formulas like $email and $password. Or drop the ready-made blocks from Add: login form, signup form, forgot password, new password. The reset email brings the visitor back signed in: put a new password block on that page.
Who is signed in
auth.uid, auth.email and auth.phone describe the visitor, auth.meta holds their profile data; auth.token is the access token to send to your API. They are empty when nobody is signed in. auth.error holds the error a sign-in provider sent back, and is empty otherwise. hasRole("admin") tests a role.
Private content and pages
Show an element to signed-in visitors only: Visible ifauth.uid != "".
Send signed-out visitors away from a page: a page load event with a condition and a navigate step (how).
Protect the data on the server: what the browser hides, a curious visitor can still request. With Supabase, use Row Level Security.
Database
Your Supabase project, inside Soltis: its tables, their rows, their columns and who can access them. Or your own database, behind your API.
Connect your Supabase account
Open the Database mode in the top bar and click Connect Supabase. A Supabase window opens: pick the organization to share with Soltis and click Authorize. The window closes and Database goes on by itself. If your browser blocks the window, Database gives you a button to open it.
Soltis keeps the access on its server, encrypted. It never goes into your site, your project file or the builder.
Database reads your project. Nothing in it changes until you turn on Allow changes.
Disconnect any time: the menu next to the project name, Settings › Database, or the settings of your Supabase organization.
No Supabase account yet? Create one in the Supabase window. It is free to start, and billed by Supabase, separately from your Soltis plan.
The project of your site
Pick the project of this site in the list (a paused project has to be restored in Supabase first). Each site has its own project, saved with it: the next time you open Database, it opens on its tables.
Picking a project also fills the Auth connection of your site: the project address and its public key (the publishable key, or the older anon key). A secret key never leaves Supabase. If your site already signs visitors in somewhere else, Soltis asks first. To change the project: Change project in the menu, or Settings › Database.
Data
The rows of the table, 50 per page. Click a column name to sort, again to sort the other way, a third time to stop sorting. Filter adds a condition (contains, is, is not, greater than, less than, is empty, is not empty); up to five, all of them must match. Click a cell to see its whole value and copy it; a value longer than 2,000 characters shows its beginning.
Database shows every row, whatever the rules of the table: it reads with your Supabase account, not as a visitor. To see what a visitor sees, use the preview of your site.
Structure
The columns of the table: their type, their default value, whether they are required, and their keys. A link to another table opens it. Checks are the conditions every row must pass.
Access
What visitors of your site can do with the table. Your site talks to Supabase with its public key, which anyone can read in the page: what protects your data is Row Level Security (RLS) and its rules.
RLS on: visitors can only do what the rules allow. With no rule, they can do nothing.
RLS off: anyone with the public key can read, add, change and delete every row. The table is marked with an open lock. Turn RLS on in Supabase, then add rules.
Each rule is told in a sentence (for example, Signed-in visitors can read the rows whose created_by is their own user id), with who it applies to and its exact condition. A view has no rules of its own: created with security_invoker = on, it follows the rules of the tables it reads; otherwise it reads them with its owner's rights, and an open lock warns you.
Changing the database
Allow changes, at the top of the list of tables, is off for every project until you turn it on. While it is off, Soltis refuses any change to this project, even from its own server. Turn it on, and Database lets you:
New table: its name, its columns (type, default value, required), an id as primary key (an automatic number or a uuid) and created_at. Row Level Security is on from the start; pick who can use the table: only your server key for now, everyone can read, signed-in visitors can read, or each user manages their own rows. Add a user column adds user_id, filled with the id of the signed-in user.
Structure: Add column (with a link to another table or to a user), and the menu of each column to change it (name, type, default value, required, unique) or delete it. The menu at the top renames or deletes the table.
Access: turn Row Level Security on or off, Add a rule from the common cases, delete a rule.
Every change opens a window that says what it does, warns you when Supabase may refuse it or when your site needs to follow (a renamed column), and shows the exact SQL that runs. Nothing runs before Apply. The whole change runs at once: if Supabase refuses a part, nothing is applied, and the window says why. Deleting a table or a column, or turning Row Level Security off, asks you to type its name: Supabase has no undo, so export the data you want to keep first.
Amanda can propose changes too (a table for a feature you ask for, a column, a rule): she shows them as cards in her answer, and each one opens this same window when you click Review and apply. She never applies them herself (how).
Change history, in the menu of the project, lists the changes made from Soltis, applied or refused, with their SQL.
Changing the database needs the Soltis app to have the Database permission in read and write on your Supabase organization. If it only has read, the window says so: disconnect and connect Supabase again once the app has the permission.
In your events
Six steps read and change the tables of the project of your site, in the Database family of the step list: Get rows, Add row, Update rows, Delete rows, Add or update row and Call function. They use the project of Auth and run as the visitor: with their session when they are signed in, with the public key otherwise. Row Level Security decides what each visitor can do, exactly as with the Supabase JavaScript library.
Table and columns come from your project: pick them in the lists. Without a connected Supabase account, type their names.
Filters: a column, a comparison (=, ≠, >, ≥, <, ≤, contains, matches, in list, is) and a value, which is a formula: $search, auth.uid, "Dune". Every filter must match. A value is always sent as a value: whatever a visitor types, it never adds a filter of its own.
Get rows: the columns to read (empty: all of them), a sort, a limit and an offset. Result gives a list, or one row (an object, empty when nothing matches). Total receives the number of matching rows, whatever the limit, for a pagination.
Add row, Update rows, Add or update row: one line per column with a formula for its value (a number stays a number); the list proposes the columns to fill, the required ones first. Update rows and Delete rows need at least one filter: without one, every row of the table would change, so Soltis refuses it. A filter whose value is empty when the event runs (a variable not set yet, an empty search) is refused the same way, and nothing is sent. Add or update row updates the row whose key already exists: the primary key, or the columns of on conflict.
After a change, the collections of the page that read the same table reload by themselves; after writing turns it off.
Call function runs a function of the public schema with its arguments, as the visitor.
The rows go into result for the next steps, and into response if you name a variable. When Supabase refuses (a rule, a missing column), the event stops, unless you name an error variable: it receives message, statut (the HTTP status) and code, and the event goes on, so a condition can show a message.
Test, in the event editor, runs the steps for real against your project, and asks first when they change rows. Trace does not run them. A step without its table, or with nothing to write, does nothing in the builder and stops the publication until it is filled.
Your own database
Your data already lives in a database of your own, behind your API? Open Database and click Use your own database (or Settings › Database):
API address: its https address. The steps call this address followed by the table.
Tables: separated by commas. A path works too: v1/books.
Who is asking: the signed-in visitor (the token of their session goes in Authorization: Bearer, and your API decides what they may read and change), or anyone.
Headers, sent with every request. A key goes in Data › Environment as a locked key, written $env.NAME here: with the Soltis proxy, the request goes through it and the key never reaches the page.
Try reads the first table from the builder (without keys or formulas) and shows its first row. Create collections creates a collection per table, for the pages that list rows.
Four steps, in the Database family of the step list, call your API:
Your API: get rows: GET /books, with a query string; with a row id, GET /books/42. Rows at names where the list is when it sits inside the answer, for example data.items.
Your API: add row: POST /books with the fields as JSON.
Your API: update row: PATCH /books/42 with the fields to change, or PUT to replace the row.
Your API: delete row: DELETE /books/42.
Then reload reloads a collection after a change. An answer that is not 2xx is an error: its message goes into the error variable when you name one, otherwise the event stops. A row id that is empty when the event runs sends nothing. Your site calls your API from the visitor's browser, so your API must allow your site's address (CORS). When a site uses only its own API, the step list shows these four steps and hides the Supabase ones; with Supabase only, the other way round; with both, both.
Files
Photos, avatars, documents: your site keeps them in the Storage of its Supabase project, in buckets. The buckets of the project are listed under Files, below the tables. Open one to see its files (look in a folder, search a name, open a file of a public bucket) and the rules that say who can use them, each in one sentence.
Public or private: anyone with the link of a file of a public bucket opens it, without signing in (avatars, product photos). A file of a private bucket opens with a signed link, given to the visitors its rules allow, and that works for a while (documents, invoices).
New bucket, with Allow changes on: its name, the largest file it takes, the types it takes (images, PDF…), public or not, and who can add files: each user, in their own folder (a signed-in user adds, changes and deletes the files of a folder named after their user id), any signed-in visitor, or only your server key for now. As for a table, you review the SQL before it runs.
The menu of a bucket makes it public or private. Making it public asks you to type its name.
Five steps, in the Files family of the step list, use the files of your site. They run as the visitor, like the Database steps: the rules of the bucket decide.
Files: upload sends the file picked in a File upload field of the page (pick the field by its name). Without a path, the file goes into the visitor's own folder, named <their user id>/<date>-<file name>, which is what the rule each user, in their own folder expects. Files sends every file picked (turn on Several files on the field), Same path replaces a file already there, Max size and Accepted types refuse other files before anything is sent. The answer has the path of the file, its url, name, size and type; the field is emptied after the upload. Save the path in a row of a table (Add row) to keep the link between the row and its file.
Files: list lists the files of a folder (auth.uid: the visitor's own folder), with a search, a sort, a limit: each has name, path, size, type, created_at and url; a sub-folder has folder set to true.
Files: get link gives the link of a file from its path.
Files: delete deletes a file. When nothing was deleted (no such file, or a rule says no), the step fails, instead of saying nothing.
Link, on upload, list and get link: public link, or signed link for a private bucket, valid for the number of seconds of Link lasts (one hour by default).
An error (a file too large, a type not accepted, a rule that refuses) stops the event, unless you name an error variable: it receives message and statut, and the event goes on.
A site with its own database uses Your API: upload file: it sends the file to your API (POST to its address) as a form, under the name file or the one you give, with the fields you add, the headers of your API and, when it asks for it, the token of the visitor. Files never go through the Soltis proxy: a header with a locked key stays empty there, and the publication says so.
Live updates
A chat, notifications, a shared board: your pages can follow the changes of a table as they happen.
In Database, the menu of a table, Turn on live updates (with Allow changes on): its changes are sent to the pages that follow it. A table that sends them shows a small signal in the list and Live updates at the top. Each visitor only gets the changes of the rows the rules of the table let them read.
A page event (or an app event) When a row of a table changes (live): pick the table, the change (any, a row added, changed or deleted) and, if you want, only rows where a column equals a value, like room_id = $room. Inside the event, ctx.change.type is INSERT, UPDATE or DELETE, ctx.change.record the row, ctx.change.old_record what it was (its key only, for a deleted row).
A Supabase collection with Stay up to date on live reloads by itself when its table changes, without showing its loading state. Several changes close together reload it once.
Your own server: a page event When your server sends an event (live) listens to a stream of Server-Sent Events (text/event-stream) at an https address, or at a path of your API like /events. ctx.message.data is what your server sent (read as JSON when it is JSON). Send the visitor's token adds it to the address as access_token.
Any collection can also reload every N seconds (5 at least) while the page is on screen.
The connection to Supabase reconnects by itself after a cut, and closes when the visitor leaves the page.
In Supabase
Open in Supabase opens the project in the Supabase dashboard, for everything Database does not do: functions, triggers, rules of your own and the settings of the project.
Working with Amanda
An assistant that builds and edits your real project.
Open Amanda in the top bar and describe what you want in plain words. She builds pages and sections, styles them, connects data and writes events, then applies the change to your project.
Choosing what she changes
Select an element in Design first, and she works on that element only: Only what I selected keeps the rest of the page out of reach. Without a selection she works on the page you have open.
Apps with data
Ask Amanda for an app while no database is connected, and she first asks where the data should live: your own database (give her the address of its API and its tables, and she connects it), Supabase (Database, Connect Supabase; a free account, billed by Supabase separately from your Soltis plan), or nothing for now. Then she builds with the matching steps, and with sign-in by Google, GitHub… once a provider is on.
Undo, per answer
Every answer shows what changed. An answer that changed the project has an Undo this change button: the project goes back to just before that answer, and anything changed after it is undone too (Ctrl Z brings it back). The button stays on your recent answers until you reload the builder.
Files and screenshots
Attach up to 8 files to a message (images, PDF, zip, code, text), by drag and drop or by pasting. The text of a file (and the text read in a PDF or a zip) stays with the chat: later in the conversation, ask about it (find the Supabase key in the HTML I sent) and Amanda reads it again. Images and videos are seen with the message they come with. Next to the files, Amanda shows about how many tokens they will use. A message is limited to about 120,000 tokens of attachments (roughly 300 pages of text), and never more than the tokens you have left: above that, the message is not sent and nothing is used.
Your chats
Every chat is saved with its project and listed on the left: pinned chats first, then your folders, then the others by date (Today, Previous 7 days, Previous 30 days, Older). The … menu of a chat renames it, pins it, moves it to a folder (or a new one) and deletes it; a folder has its own menu to rename or remove it. Opening another project shows that project's chats.
AI tokens and usage
The AI tokens card at the bottom of the chat list shows the tokens you have left and the day your monthly tokens refill; its ring empties as you use the month's tokens. Click it to open Usage:
Plan usage limits: the share of your monthly tokens already used, the day they refill, and what you used today, over the last 7 days and the last 30 days.
Extra tokens: the tokens you bought, used and left. They are used once your monthly tokens run out and never expire. Pick a pack and the button shows its price; the payment opens in a new tab, and you come back to the builder once it is paid.
Your plan: what the plan above yours adds, or a monthly boost on top of your plan.
Reasoning: how much Amanda thinks before she answers, with what each level uses.
Stopping Amanda
While she works, the send button becomes a square: click it to stop her. Nothing more is applied; what she had already applied stays, and Undo this change removes it. Leaving the project stops her too. When your tokens run out, she stops and says so.
Questions and changes
Ask a question (why is the button not centered?, how far can I go with Soltis?) and she answers it. If her answer also proposes a change, you are asked before anything is applied. Ask for a change (center the button) and she makes it.
Checks before a change is kept
Her code is validated before anything is applied. When it is refused, she corrects it (the step shows Fixing), and the builder repairs the small mistakes itself.
The pages she touched are rendered and measured once her change is applied: unreadable text, a page without background under light text, a white field or an invisible button on a dark page are sent back to her once to fix, with the exact elements. That fix only touches those elements and the ones of her answer.
A squeezed column is caught the same way: a button whose label breaks on two lines, a field narrower than 150 px, a title with one word per line. She is told which element pushes it.
A row whose two blocks end up one under the other on a desktop screen (a cover above its title) is caught too, with the change that keeps them side by side.
Your app has a look, and she follows it. She is given the look of your app, measured on its pages: the page that leads to the one she builds, and the header they share (the background, the text, the fonts, the surfaces, the buttons, the badges, the titles, the accent). A new page takes that background, text and fonts, and what she leaves unstyled takes the rest. A page she builds in colors that are not your app's is sent back to her once, with the colors to use; its layout stays. A screenshot you show her is a model for the layout, not for the colors, unless you ask for them.
When a check sends something back, she also sees the page as it is rendered, on a desktop screen and on a phone, and fixes what looks wrong in what she just made.
Logic that cannot work (an alert during a drag) is refused with the right way to do it.
When a change is not applied
If her code is still refused after her corrections, nothing is applied and your project is unchanged. Her message ends with a box that says so, and Try again asks her to make it again. The same box tells you when you stopped her, when the project changed while she was working, or when your tokens ran out.
Reasoning
The button next to the paperclip sets how much Amanda thinks before she answers. Low is the default and right for most changes; Medium, High and Max plan longer before building, for complex pages and logic. More reasoning uses more tokens: each level shows its use next to it (for example ×1.7 means about 1.7 times the tokens of the same message at Low), measured on real messages as soon as there are enough. Your choice is kept for your next messages.
What Amanda changes
Amanda changes what you ask, on the page you ask about, and nothing else. Every element of a project has its own number, so a change she makes on one page never lands on another. The background, text color and fonts she gives a page she writes are not applied to your other pages.
Changes to your database
When what you ask needs a new table, a column, an access rule or live updates in your Supabase project, Amanda proposes the change instead of making it: her answer lists each change in order, with Review and apply. The review shows the exact SQL that runs in your project and asks you to confirm what deletes data; nothing runs before you click Apply. Skip leaves a change out. Once every change is applied or skipped, Amanda is told and goes on with your request: the collections, forms and steps that use the new table. Allow changes must be on for the project: the first review asks to turn it on.
Testing your API
When she needs the real answer of your API to bind the right fields, she shows a button in her message: click it to run the request, and she uses the result. What she built before asking (the collection, the page) is applied first. A field that contains HTML is shown with a rich text element, not a paragraph.
Getting good results
Describe the result, not the implementation: a pricing section with three plans, the middle one highlighted.
Point at something precise: select it, or name it (the hero title).
Ask for one thing at a time when it matters, then refine.
Amanda is included in the paid plans and uses AI tokens. Your balance is in Account & billing. At 80% of the month's tokens, Amanda shows it, with Get more tokens. See AI tokens.
Publish and hosting
Online in seconds, on Soltis or anywhere else.
Publishing
Click Publish. Every page is rendered and your site goes online at its Soltis address (https://<id>.soltis.app), with a 404 page, a sitemap and a robots file. Publishing again replaces the previous version: new visitors get it right away, and a visitor who already had the page open gets it within 5 minutes.
Addresses
Pages have clean addresses: /, /pricing, /book/my-first-book. Add your own domain from Settings › Hosting.
Export
Download your site as a zip, either as ready-to-host HTML files or as its Albert source (re-importable). The export works on any host; it uses .html addresses.
Pages delivered
Usage is counted in pages delivered: one page of your site sent by our servers to a browser or a robot (search engines included). A page the visitor already has is not counted again. Visits are shown for information. When a plan limit is reached, the site shows a paused page until next month or an upgrade; on Pro, extra pages are billed instead and the site is never paused. See Plans and billing.
Mobile app
In Settings › Mobile app, Installable app lets visitors add your site to their home screen: your name, your icon, the colors of your theme, full screen. Portrait only keeps the installed app upright when the phone turns. Android follows it; on an iPhone, an app added from Safari turns anyway. In a browser tab, the page always turns with the phone.
Custom code
On paid plans, Custom code in <head> adds analytics, a verification tag or a chat widget to every page. It never runs inside the builder.
Custom domain
Your site on your own domain, with HTTPS.
Available on paid plans, one domain per published site.
Add itIn Settings › Hosting, click Add domain and type it: www.yourdomain.com.
Create two recordsAt your registrar, in the DNS of your domain, create the two records shown by Soltis (table below).
VerifyBack in Soltis, click Verify. As long as the DNS is not visible yet, Soltis says which record is missing: wait a few minutes and click again.
DoneOnce active, the HTTPS certificate is issued automatically within minutes, and your site answers on your domain.
Type
Name
Value
CNAME
www
sites.soltis.app
TXT
_soltis.www
the soltis-verification=… value shown by Soltis
The TXT record proves that the domain is yours. In the name field, type the short name: most registrars add the rest of the domain themselves.
If your DNS is at Cloudflare, the CNAME must be DNS only (grey cloud), not proxied.
The bare domain
For yourdomain.com without www, create a redirection to https://www.yourdomain.com at your registrar (often called URL redirect or forwarding).
Good to know
The domain can be changed once a month.
If the plan ends, the domain is disconnected; it comes back when you subscribe again.
If the domain does not answer
Check that the CNAME points to sites.soltis.app and is not proxied.
Check that Verify succeeded and the domain shows as active.
Give the certificate a few minutes: it is issued after the verification, not before.
SEO
What search engines need, and where to set it.
A title and a description per page, in the page settings: the title under 60 characters with the main words first, the description between 120 and 155 characters.
One main heading per page (heading level 1), then level 2 for the sections.
A description for every image (its alternative text).
A social image, shown when your page is shared.
A favicon, with an optional light version for dark browser tabs.
Generated for you
Every publish writes the sitemap (/sitemap.xml), the robots file and the canonical address of each page, and tells Bing about your pages (see below). For Google, follow Get found on Google once; a new domain takes days to weeks to be indexed.
Get found on Google
Google lists a site once its owner has proved it is theirs, in Google Search Console. Nobody else can do that for you, Soltis included: that is why it cannot be automatic. It takes five minutes, once per site:
Open Google Search Console and add a property of type URL prefix with your site address (your custom domain if you have one).
Choose the HTML tag method and copy the tag.
In the builder, Settings › Search engines, paste it (the whole tag or just its code). It goes into every page at your next publish, on every plan.
Publish, then click Verify in Search Console.
In Sitemaps, submit your sitemap address. Settings › Search engines shows it, ready to copy.
After a publish, the publish window shows a Get your site on Google button as long as no code is set.
Bing and the others
Every publish tells Bing which pages your site has (IndexNow), with nothing to set up. DuckDuckGo and Yahoo rely largely on Bing's results, and Yandex, Seznam and Naver receive the same notice. Google does not take part in IndexNow: use Search Console as above.
Dynamic pages
Give a dynamic page an SEO source so that each item has its own title, description and image for search engines and social networks, and a sitemap source so that every item address is listed.
Pages to hide
A page that, when it opens, sends visitors who are not signed in to another page (a dashboard, an account page) is hidden from search engines automatically, and left out of sitemap.xml: its settings show Hide this page from search engines ticked, with the reason. Untick it to list the page anyway.
For any other page that must not appear, a thank-you page or a reset password page: tick Hide this page from search engines. It stays reachable by its address; to make it private, protect it (how).
A dynamic page is listed by its items (/book/dune), never on its own: /book alone shows no book, so it is kept out of search engines.
Versions and backups
Nothing gets lost.
Automatic backups
While you work, your project is backed up automatically (at most every 10 minutes, and only when it changed). Soltis keeps the 5 most recent backups, the last one of each of the past 14 days, and the 2 oldest. Restore any of them from the dashboard: Backups in the menu of the project.
Named versions
In Settings › Versions, give the current state a name and click Save as: a named version stays until you delete it, whatever the automatic backups do.
Plan
Named versions per project
Free
3
Starter
8
Pro
15
Restoring first backs up the current state, so a restore can itself be undone.
History in your browser
Undo and redo are kept in your browser for each project: after a reload, a closed tab or a crash, Ctrl Z still goes back. The clock button next to undo and redo opens History: every change, newest first, with its time and what it did (an answer from Amanda, a restored version, Variable $total added, Page /home changed). Go back before this undoes the later changes in one go; Redo up to here brings them back. Restoring a version or a backup in the editor can be undone the same way. Signing out clears the history kept in that browser.
Plans and billing
What each plan includes, and how usage is counted. All prices exclude VAT.
50,000 protected calls per month, then €10 per extra million
1 million protected calls per month, then €10 per extra million
AI tokens per month
none
4 million
12 million
Named versions
3
8
15
Soltis badge
yes
no
no
At the limit
You get an email at 80% of the pages of your plan, and another one at 100%. Then sites on Free and Starter show a paused page until next month or an upgrade. A Pro site is never paused: the extra pages appear on the next invoice.
Protected calls
A protected call is one request from your published site to your API through the Soltis proxy, which adds your secret keys on our side (Secret keys). Starter includes 50,000 per month and Pro 1 million. Above that, each started million costs €10, added to the invoice at the start of the next month: your site is never paused for it. To know the amount in advance, add Secured API calls in Account & billing › Add-ons: 1 million more calls every month for €10 per month, as many times as you need. The counter is in Account & billing › Plan & usage. With your own proxy (a Worker on your Cloudflare account), the calls count on your Cloudflare account, at Cloudflare's prices, not on Soltis.
Options
On any paid plan, from Account & billing: an extra published site for €10 per month, 50 extra projects for €5 per month, 1 million secured API calls per month for €10 per month, and 10 GB of file storage for €5 per month. If an option stops, your oldest sites stay online, and your projects and files are always kept.
AI tokens
Paid plans include AI tokens every month. If you need more: a monthly boost (renewed with your plan), or a one-off pack that never expires.
Tokens
Monthly boost
One-off pack
5M
€10 per month
€13
12M
€20 per month
€26
30M
€50 per month
€65
60M
€100 per month
€130
150M
€250 per month
€325
300M
€500 per month
€650
640M
€1,000 per month
€1,300
1.32B
€2,000 per month
€2,600
2.8B
€4,000 per month
€5,200
Prices exclude VAT. M is a million tokens, B a billion. Amanda tells you when 80% of the month's tokens are used, with a button to get more. The reasoning level changes how many tokens a message uses (how).
Account & billing
From the dashboard: your plan and usage, plans, tokens, options, the subscription portal (invoices, payment method), your email and password, the export of your data, and a request to delete your account.
Soltis Docs. Something missing or unclear? Ask Amanda in the builder, or write to support from the dashboard.